Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 362
Vladimir M. Avatar asked Feb 20 '17 14:02

Vladimir M.


3 Answers

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 type int, returns the ArrayType, and has no throws 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 form G<T₁,...,Tₙ>.

There is an unchecked conversion from the raw array type G[]ᵏ to any array type of the form G<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 ≤ in) are unbounded wildcards (§4.5.1), or the unchecked warning is suppressed by the SuppressWarnings 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.

like image 97
Holger Avatar answered Oct 17 '22 13:10

Holger


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.

like image 31
Willis Blackburn Avatar answered Oct 17 '22 14:10

Willis Blackburn


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.

like image 5
Patrick Parker Avatar answered Oct 17 '22 15:10

Patrick Parker