public class Main {
List<List<Integer>> f0() {
return List.of(List.of(1L));
}
List<List<Integer>> f1() {
return List.of((List) List.of(1L));
}
List<List<Integer>> f2() {
var r = List.of((List) List.of(1L));
return r;
}
List<List<Integer>> f3() {
return List.of((List) List.of(1L), List.of(1L));
}
List<List<Integer>> f4() {
return List.of((List) List.of(1L), (List) List.of(1L));
}
}
In the code above, f1 and f4 compile while f0, f2 and f3 do not.
I can see why f0 doesn't compile: It needs List<List<Integer>> but it sees List<List<Long>>. But I have no idea why others behave the way they do.
f2:
incompatible types: List<List> cannot be converted to List<List<Integer>>
f3:
error: incompatible types: inference variable E has incompatible bounds
return List.of((List) List.of(1L), List.of(1L));
^
equality constraints: Integer
lower bounds: Long
where E is a type-variable:
E extends Object declared in method <E>of(E)
In each function, what exactly is the type of the expression being returned? Why does Java behave like this? And what is happening according to the JLS? I'm using Java 17 if it's important.
The difference between
f2andf0is thatList.ofis not in an assignment context inf2, §14.4.1:Hence, the call is not a poly expression. It doesn't satisfy the requirements for being a poly expression in §15.12:
This means that type inference no longer takes into account the target type (§15.12.2.6).
The entirety of §18.5.2.1, which would have added some constraint about
Integer, is skipped.If you put
List.ofin a return statement, however, it is in an assignment context (§14.17):(§5.2 is the section for assignment contexts)
More informally, Java doesn't look at all the places where
ris used to infer the type parameter ofList.of. It only looks at the callList.of((List) List.of(1L)). It is assigned to avar, so type inference results inList<List>. Type inference has no reason to randomly add a<Integer>in there.List<List>is not compatible withList<List<Integer>>, because generics are by default invariant. For how exactly this is specified, see §4.10. Try showing thatList<List>is not a subtype ofList<List<Integer>>.For
f3, there are conflicting constraints. I'll useEto refer to the type parameter of the outerList.ofcall, andE1andE2to refer to the type parameters of the first and second innerList.ofcall.Because the outer
List.ofis in a return statement, §18.5.2.1 applies, andEgets a lower bound ofList<Integer>.E1is inferred to have a lower bound ofLong, then §18.5.2.1 applies, and the target type is rawList, the type you are casting to. Nothing is wrong with that and we carry on.The raw
Listin the first argument gives an upper bound ofListforE. That's fine - no conflicts here.In
f4, the same thing happens with the second argument andE2, so there is no problem.In
f3, §18.5.2.1 applies when inferringE2, the target type isList<Integer>, because of the constraint onE. This adds an equality constraint toE2. The argument1Lalso adds a lower bound ofLongtoE2.That's where the conflict is. There is no type that satisfies both
== Integerand>= Long.