The following compiles just fine in JDK8, but gives an incompatible types error with JDK7.
List<List<? extends Number>> xs = Arrays.asList(Arrays.asList(0));
According to this answer, List<List<? extends Number>>
doesn't have a supertype relationship to List<List<Integer>>
.
What changed in Java 8 that made this assignment work? I'm also having a hard time understanding why it doesn't work in Java 7.
Both of these statements compile without type error using JDK7:
List<? extends Number> xs = Arrays.asList(0);
List<? extends List<? extends Number>> ys = Arrays.asList(Arrays.asList(0));
It seems very unintuitive to me that both of those work in JDK7, but the original example above does not. All of them of course will work in JDK8. I think to really understand what's going on here I'd need to understand why these examples are legal in Java 7 but the original example is not.
It is quite simple:
In Java 7, the context of a method call was not considered when inferring type arguments. The only thing that was considered where the arguments of a method call.
In your case,
int
is boxed toInteger
which yieldsList<Integer>
as type for the inner call and thenList<List<Integer>>
as type for the outer call. Now you have a problem, since you want to assign the result to a variable of typeList<List<? extends Number>>
and this is just not possible since generics are invariant as long as there are no wildcards used, i.e.,List<X>
can never be converted toList<Y>
. In your caseX
isList<Integer>
andY
isList<? extends Number>
. Even ifList<? extends Number>
contains a wildcard, it does not use a wildcard itself, i.e., it is not? extends List<? extends Number>
. This is why it does not compile in Java 7.I know understanding generics and variance and wildcards is not that easy. Maybe I can clarify it like this for you:
A<Y>
is never considered a subtype of anyA<Z>
.Z
inherits fromY
, thenA<Z>
is a subtype ofA<? extends Y>
.A<Z>
andA<? extends Y>
are related in a subtype relation. But if we wrap them in another generic type, likeB<A<Z>>
andB<A<? extends Y>>
then rule 1. applies: Since there are no wildcards, the secondB
is not considered a subtype of the first one. If we again introduce a wildcard, then they are:B<A<Z>>
is indeed a subtype ofB<? extends A<? extends Y>>
. But now note that the outher? extends
is missing in your example, so it does not compile in Java 7.Now to Java 8. Java 8 also takes the context of a call into account when inferring type arguments. Thus, Java 8 considers that you want to pass the result of the
Arrays.asList
calls to a variable of typeList<List<? extends Number>>
. It therefore tries to find type arguments that will make this assignment legal. It then infers that the type argument for the inner call must beNumber
, cause otherwise the assignment would not be legal.To cut it short: Java 8 is just a lot more clever than Java 7 when choosing type arguments, since it also looks at the context, not only the arguments.
I believe this has to do with invocation contexts and widening reference conversion.
Basically, in that invocation context, the type of the argument
0
inArrays.asList(0)
can be boxed toInteger
and then widened toNumber
. When that happens,Arrays.asList(0)
has a return type ofList<Number>
. Through the same process, thatList<Number>
can be converted toList<? extends Number>
before being used as an argument to the outerArrays.asList(..)
.This is equivalent to using explicit type arguments in Java 7
In Java 7, the type of an expression is the same regardless of where it is used, whether it's a standalone expression or used in assignment expression.
Java 8 introduced poly-expressions in which the type of the expression could be influenced by the target type of the expression.
For example, in the following assignment expression
The type of the expression
Arrays.asList(1)
is the return type of the method being invoked which depends entirely on the generic type parameter. In this case, that type argument will be inferred asInteger
because the value1
can be converted toInteger
through boxing conversion (primitives can't be used with generics). So the type of the expression isList<Integer>
.In Java 7, this assignment expression would not compile because
List<Integer>
cannot be assigned toList<Number>
. This could be fixed by providing an explicit type argument when invokingasList
in which case, the method invocation expects a
Number
argument for its first parameter and the value1
satisfies that.In Java 8, the assignment expression is a poly expression
Being a poly expression, it can be influenced by the type of the variable it is being assigned to. And that's what happens. The generic type
Number
influences the type argument inferred in the invocation ofArrays.asList(1)
.Note how it wouldn't work in the following example
So it's not covariance, but we get some of its benefits in some places.