【发布时间】:2016-01-03 05:05:51
【问题描述】:
这个问题是由this StackOverflow question about unsafe casts: Java Casting method without knowing what to cast to 提出的。在回答我遇到这种行为的问题时,我无法仅根据规范来解释
我在 The Java Tutorials 中找到了以下语句 甲骨文文档:
- 必要时插入类型转换以保持类型安全。 The Java Tutorials: Type Erasure
没有解释“如果需要”的确切含义,并且 我在Java Language Specification 中发现根本没有提及这些演员阵容,所以我开始尝试。
我们来看下面这段代码:
// 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'
}
用javac编译,用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());
}
我可以看到在第一种情况下有一个确保类型安全的强制转换,
但在第二种情况下,它被省略了。这是
当然可以,因为我想将返回值存储在 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());
这意味着如果一个泛型函数应该返回一个给定的对象
类型,不能保证它会最终返回该类型。一个
使用上述代码的应用程序将在它尝试的第一个点失败
将返回值转换为Integer,如果它这样做的话,所以我觉得
它打破了fail-fast principle。
编译器在插入此强制转换期间的确切规则是什么 确保类型安全的编译以及这些规则在哪里指定?
编辑:
我看到编译器不会深入研究代码并试图证明通用代码确实返回了它应该返回的内容,但它可以插入一个断言,或者至少是一个类型转换(它在特定情况下已经这样做了,如第一个示例所示)以确保返回类型正确,因此后者将抛出 ClassCastException:
// It could compile to this, throwing ClassCastException:
Object localObject2 = (Integer)unsafeIdentity("foo");
【问题讨论】:
-
我不是这方面的专家,但我认为编译器在这种情况下无法进行任何检查,因为(1)当它看到
return (T) x;行时,它没有办法静态知道x不能转换为T; (2) 当您实际调用unsafeIdentity时,编译器无法知道这将失败因为它不会深入研究方法的代码并查找将失败的语句。基本上,我认为这意味着方法中对(T)的强制转换是没有用的。 -
感谢@ajb,当然转换为 (T) 是没用的,它真的是一个极简的例子。但它可以很容易地将外部函数编译为
Object o = (Integer)unsafeIdentity("foo");,这会抛出ClassCastException还是我遗漏了什么? -
我认为编译器不会/必须插入断言,除非您对其进行编码,那么为什么要在这里这样做呢?但问题的其余部分很有趣,+1。
-
好的,我明白了——该方法被声明为返回一个
T,所以我可以看到编译器如何能够在不阅读方法代码的情况下添加此检查。但这会在绝大多数情况下增加不必要的开销,包括许多Collections类,例如,get()方法返回泛型类型。这可能是一个不可接受的权衡。 -
我不完全理解这个问题。为什么要插入演员表?它在编译
unsafeIdentity方法时明确警告未经检查的强制转换,从那以后,无论如何都不能保证类型。但是,我认为hg.openjdk.java.net/jdk8/jdk8/langtools/file/756ae3791c45/src/… 在这里可能是相关的,因为它清楚地表明它根本不会在不需要时插入演员表(事实上,这甚至可能是您问题的答案 - 但我不是当然)
标签: java generics type-erasure