何时是类型擦除后输入的函数的generics返回值?

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

我在Oracle文档的Java教程中找到了以下语句:

  • 如有必要,插入类型铸件以保持类型安全。 Java教程:类型擦除

没有解释“如果有必要”究竟意味着什么,而且我根本没有在Java语言规范中找到这些演员 ,所以我开始尝试。

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

// Java source public static  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' } 

使用javac编译并使用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()); } 

我可以看到,在第一种情况下有一个确保类型安全的铸件,但在第二种情况下它被省略。 当然很好,因为我想将返回值存储在Object类型的变量中,因此根据类型安全性,强制转换并不是必需的。 然而,它导致一个有趣的行为与不安全的演员:

 public class Erasure { public static  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.unsafeIdentity("foo"); System.out.println(c.getClass().getName()); // but Prints 'java.lang.String' } } 

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

 // 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()); 

这意味着如果generics函数返回给定类型的对象,则无法保证它最终返回该类型。 使用上面代码的应用程序将在尝试将返回值转换为Integer的第一个点失败,如果它完全这样做的话,所以我觉得它打破了fail-fast原则 。

编译期间编译器插入此强制转换的确切规则是什么,以确保类型安全以及指定的规则在哪里?

编辑:

我看到编译器不会深入研究代码并试图certificate通用代码确实返回它应该的内容,但是它可以插入一个断言,或者至少是一个类型转换(它在特定情况下已经做过,如下所示)第一个例子)确保正确的返回类型,因此后者会抛出ClassCastException

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

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

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

 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()); } 

在第一种情况下,在删除的代码中需要强制转换,因为如果删除它,则擦除的代码将无法编译。 这是因为Java保证在可执行类型的引用变量中运行时保存的内容必须是可重新类型的instanceOf ,因此这里需要运行时检查。

在第二种情况下,擦除的代码在没有强制转换的情况下编译。 是的,如果您添加了演员表,它也会编译。 所以编译器可以决定哪种方式。 在这种情况下,编译器决定不插入强制转换。 这是一个完全有效的选择。 您不应该依赖编译器来决定任何一种方式。

版本1是首选,因为它在编译时失败。

Typesafe版本1非遗留代码:

 class Erasure { public static  T unsafeIdentity(T x) { //no cast necessary, type checked in the parameters at compile time return x; } public static void main(String args[]) { // This will fail at compile time and you should use Integer c = ... in real code Object c = Erasure.unsafeIdentity("foo"); System.out.println(c.getClass().getName()); } } 

Typesafe版本2遗留代码( 运行时类型错误[…]在自动生成的 强制转换中 引入,以确保对不可重新生成的类型和引用类型转换 的操作的有效性 ):

 class Erasure { public static  T unsafeIdentity(Object x) { return (T) x; //Compiled version: return (Object) x; //optimised version: return x; } public static void main(String args[]) { // This will fail on return, as the returned Object is type Object and Subtype Integer is expected, this results in an automatic cast and a ClassCastException: Integer c = Erasure.unsafeIdentity("foo"); //Compiled version: Integer c = (Integer)Erasure.unsafeIdentity("foo"); System.out.println(c.getClass().getName()); } } 

TypeSafe版本3遗留代码,每次都知道超类型的方法( JLS类型变量的擦除(第4.4节)是其最左边界的擦除。 ):

 class Erasure { public static  T unsafeIdentity(Object x) { // This will fail due to Type erasure and incompatible types: return (T) x; // Compiled version: return (Integer) x; } public static void main(String args[]) { //You should use Integer c = ... Object c = Erasure.unsafeIdentity("foo"); System.out.println(c.getClass().getName()); } } 

Object仅用于说明Object是版本1和版本3中的有效分配目标,但如果可能,您应该使用实际类型或generics类型。

如果你使用另一个版本的java,你应该查看规范的特定页面,我不期望任何更改。

我无法解释它,但评论不能添加我想要的代码,所以我添加这个答案。 希望这个答案可以帮助你理解。评论不能添加我想要的代码。

在你的代码中:

 public class Erasure { public static  T unsafeIdentity(Object x) { return (T) x; } public static void main(String args[]) { // I would expect it to fail: Object c = Erasure.unsafeIdentity("foo"); System.out.println(c.getClass().getName()); // but Prints 'java.lang.String' } } 

它将在编译后擦除generics。 在编译时,Erasure.unsafeIdentity没有错误。 jvm擦除generics依赖于你给出的generics参数(整数)。 之后,function是这样的?:

 public static Integer unsafeIdentity(Object x) { return x; } 

事实上,协变返回将添加桥接方法 :

 public static Object unsafeIdentity(Object x) { return x; } 

如果该函数与上一个函数类似,您认为main方法中的代码编译失败吗? 它没有错误。genericsErasure不会在此函数中添加强制转换,并且返回参数不是java函数的缩进。

我的解释有点牵强,但希望可以帮助你理解。

编辑:

谷歌关于该主题后,我猜你的问题是使用桥接方法的协变返回类型。 BridgeMethods