类型擦除后功能的通用返回值是什么时候? [英] When is generic return value of function casted after type erasure?

查看:134
本文介绍了类型擦除后功能的通用返回值是什么时候?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这个问题是由关于不安全转换的StackOverflow问题:Java Casting方法不知道要投入什么。在回答我遇到这个问题的时候,我无法根据纯粹的规范来解释



我在The Java Tutorials的$ b中找到以下语句$ b Oracle文档:



没有说明如有必要 em>的意思,以及
我已经找到这些演员表中没有提及 Java语言
规范
,所以我开始尝试。



让我们看看下面这段代码:

  // Java源代码
public static< T> T身份(T x){
return x;

public static void main(String args []){
String a = identity(foo);
System.out.println(a.getClass()。getName());
//打印'java.lang.String'

Object b = identity(foo);
System.out.println(b.getClass()。getName());
//打印'java.lang.String'
}

编译 javac 并使用 Java反编译器进行反编译:

  //反编译代码
public static void main(String [] paramArrayOfString)
{
/ /编译器插入一个强制转换为字符串以确保类型安全
String str =(String)identity(foo);
System.out.println(str.getClass()。getName());

//编译器省略了强制转换,因为在运行时类型安全性方面不需要
//,但它实际上可以
//执行额外的检查。它是一种优化
//减少开销吗?这种行为在哪里指定?
Object localObject1 = identity(foo);
System.out.println(localObject1.getClass()。getName());
}

我可以看到在第一种情况下有一个确保类型安全的强制转换,
,但在第二种情况下,它被省略。它当然是
,因为我想将返回值存储在一个 Object
类型变量中,所以这个转换不是必须的类型安全。然而,它会导致一个有趣的行为,带有不安全的强制转换:

  public class Erasure {
public static< T> T unsafedentity(Object x){
return(T)x;
}

public static void main(String args []){
//我希望c在这个
//之后可以是一个Integer,或者当
//返回值不是整数
Object c =擦除时抛出ClassCastException。< Integer> unsafeIdentity(foo);
System.out.println(c.getClass()。getName());
//但打印'java.lang.String'
}
}

编译和反编译,我看到没有类型转换以确保在运行时返回类型正确:

  // //类型不检查unsafeIdentity的返回值,
//就像在第二个例子中一样。
Object localObject2 = unsafeIdentity(foo);
System.out.println(localObject2.getClass()。getName());

这意味着如果一个通用函数应该返回一个给定的对象
类型,但不能保证它最终会 返回该类型。使用上述代码的
应用程序在尝试
将返回值转换为整数的第一点会失败,如果这样做的话,所以我觉得
违反了快速原则


编译器在
编译过程中插入这个转换的确切规则是什么,以确保类型的安全性以及这些规则在哪里指定?



编辑:



我发现编译器不会深入代码,试图证明泛型代码确实返回了它应该做的事情,但是它可以插入一个断言或至少一个类型转换(在第一个例子中它已经在特定情况下已经做了)以确保正确的返回类型,所以后者会抛出 ClassCastException

  //它可以编译成这个,抛出ClassCastException:
Object localObject2 =(In teger)unsafeIdentity( 富);


解决方案

如果在规范中找不到它,这意味着它没有被指定,只要擦除的代码符合非泛型代码的类型安全规则,就由编译器实现决定在哪里插入强制类型转换。



在这种情况下,编译器的已擦除代码如下所示:

  public static Object identity(Object x){ 
return x;

public static void main(String args []){
String a =(String)identity(foo);
System.out.println(a.getClass()。getName());

Object b = identity(foo);
System.out.println(b.getClass()。getName());



$ b $ p
$ b

在第一种情况下,转换在擦除代码中是必需的,因为如果你删除它,删除的代码不会编译。这是因为Java保证在可执行类型的引用变量中的运行时保持的是可重用类型的 instanceOf ,因此在此需要运行时检查。



在第二种情况下,已删除的代码无需转换即可编译。是的,如果您添加了演员阵容,它也会进行编译。所以编译器可以决定哪种方式。在这种情况下,编译器决定不插入转换。这是一个完全有效的选择。您不应该依赖编译器来决定。


This question was inducted by this StackOverflow question about unsafe casts: Java Casting method without knowing what to cast to. While answering the question I encountered this behaviour I couldn't explain based on purely the specification

I found the following statement in The Java Tutorials at the Oracle docs:

It is not explained what "if necessary" means exactly, and I've found no mention about these casts in the Java Language Specification at all, so I started to experiment.

Let's look at the following piece of code:

// Java source
public static <T> T identity(T x) {
    return x;
}
public static void main(String args[]) {
    String a = identity("foo");
    System.out.println(a.getClass().getName());
    // Prints 'java.lang.String'

    Object b = identity("foo");
    System.out.println(b.getClass().getName());
    // Prints 'java.lang.String'
}

Compiled with javac and decompiled with the Java Decompiler:

// Decompiled code
public static void main(String[] paramArrayOfString)
{
    // The compiler inserted a cast to String to ensure type safety
    String str = (String)identity("foo");
    System.out.println(str.getClass().getName());

    // The compiler omitted the cast, as it is not needed
    // in terms of runtime type safety, but it actually could
    // do an additional check. Is it some kind of optimization
    // to decrease overhead? Where is this behaviour specified?
    Object localObject1 = identity("foo");
    System.out.println(localObject1.getClass().getName());
}

I can see that there is a cast which ensures type safety in the first case, but in the second case it is omitted. It is fine of course, because I want to store the return value in an Object typed variable, so the cast is not strictly necessary as per type safety. However it leads to an interesting behaviour with unsafe casts:

public class Erasure {
    public static <T> T unsafeIdentity(Object x) {
        return (T) x;
    }

    public static void main(String args[]) {
        // I would expect c to be either an Integer after this
        // call, or a ClassCastException to be thrown when the
        // return value is not Integer
        Object c = Erasure.<Integer>unsafeIdentity("foo");
        System.out.println(c.getClass().getName());
        // but Prints 'java.lang.String'
    }
}

Compiled and decompiled, I see no type cast to ensure correct return type at runtime:

// The type of the return value of unsafeIdentity is not checked,
// just as in the second example.
Object localObject2 = unsafeIdentity("foo");
System.out.println(localObject2.getClass().getName());

This means that if a generic function should return an object of a given type, it is not guaranteed it will return that type ultimately. An application using the above code will fail at the first point where it tries to cast the return value to an Integer if it does so at all, so I feel like it breaks the fail-fast principle.

What are the exact rules of the compiler inserting this cast during compilation that ensures type safety and where are those rules specified?

EDIT:

I see that the compiler will not dig into the code and try to prove that the generic code really returns what it should, but it could insert an assertation, or at least a type cast (which it already does in specific cases, as seen in the first example) to ensure correct return type, so the latter would throw a ClassCastException:

// It could compile to this, throwing ClassCastException:
Object localObject2 = (Integer)unsafeIdentity("foo");

解决方案

If you can't find it in the specification, that means it's not specified, and it is up to the compiler implementation to decide where to insert casts or not, as long as the erased code meets the type safety rules of non-generic code.

In this case, the compiler's erased code looks like this:

public static Object identity(Object x) {
    return x;
}
public static void main(String args[]) {
    String a = (String)identity("foo");
    System.out.println(a.getClass().getName());

    Object b = identity("foo");
    System.out.println(b.getClass().getName());
}

In the first case, the cast is necessary in the erased code, because if you removed it, the erased code wouldn't compile. This is because Java guarantees that what is held at runtime in a reference variable of reifiable type must be instanceOf that reifiable type, so a runtime check is necessary here.

In the second case, the erased code compiles without a cast. Yes, it will also compile if you added a cast. So the compiler can decide either way. In this case, the compiler decided not to insert a cast. That is a perfectly valid choice. You should not rely on the compiler to decide either way.

这篇关于类型擦除后功能的通用返回值是什么时候?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆