什么时候在类型擦除后转换函数的泛型 return 值?
When is generic return value of function casted after type erasure?
这个问题是由 提出的。在回答这个问题时,我遇到了这种行为,我无法仅根据规范
进行解释
我在 Java 教程中找到了以下语句
Oracle 文档:
- Insert type casts if necessary to preserve type safety.
The Java Tutorials: Type Erasure
没有解释 "if necessary" 的确切含义,并且
我发现 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'
}
用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());
}
我可以看到在第一种情况下有一个确保类型安全的转换,
但在第二种情况下,它被省略了。这是
当然可以,因为我想将 return 值存储在 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'
}
}
编译和反编译,我没有看到类型转换以确保在运行时正确的 return 类型:
// 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());
这意味着如果一个泛型函数应该return一个给定的对象
类型,不能保证 最终会 return 那个类型。一个
使用上述代码的应用程序将在它尝试的第一点失败
将 return 值转换为 Integer
如果它确实如此,所以我觉得
它打破了 fail-fast principle.
编译器插入此强制转换的确切规则是什么
确保类型安全的编译以及这些规则在哪里指定?
编辑:
我看到编译器不会深入研究代码并尝试证明通用代码确实 return 它应该是什么,但它可以插入一个断言,或者至少是一个类型转换(这它已经在特定情况下执行,如第一个示例所示)以确保正确的 return 类型,因此后者会抛出 ClassCastException
:
// It could compile to this, throwing ClassCastException:
Object localObject2 = (Integer)unsafeIdentity("foo");
版本 1 更可取,因为它在 compiletime.
失败
类型安全版本 1 non-legacy 代码:
class Erasure {
public static <T> 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.<Integer>unsafeIdentity("foo");
System.out.println(c.getClass().getName());
}
}
类型安全版本 2 遗留代码 (A run-time type error [...] In an automatically generated cast introduced to ensure the validity of an operation on a non-reifiable type and reference type casting):
class Erasure {
public static <T> 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.<Integer>unsafeIdentity("foo");
//Compiled version: Integer c = (Integer)Erasure.unsafeIdentity("foo");
System.out.println(c.getClass().getName());
}
}
TypeSafe 版本 3 遗留代码,您每次都知道超类型的方法 (JLS The erasure of a type variable (§4.4) is the erasure of its leftmost bound.):
class Erasure {
public static <T extends Integer> 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.<Integer>unsafeIdentity("foo");
System.out.println(c.getClass().getName());
}
}
在版本1和3中,Object只是用来说明Object是一个有效的赋值目标,但是你应该尽可能使用真实类型或泛型。
如果您使用其他版本的 java,您应该查看规范的特定页面,我不希望有任何更改。
我解释的不是很好,但是评论不能像我想的那样添加代码,所以我添加了这个答案。只是希望这个答案可以帮助您 understanding.The 评论不能像我想要的那样添加代码。
在您的代码中:
public class Erasure {
public static <T> T unsafeIdentity(Object x) {
return (T) x;
}
public static void main(String args[]) {
// I would expect it to fail:
Object c = Erasure.<Integer>unsafeIdentity("foo");
System.out.println(c.getClass().getName());
// but Prints 'java.lang.String'
}
}
它会在编译后擦除泛型。在编译时,Erasure.unsafeIdentity 没有错误。 jvm 擦除泛型取决于您提供的泛型参数(整数)。之后的功能是这样的?:
public static Integer unsafeIdentity(Object x) {
return x;
}
其实协变returns会加上Bridge Methods:
public static Object unsafeIdentity(Object x) {
return x;
}
如果函数和上一个一样,你认为你的main方法中的代码会编译失败吗?它没有 errors.Generics Erasure 不会在这个函数中添加强制转换,并且 return 参数不是 java 函数的标识。
我的解释有点牵强,但希望能帮助你理解。
编辑:
在 google 关于该主题之后,我猜你的问题是使用桥接方法的协变 return 类型。 BridgeMethods
如果你在规范中找不到它,那就意味着它没有被指定,并且由编译器实现来决定在哪里插入强制转换,只要擦除的代码符合类型安全规则non-generic 个代码。
在这种情况下,编译器擦除的代码如下所示:
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
那个具体化类型,所以这里需要进行运行时检查。
在第二种情况下,擦除的代码无需转换即可编译。是的,如果您添加了演员表,它也会编译。所以编译器可以决定任何一种方式。在这种情况下,编译器决定不插入强制转换。这是一个完全正确的选择。您不应该依赖编译器来决定任何一种方式。
这个问题是由
我在 Java 教程中找到了以下语句 Oracle 文档:
- Insert type casts if necessary to preserve type safety. The Java Tutorials: Type Erasure
没有解释 "if necessary" 的确切含义,并且 我发现 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'
}
用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());
}
我可以看到在第一种情况下有一个确保类型安全的转换,
但在第二种情况下,它被省略了。这是
当然可以,因为我想将 return 值存储在 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'
}
}
编译和反编译,我没有看到类型转换以确保在运行时正确的 return 类型:
// 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());
这意味着如果一个泛型函数应该return一个给定的对象
类型,不能保证 最终会 return 那个类型。一个
使用上述代码的应用程序将在它尝试的第一点失败
将 return 值转换为 Integer
如果它确实如此,所以我觉得
它打破了 fail-fast principle.
编译器插入此强制转换的确切规则是什么 确保类型安全的编译以及这些规则在哪里指定?
编辑:
我看到编译器不会深入研究代码并尝试证明通用代码确实 return 它应该是什么,但它可以插入一个断言,或者至少是一个类型转换(这它已经在特定情况下执行,如第一个示例所示)以确保正确的 return 类型,因此后者会抛出 ClassCastException
:
// It could compile to this, throwing ClassCastException:
Object localObject2 = (Integer)unsafeIdentity("foo");
版本 1 更可取,因为它在 compiletime.
失败类型安全版本 1 non-legacy 代码:
class Erasure {
public static <T> 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.<Integer>unsafeIdentity("foo");
System.out.println(c.getClass().getName());
}
}
类型安全版本 2 遗留代码 (A run-time type error [...] In an automatically generated cast introduced to ensure the validity of an operation on a non-reifiable type and reference type casting):
class Erasure {
public static <T> 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.<Integer>unsafeIdentity("foo");
//Compiled version: Integer c = (Integer)Erasure.unsafeIdentity("foo");
System.out.println(c.getClass().getName());
}
}
TypeSafe 版本 3 遗留代码,您每次都知道超类型的方法 (JLS The erasure of a type variable (§4.4) is the erasure of its leftmost bound.):
class Erasure {
public static <T extends Integer> 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.<Integer>unsafeIdentity("foo");
System.out.println(c.getClass().getName());
}
}
在版本1和3中,Object只是用来说明Object是一个有效的赋值目标,但是你应该尽可能使用真实类型或泛型。
如果您使用其他版本的 java,您应该查看规范的特定页面,我不希望有任何更改。
我解释的不是很好,但是评论不能像我想的那样添加代码,所以我添加了这个答案。只是希望这个答案可以帮助您 understanding.The 评论不能像我想要的那样添加代码。
在您的代码中:
public class Erasure {
public static <T> T unsafeIdentity(Object x) {
return (T) x;
}
public static void main(String args[]) {
// I would expect it to fail:
Object c = Erasure.<Integer>unsafeIdentity("foo");
System.out.println(c.getClass().getName());
// but Prints 'java.lang.String'
}
}
它会在编译后擦除泛型。在编译时,Erasure.unsafeIdentity 没有错误。 jvm 擦除泛型取决于您提供的泛型参数(整数)。之后的功能是这样的?:
public static Integer unsafeIdentity(Object x) {
return x;
}
其实协变returns会加上Bridge Methods:
public static Object unsafeIdentity(Object x) {
return x;
}
如果函数和上一个一样,你认为你的main方法中的代码会编译失败吗?它没有 errors.Generics Erasure 不会在这个函数中添加强制转换,并且 return 参数不是 java 函数的标识。
我的解释有点牵强,但希望能帮助你理解。
编辑:
在 google 关于该主题之后,我猜你的问题是使用桥接方法的协变 return 类型。 BridgeMethods
如果你在规范中找不到它,那就意味着它没有被指定,并且由编译器实现来决定在哪里插入强制转换,只要擦除的代码符合类型安全规则non-generic 个代码。
在这种情况下,编译器擦除的代码如下所示:
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
那个具体化类型,所以这里需要进行运行时检查。
在第二种情况下,擦除的代码无需转换即可编译。是的,如果您添加了演员表,它也会编译。所以编译器可以决定任何一种方式。在这种情况下,编译器决定不插入强制转换。这是一个完全正确的选择。您不应该依赖编译器来决定任何一种方式。