繁体   English   中英

何时是类型擦除后输入的函数的泛型返回值?

[英]When is generic return value of function casted after type erasure?

这个问题是由StackOverflow关于不安全转换的问题引起的:Java Casting方法,不知道要转换为什么 在回答我遇到此问题的问题时,我无法根据纯粹的规范进行解释

我在Oracle文档的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'
}

使用javac编译并使用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原则

编译期间编译器插入此强制转换的确切规则是什么,以确保类型安全以及指定的规则在哪里?

编辑:

我看到编译器不会深入研究代码并试图证明通用代码确实返回它应该的东西,但是它可以插入一个断言,或者至少是一个类型转换(它在特定情况下已经做过,如下所示)第一个例子)确保正确的返回类型,所以后者会抛出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 ,因此这里需要运行时检查。

在第二种情况下,擦除的代码在没有强制转换的情况下编译。 是的,如果您添加了演员表,它也会编译。 所以编译器可以决定哪种方式。 在这种情况下,编译器决定不插入强制转换。 这是一个完全有效的选择。 您不应该依赖编译器来决定任何一种方式。

版本1更可取,因为它在编译时失败。

Typesafe版本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());
   }
}

Typesafe版本2遗留代码( 运行时类型错误[...]在引入的自动生成的转换中,以确保对不可再生类型引用类型转换 的操作的有效性 ):

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类型变量的擦除(第4.4节)是其最左边界的擦除。 ):

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());
   }
}

Object仅用于说明Object在版本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'
    }
}

它将在编译后擦除泛型。 在编译时,Erasure.unsafeIdentity没有错误。 jvm擦除泛型依赖于你给出的泛型参数(整数)。 之后,功能是这样的吗?:

public static Integer unsafeIdentity(Object x) {
    return x;
}

事实上,协变返回将添加桥接方法

public static Object unsafeIdentity(Object x) {
    return x;
}

如果该函数与上一个函数类似,您认为main方法中的代码编译失败吗? 它没有错误。泛型Erasure不会在这个函数中添加强制转换,而返回参数不是java函数的缩进。

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

编辑:

谷歌关于该主题后,我猜你的问题是使用桥接方法的协变返回类型。 BridgeMethods

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM