Why is generic of a return type erased when there is an unchecked conversion of a method parameter in Java 8?

后端 未结 4 1835
野趣味
野趣味 2020-12-05 13:22

Consider the following code sample:

import java.util.ArrayList;
import java.util.List;

public class Main {
    public static void main(String[] args) {
             


        
相关标签:
4条回答
  • 2020-12-05 13:40

    I guess the reasoning goes like this, focusing on "migration" compatibility -

    If the invocation supplies a raw-type argument to a method parameter of a generic-type, it is highly likely that the invocation code is the pre-generic code, and the method declaration, which used to pre-generic too, has since been "evolved" to use generic types.

    To maintain perfect compatibility, the fool-proof solution is to erasure the method type, so that it's just like the pre-generic version. This way, the meaning of the invocation stays exactly the same.

    A more sophisticated solution might be too complicated for JLS, for example - how do we do type inference if there are raw-type arguments.


    Today, that assumption may no longer hold- the invocation is more likely a post-generic code, which, for various reasons, still uses raw type nevertheless. It's better to "correct" the raw type early on

    List list0 = ...; // got it from somewhere, possibly from an old API
    
    @SuppressWarnings("unchecked")
    List<Integer> list = list0; 
    
    0 讨论(0)
  • 2020-12-05 13:49

    This looks like a known compatibility issue reported here and here.

    From the second link:

    The following code which compiled, with warnings, in JDK 7 will not compile in JDK 8:

    import java.util.List;
    class SampleClass {
    
         static class Baz<T> {
             public static List<Baz<Object>> sampleMethod(Baz<Object> param) {
                 return null;
             }
         }
    
         private static void bar(Baz arg) {
             Baz element = Baz.sampleMethod(arg).get(0);
         }
    }
    

    Compiling this code in JDK 8 produces the following error:

    SampleClass.java:12: error:incompatible types: Object cannot be converted to Baz

    Baz element = Baz.sampleMethod(arg).get(0);
    

    Note: SampleClass.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. 1 error

    Deriving from this, the OP's code can be fixed by replacing this line (the type declartion on the right hand side threw me off - I read it as a typed array list which it is not):

    List list = new ArrayList<Integer>();
    

    with

    List<Integer> list = new ArrayList<Integer>();
    

    which will not result in type being being erased from return type of method getProducer(List<Integer> list)

    Quote from second link again:

    In this example, a raw type is being passed to the sampleMethod(Baz<Object>) method which is applicable by subtyping (see the JLS, Java SE 7 Edition, section 15.12.2.2). An unchecked conversion is necessary for the method to be applicable, so its return type is erased (see the JLS, Java SE 7 Edition, section 15.12.2.6). In this case the return type of sampleMethod(Baz<Object>) is java.util.List instead of java.util.List<Baz<Object>> and thus the return type of get(int) is Object, which is not assignment-compatible with Baz.

    0 讨论(0)
  • 2020-12-05 14:01

    The real question is; why can you use raw type? For backward compatibility. If it's for backward compatibility, the assumption is only raw types should be used.

    How generic type of passed argument could affect generic type of method return value while generic type of return value is fixed in method signature?

    A method or constructor has either of two modes. It is either completely generic, or it is completely raw typed for backward compatibility. There is no mode mode of use where it is partly raw typed and partly generic. The only reason raw types are an option is for backward compatibility and in this situation it assumes all types are/were raw types.

    Why there is such backward incompatible change in behavior between Java7 and Java8?

    Since Java 1.4 is not supported any more and hasn't been for a while, the backward compatibility argument doesn't hold as strongly and make some sense to give raw types a place in the current language.

    0 讨论(0)
  • 2020-12-05 14:04

    Let’s look into the Java Language Specification:

    Java 8

    15.12.2.6. Method Invocation Type

    • If the chosen method is not generic, then:

      • If unchecked conversion was necessary for the method to be applicable, the parameter types of the invocation type are the parameter types of the method's type, and the return type and thrown types are given by the erasures of the return type and thrown types of the method's type.

    Java 7

    15.12.2.6. Method Result and Throws Types

    The result type of the chosen method is determined as follows:

    • If the chosen method is declared with a return type of void, then the result is void.

    • Otherwise, if unchecked conversion was necessary for the method to be applicable, then the result type is the erasure (§4.6) of the method's declared return type.

    It’s important to understand, what “generic method” means:

    Reference

    8.4.4. Generic Methods

    A method is generic if it declares one or more type variables (§4.4).

    In other words, the method

    static Producer<String> getProducer(List<Integer> list)
    

    has generic parameter and return types but is not generic as it doesn’t declare type variables.

    So the cited parts apply, and despite making differences regarding the prerequisites, they agree on the consequences for this specific method invocation, “if unchecked conversion was necessary for the method to be applicable, then the result type is the erasure … of the method's declared return type”.

    So it’s the older compiler violating the specification by using the return type Producer<String> instead of the erasure Producer.

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