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

前端 未结 3 2030
孤街浪徒
孤街浪徒 2020-12-05 19:33

This question was inducted by this StackOverflow question about unsafe casts: Java Casting method without knowing what to cast to. While answering the question I encount

相关标签:
3条回答
  • 2020-12-05 20:23

    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. The jvm erasure Generics depend on the Generics params you give(Integer). 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? It has no errors.Generics Erasure will not add cast in this function, and the return params is not the indentity of java function.

    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

    0 讨论(0)
  • 2020-12-05 20:31

    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.

    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.

    0 讨论(0)
  • 2020-12-05 20:36

    Version 1 is preferable because it fails at compiletime.

    Typesafe version 1 non-legacy code:

    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):

    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.):

    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.

    If you use another version of java you should look at the particular pages of the specification, I don't expect any changes.

    0 讨论(0)
提交回复
热议问题