这个问题是由StackOverflow关于不安全转换的问题引起的:Java Casting方法,不知道要转换为什么.在回答我遇到此问题的问题时,我无法根据纯粹的规范进行解释
我在Oracle文档的Java教程中找到了以下语句:
如有必要,插入类型铸件以保持类型安全. Java教程:类型擦除
没有解释"如果有必要"究竟意味着什么,而且我根本没有在Java语言规范中找到这些演员,所以我开始尝试.
让我们看看下面这段代码:
// Java source public staticT 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' }
使用Java Decompiler编译javac
和反编译:
// 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 staticT 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());
这意味着如果泛型函数应返回给定类型的对象,则无法保证它最终将返回该类型.使用上面代码的应用程序将在尝试将返回值转换为a的第一点失败,Integer
如果它完全如此,那么我觉得它打破了故障快速原则.
编译期间编译器插入此强制转换的确切规则是什么,以确保类型安全以及指定的规则在哪里?
编辑:
我看到编译器不会深入研究代码并试图证明通用代码确实返回它应该的东西,但是它可以插入一个断言,或者至少是一个类型转换(它在特定情况下已经做过,如下所示)第一个例子)确保正确的返回类型,所以后者会抛出ClassCastException
:
// It could compile to this, throwing ClassCastException: Object localObject2 = (Integer)unsafeIdentity("foo");
newacct.. 5
如果您在规范中找不到它,则表示未指定它,并且只要擦除的代码满足非泛型代码的类型安全规则,则由编译器实现决定是否在哪里插入强制类型转换。 。
在这种情况下,编译器的擦除代码如下所示:
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
该可更改类型,因此此处需要进行运行时检查。
在第二种情况下,擦除的代码无需强制转换即可编译。是的,如果添加了强制转换,它也会编译。因此,编译器可以决定采用哪种方式。在这种情况下,编译器决定不插入强制类型转换。那是一个完全正确的选择。您不应该依赖编译器来决定哪种方式。
如果您在规范中找不到它,则表示未指定它,并且只要擦除的代码满足非泛型代码的类型安全规则,则由编译器实现决定是否在哪里插入强制类型转换。 。
在这种情况下,编译器的擦除代码如下所示:
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
该可更改类型,因此此处需要进行运行时检查。
在第二种情况下,擦除的代码无需强制转换即可编译。是的,如果添加了强制转换,它也会编译。因此,编译器可以决定采用哪种方式。在这种情况下,编译器决定不插入强制类型转换。那是一个完全正确的选择。您不应该依赖编译器来决定哪种方式。