这个问题是由有关不安全强制转换的StackOverflow问题引起的:Java强制转换方法不知道要强制转换为什么。在回答我遇到的问题时,我无法仅根据规范进行解释
我在Oracle文档的The Java Tutorials中找到了以下语句:
- 必要时插入类型转换,以保持类型安全。Java教程:类型擦除
没有解释“必要时”的确切含义,并且在Java语言规范中我完全没有提到这些强制转换,因此我开始尝试。
让我们看下面的代码:
// 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'
}
使用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 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'
}
}
经过编译和反编译,我看不到任何类型强制转换以确保在运行时返回正确的类型:
// 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");
如果您在规范中找不到它,则表示未指定它,并且只要擦除的代码满足非泛型代码的类型安全规则,则由编译器实现决定是否在哪里插入强制类型转换。 。
在这种情况下,编译器的擦除代码如下所示:
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
该可更改类型,因此此处需要进行运行时检查。
在第二种情况下,擦除的代码无需强制转换即可编译。是的,如果添加了强制转换,它也会编译。因此,编译器可以选择任何一种方式。在这种情况下,编译器决定不插入强制类型转换。那是一个完全有效的选择。您不应该依赖编译器来决定哪种方式。
本文收集自互联网,转载请注明来源。
如有侵权,请联系 [email protected] 删除。
我来说两句