简体   繁体   English

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

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

This question was inducted by this StackOverflow question about unsafe casts: Java Casting method without knowing what to cast to . 这个问题是由StackOverflow关于不安全转换的问题引起的:Java Casting方法,不知道要转换为什么 While answering the question I encountered this behaviour I couldn't explain based on purely the specification 在回答我遇到此问题的问题时,我无法根据纯粹的规范进行解释

I found the following statement in The Java Tutorials at the Oracle docs: 我在Oracle文档的Java教程中找到了以下语句:

It is not explained what "if necessary" means exactly, and I've found no mention about these casts in the Java Language Specification at all, so I started to experiment. 没有解释“如果有必要”究竟意味着什么,而且我根本没有Java语言规范中找到这些演员 ,所以我开始尝试。

Let's look at the following piece of code: 让我们看看下面这段代码:

// 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'
}

Compiled with javac and decompiled with the Java Decompiler : 使用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());
}

I can see that there is a cast which ensures type safety in the first case, but in the second case it is omitted. 我可以看到在第一种情况下有一个铸件确保了类型安全,但在第二种情况下它被省略了。 It is fine of course, because I want to store the return value in an Object typed variable, so the cast is not strictly necessary as per type safety. 当然很好,因为我想将返回值存储在Object类型变量中,因此根据类型安全性,不一定要强制转换。 However it leads to an interesting behaviour with unsafe casts: 然而,它会导致一个有趣的行为与不安全的演员表:

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'
    }
}

Compiled and decompiled, I see no type cast to ensure correct return type at runtime: 编译和反编译,我看到没有类型转换,以确保在运行时正确的返回类型:

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

This means that if a generic function should return an object of a given type, it is not guaranteed it will return that type ultimately. 这意味着如果泛型函数返回给定类型的对象,则无法保证它最终返回该类型。 An application using the above code will fail at the first point where it tries to cast the return value to an Integer if it does so at all, so I feel like it breaks the fail-fast principle . 使用上面代码的应用程序将在尝试将返回值转换为Integer的第一点失败,如果它完全这样做的话,那么我觉得它打破了fail-fast原则

What are the exact rules of the compiler inserting this cast during compilation that ensures type safety and where are those rules specified? 编译期间编译器插入此强制转换的确切规则是什么,以确保类型安全以及指定的规则在哪里?

EDIT: 编辑:

I see that the compiler will not dig into the code and try to prove that the generic code really returns what it should, but it could insert an assertation, or at least a type cast (which it already does in specific cases, as seen in the first example) to ensure correct return type, so the latter would throw a ClassCastException : 我看到编译器不会深入研究代码并试图证明通用代码确实返回它应该的东西,但是它可以插入一个断言,或者至少是一个类型转换(它在特定情况下已经做过,如下所示)第一个例子)确保正确的返回类型,所以后者会抛出ClassCastException

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

If you can't find it in the specification, that means it's not specified, and it is up to the compiler implementation to decide where to insert casts or not, as long as the erased code meets the type safety rules of non-generic code. 如果在规范中找不到它,那意味着它没有被指定,只要擦除的代码符合非泛型代码的类型安全规则,由编译器实现来决定是否插入强制转换的位置。 。

In this case, the compiler's erased code looks like this: 在这种情况下,编译器的擦除代码如下所示:

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

In the first case, the cast is necessary in the erased code, because if you removed it, the erased code wouldn't compile. 在第一种情况下,在删除的代码中必须使用强制转换,因为如果删除它,则删除的代码将无法编译。 This is because Java guarantees that what is held at runtime in a reference variable of reifiable type must be instanceOf that reifiable type, so a runtime check is necessary here. 这是因为Java保证在可执行类型的引用变量中运行时保存的内容必须是可重新生成类型的instanceOf ,因此这里需要运行时检查。

In the second case, the erased code compiles without a cast. 在第二种情况下,擦除的代码在没有强制转换的情况下编译。 Yes, it will also compile if you added a cast. 是的,如果您添加了演员表,它也会编译。 So the compiler can decide either way. 所以编译器可以决定哪种方式。 In this case, the compiler decided not to insert a cast. 在这种情况下,编译器决定不插入强制转换。 That is a perfectly valid choice. 这是一个完全有效的选择。 You should not rely on the compiler to decide either way. 您不应该依赖编译器来决定任何一种方式。

Version 1 is preferable because it fails at compiletime . 版本1更可取,因为它在编译时失败。

Typesafe version 1 non-legacy code: 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 version 2 legacy code ( 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 ): 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 version 3 legacy code, Methods where you know a supertype everytime ( JLS The erasure of a type variable (§4.4) is the erasure of its leftmost bound. ): 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 was only used to illustrate that Object is a valid assignment target in version 1 and 3, but you should use the real type or the generic type if possible. Object仅用于说明Object在版本1和版本3中是有效的分配目标,但如果可能,您应该使用实际类型或泛型类型。

If you use another version of java you should look at the particular pages of the specification, I don't expect any changes. 如果你使用另一个版本的java,你应该查看规范的特定页面,我不期望任何更改。

I can't explain it very well, but the comment can't add code as well as I want,so I add this answer. 我无法解释它,但评论不能添加我想要的代码,所以我添加这个答案。 Just hope this answer can help your understanding.The comment can't add code as well as I want. 希望这个答案可以帮助你理解。评论不能添加我想要的代码。

In your code: 在你的代码中:

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'
    }
}

It will erasure Generics after compile time. 它将在编译后擦除泛型。 At compile time, the Erasure.unsafeIdentity has not errors. 在编译时,Erasure.unsafeIdentity没有错误。 The jvm erasure Generics depend on the Generics params you give(Integer). jvm擦除泛型依赖于你给出的泛型参数(整数)。 After that, the function is like this?: 之后,功能是这样的吗?:

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

In fact, the covariant returns will add Bridge Methods : 事实上,协变返回将添加桥接方法

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

If the function is like last one, do you think the code in your main method will compile fail? 如果该函数与上一个函数类似,您认为main方法中的代码编译失败吗? It has no errors.Generics Erasure will not add cast in this function, and the return params is not the indentity of java function. 它没有错误。泛型Erasure不会在这个函数中添加强制转换,而返回参数不是java函数的缩进。

My explanation is a bit farfetched, but hope can help you to understand. 我的解释有点牵强,但希望可以帮助你理解。

Edit: 编辑:

After google about that topic, I guess your problems is covariant return types using bridge methods. 谷歌关于该主题后,我猜你的问题是使用桥接方法的协变返回类型。 BridgeMethods BridgeMethods

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

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