Consider the following code sample:
import java.util.ArrayList;
import java.util.List;
public class Main {
public static void main(String[] args) {
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;
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 ofsampleMethod(Baz<Object>)
isjava.util.List
instead ofjava.util.List<Baz<Object>>
and thus the return type ofget(int)
isObject
, which is not assignment-compatible withBaz
.
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.
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
.