Java 在类型擦除之后,函数的泛型返回值何时强制转换?

Java 在类型擦除之后,函数的泛型返回值何时强制转换?,java,generics,type-erasure,Java,Generics,Type Erasure,这个问题是由我提出的。在回答这个问题时,我遇到了这种行为,我无法完全根据规范进行解释 我在Java教程的 Oracle文档: 如有必要,插入类型强制转换以保持类型安全。 没有解释“如有必要”的确切含义,以及 我发现中根本没有提到这些演员,所以我开始尝试 让我们看一下下面的代码: // Java source public static <T> T identity(T x) { return x; } public static void main(String arg

这个问题是由我提出的。在回答这个问题时,我遇到了这种行为,我无法完全根据规范进行解释

我在Java教程的 Oracle文档:

  • 如有必要,插入类型强制转换以保持类型安全。
没有解释“如有必要”的确切含义,以及 我发现中根本没有提到这些演员,所以我开始尝试

让我们看一下下面的代码:

// 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'
}
我可以看到,在第一种情况下,有一个铸件可以确保类型安全, 但在第二种情况下,它被省略了。它是 当然可以,因为我想将返回值存储在
对象中
类型化变量,因此根据类型安全性,强制转换不是严格必需的。但是,它会导致不安全强制转换的有趣行为:

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'
    }
}
这意味着如果泛型函数应该返回给定 类型,不保证它最终会返回该类型。一 使用上述代码的应用程序将在第一次尝试时失败 要将返回值强制转换为
整数
,如果它确实这样做了,那么我想 它打破了传统


编译器在编译过程中插入此强制转换的确切规则是什么 确保类型安全的编译以及这些规则在哪里指定?

编辑:

我看到编译器不会深入研究代码并试图证明泛型代码确实返回了它应该返回的内容,但它可以插入一个assertation,或者至少插入一个类型转换(在特定情况下它已经这样做了,如第一个示例中所示),以确保正确的返回类型,因此后者将抛出一个
ClassCastException

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

版本1更可取,因为它在以下位置失败

类型安全版本1非遗留代码:

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());
   }
}
类擦除{
公共静态不安全性(T x){
//无需强制转换,编译时在参数中检查类型
返回x;
}
公共静态void main(字符串参数[]){
//这将在编译时失败,您应该在实际代码中使用整数c=
对象c=擦除。不安全性(“foo”);
System.out.println(c.getClass().getName());
}
}
类型安全版本2遗留代码(和):

类擦除{
公共静态不安全性(对象x){
返回(T)x;
//编译版本:返回(Object)x;
//优化版本:返回x;
}
公共静态void main(字符串参数[]){
//这将在返回时失败,因为返回的对象是Object类型,并且需要Integer子类型,这将导致自动强制转换和ClassCastException:
整数c=擦除。不安全性(“foo”);
//编译版本:Integer c=(Integer)Erasure.unsafeIdentity(“foo”);
System.out.println(c.getClass().getName());
}
}
TypeSafe版本3遗留代码,每次()都知道超类型的方法:

类擦除{
公共静态不安全性(对象x){
//由于类型擦除和不兼容的类型,此操作将失败:
返回(T)x;
//编译版本:返回(整数)x;
}
公共静态void main(字符串参数[]){
//您应该使用整数c=。。。
对象c=擦除。不安全性(“foo”);
System.out.println(c.getClass().getName());
}
}
对象仅用于说明该对象在版本1和版本3中是有效的赋值目标,但如果可能,应使用实类型或泛型类型


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

我不能很好地解释它,但是注释不能像我希望的那样添加代码,所以我添加了这个答案。只是希望这个答案能帮助你理解。评论不能像我希望的那样添加代码

在代码中:

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'
    }
}
事实上,协变回报将增加:

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

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

编辑:


在谷歌讨论过这个话题之后,我想你的问题是使用桥接方法的协变返回类型

如果在规范中找不到强制类型转换,这意味着没有指定强制类型转换,只要删除的代码符合非泛型代码的类型安全规则,就由编译器实现决定是否插入强制类型转换

在本例中,编译器已擦除的代码如下所示:

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)当它看到
行返回(t)x
,它无法静态地知道
x
不能转换为
t
;(2)当您实际调用
unsafeIdentity
时,编译器无法知道这将失败,因为它不会深入研究方法的代码并查找将失败的语句。基本上,我认为这意味着方法中对
(T)
的转换是无用的。感谢@ajb,当然,对(T)的转换是无用的,这确实是一个极简主义的例子。但是它可以很容易地将外部函数编译成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()); } }
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());
   }
}
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'
    }
}
public static Integer unsafeIdentity(Object x) {
    return x;
}
public static Object unsafeIdentity(Object x) {
    return x;
}
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());
}