Constructor reference - no warning when generics array is created
In Java, it's not possible to create an array of generic type directly:
Test<String>[] t1 = new Test<String>[10]; // Compile-time error
However, we can do this using raw type:
Test<String>[] t2 = new Test[10]; // Compile warning "unchecked"
In Java 8, it's also possible to use a constructor reference:
interface ArrayCreator<T> {
T create(int n);
}
ArrayCreator<Test<String>[]> ac = Test[]::new; // No warning
Test<String>[] t3 = ac.create(10);
Why doesn't the compiler display the warning in the last case? It still uses raw type to create the array, right?
Your question is justified. In short, the method reference does indeed use the raw type (or should use the raw type) and the reason, why the creation of generic arrays is forbidden, still applies when using method references, hence, being able to silently create a function creating a generic array clearly violates the intention of the language design.
The reason why the creation of generic array is forbidden, is that the array type inheritance, stemming from a pre-Generics era, is incompatible with the generic type system. I.e. you can write:
IntFunction<List<String>[]> af = List[]::new; // should generate warning
List<String>[] array = af.apply(10);
Object[] objArray = array;
objArray[0] = Arrays.asList(42);
List<String> list = array[0]; // heap pollution
At this place, it must be emphasized that contrary to some answers here, the compiler does not perform type inference on the expression List[]::new
to deduce the generic element type List<String>
. It’s easy to prove that generic array creation still is forbidden:
IntFunction<List<String>[]> af = List<String>[]::new; // does not compile
Since List<String>[]::new
is illegal, it would be strange if List[]::new
was accepted without a warning, by inferring it to be effectively the illegal List<String>[]::new
.
JLS §15.13 clearly states:
If a method reference expression has the form ArrayType
::
new
, then ArrayType must denote a type that is reifiable (§4.7), or a compile-time error occurs.
This already implies that List<String>[]::new
is illegal, because List<String>
is not reifiable, whereas List<?>[]::new
is legal, as List<?>
is reifiable, and List[]::new
is legal if we consider List
to be a raw type, as the raw type List
is reifiable.
Then §15.13.1 states:
If the method reference expression has the form ArrayType
::
new
, a single notional method is considered. The method has a single parameter of typeint
, returns the ArrayType, and has nothrows
clause. If n = 1, this is the only potentially applicable method; otherwise, there are no potentially applicable methods.
In other words, the behavior of the List[]::new
expression above is the same as if you had written:
IntFunction<List<String>[]> af = MyClass::create;
…
private static List[] create(int i) {
return new List[i];
}
except that the method create
is only notional. And indeed, with this explicit method declaration, there are only raw type warnings at the create
method, but no unchecked warnings regarding the conversion of List[]
to List<String>[]
at the method reference. So it’s understandable, what happens in the compiler in the List[]::new
case, where the method using raw types is only notional, i.e. doesn’t exist in source code.
But the absence of unchecked warnings is a clear violation of JLS §5.1.9, Unchecked Conversion:
Let
G
name a generic type declaration with n type parameters.There is an unchecked conversion from the raw class or interface type (§4.8)
G
to any parameterized type of the formG<T₁,...,Tₙ>
.There is an unchecked conversion from the raw array type
G[]ᵏ
to any array type of the formG<T₁,...,Tₙ>[]ᵏ
. (The notation[]ᵏ
indicates an array type of k dimensions.)Use of an unchecked conversion causes a compile-time unchecked warning unless all type arguments
T
ᵢ (1 ≤ i ≤ n) are unbounded wildcards (§4.5.1), or the unchecked warning is suppressed by theSuppressWarnings
annotation (§9.6.4.5).
So, a conversion of List[]
to List<?>[]
is legal, as List
is parameterized with an unbounded wildcard, but the conversion from List[]
to List<String>[]
must produce an unchecked warning, which is crucial here, as the use of List[]::new
does not produce the raw type warning that appears with an explicit creation method. The absence of raw type warnings seems not to be a violation (as far as I understood §4.8) and it wouldn’t be a problem, if javac
created the required unchecked warning.
The best that I can come up with is that the JLS specifies that a method reference to the constructor of a generic type infers the generic parameters:
"If a method or constructor is generic, the appropriate type arguments may either be inferred or provided explicitly." Later it gives ArrayList::new
as an example and describes it as "inferred type arguments for generic class," thus establishing that ArrayList::new
(and not ArrayList<>::new
) is the syntax that infers arguments.
Given a class:
public static class Test<T> {
public Test() {}
}
this gives a warning:
Test<String> = new Test(); // No <String>
but this doesn't:
Supplier<Test<String>> = Test::new; // No <String> but no warning
because Test::new
implicitly infers the generic arguments.
So I assume that a method reference to an array constructor works the same way.
It still uses raw type to create the array, right?
Java generics are just a compile-time illusion, so the raw type will of course be used at runtime to create the array.
Why doesn't the compiler display the warning in the last case?
Yes, the unchecked cast from Test[]
to Test<String>[]
is still happening; it's just happening behind the scenes in an anonymous context.
Test<String>[] t3 = ((IntFunction<Test<String>[]>) Test[]::new).apply(10);
Since the anonymous method is doing the dirty work, the unchecked cast effectively disappears from the managed code.