While I do understand some of the corner-cases of generics, I'm missing something with the following example.
I have the following class
1 public class Test<T> {
2 public static void main(String[] args) {
3 Test<? extends Number> t = new Test<BigDecimal>();
4 List<Test<? extends Number>> l =Collections.singletonList(t);
5 }
6 }
Line 4 gives me the error
Type mismatch: cannot convert from List<Test<capture#1-of ? extends Number>>
to List<Test<? extends Number>>`.
Obviously, the compiler thinks that the different ?
are not really equal. While my gut-feeling tells me, this is correct.
Can anyone provide an example where I would get a runtime-error if line 4 was legal?
EDIT:
To avoid confusion, I replaced the =null
in Line 3 by a concrete assignment
As Kenny has noted in his comment, you can get around this with:
List<Test<? extends Number>> l =
Collections.<Test<? extends Number>>singletonList(t);
This immediately tells us that the operation isn't unsafe, it's just a victim of limited inference. If it were unsafe, the above wouldn't compile.
Since using explicit type parameters in a generic method as above is only ever necessary to act as a hint, we can surmise that it being required here is a technical limitation of the inference engine. Indeed, the Java 8 compiler is currently slated to ship with many improvements to type-inference. I'm not sure whether your specific case will be resolved.
So, what's actually happening?
Well, the compile error we're getting shows that the type parameter T
of Collections.singletonList
is being inferred to be capture<Test<? extends Number>>
. In other words, the wildcard has some metadata associated with it that links it to a specific context.
- The best way to think of a capture of a wildcard (
capture<? extends Foo>
) is as an unnamed type parameter of the same bounds (i.e. <T extends Foo>
, but without being able to reference T
).
- The best way to "unleash" the power of the capture is by binding it to a named type parameter of a generic method. I'll demonstrate this in an example below. See the Java tutorial "Wildcard Capture and Helper Methods" (thanks for the reference @WChargin) for further reading.
Say we want to have a method that shifts a list, wrapping to the back. Then let's assume that our list has an unknown (wildcard) type.
public static void main(String... args) {
List<? extends String> list = new ArrayList<>(Arrays.asList("a", "b", "c"));
List<? extends String> cycledTwice = cycle(cycle(list));
}
public static <T> List<T> cycle(List<T> list) {
list.add(list.remove(0));
return list;
}
This works fine, because T
is resolved to capture<? extends String>
, not ? extends String
. If we instead used this non-generic implementation of cycle:
public static List<? extends String> cycle(List<? extends String> list) {
list.add(list.remove(0));
return list;
}
It would fail to compile, because we haven't made the capture accessible by assigning it to a type parameter.
So this begins to explain why the consumer of singletonList
would benefit from the type-inferer resolving T
to Test<capture<? extends Number>
, and thus returning a List<Test<capture<? extends Number>>>
instead of a List<Test<? extends Number>>
.
But why isn't one assignable to the other?
Why can't we just assign a List<Test<capture<? extends Number>>>
to a List<Test<? extends Number>>
?
Well if we think about the fact that capture<? extends Number>
is the equivalent of an anonymous type parameter with an upper bound of Number
, then we can turn this question into "Why doesn't the following compile?" (it doesn't!):
public static <T extends Number> List<Test<? extends Number>> assign(List<Test<T>> t) {
return t;
}
This has a good reason for not compiling. If it did, then this would be possible:
//all this would be valid
List<Test<Double>> doubleTests = null;
List<Test<? extends Number>> numberTests = assign(doubleTests);
Test<Integer> integerTest = null;
numberTests.add(integerTest); //type error, now doubleTests contains a Test<Integer>
So why does being explicit work?
Let's loop back to the beginning. If the above is unsafe, then how come this is allowed:
List<Test<? extends Number>> l =
Collections.<Test<? extends Number>>singletonList(t);
For this to work, it implies that the following is allowed:
Test<capture<? extends Number>> capturedT;
Test<? extends Number> t = capturedT;
Well, this isn't valid syntax, as we can't reference the capture explicitly, so let's evaluate it using the same technique as above! Let's bind the capture to a different variant of "assign":
public static <T extends Number> Test<? extends Number> assign(Test<T> t) {
return t;
}
This compiles successfully. And it's not hard to see why it should be safe. It's the very use case of something like
List<? extends Number> l = new List<Double>();
There is no potential runtime error, it's just outside the compiler's ability to statically determine that. Whenever you cause a type inference it automatically generates a new capture of <? extends Number>
, and two captures are not considered equivalent.
Hence if you remove the inference from the invocation of singletonList by specifying <T>
for it:
List<Test<? extends Number>> l = Collections.<Test<? extends Number>>singletonList(t);
It works fine. The generated code is no different than if your call had been legal, it's just a limitation of the compiler that it can't figure that out on its own.
The rule that an inference creates a capture and captures aren't compatible is what stops this tutorial example from compiling and then blowing up at runtime:
public static void swap(List<? extends Number> l1, List<? extends Number> l2) {
Number num = l1.get(0);
l1.add(0, l2.get(0));
l2.add(0, num);
}
Yes the language specification and compiler probably could be made more sophisticated to tell your example apart from that, but it's not and it's simple enough to work around.
Maybe this can explain the problem of the compiler:
List<? extends Number> myNums = new ArrayList<Integer>();
This genric wildcard list can hold any elements extending from Number. So its OK to assign an Integer list to it. However now I could add a Double to myNums because Double is also extending from Number which would lead to a runtime problem. So the compiler forbids every write access to myNums and I can only use read methods on it, because I only know what ever I get can be cast to Number.
And so the compiler is complaining about a lot of things you can do with such a wildcard generic. Sometimes he is mad about things which you can ensure they are safe and OK.
But luckily there is a trick to get around this error so you can test on your own what can maybe break this:
public static void main(String[] args) {
List<? extends Number> list1 = new ArrayList<BigDecimal>();
List<List<? extends Number>> list2 = copyHelper(list1);
}
private static <T> List<List<T>> copyHelper(List<T> list) {
return Collections.singletonList(list);
}
The reason is that the compiler doesn't know that your wildcard types are the same type.
It also doesn't know that your instance is null
. Although null
is a member of all types, the compiler considers only declared types, not what the value of the variable might contain, when type checking.
If the code executed, it wouldn't cause an exception, but that's only because the value is null. There is still a potential type mismatch, and that's what the compiler's job is - to disallow type mismatches.
Take a look at type erasure. The problem is "compile time" is the only opportunity that Java has to enforce these generics, so if it let this through it wouldn't be able to tell if you tried to insert something invalid. This is actually a good thing, because it means that once the program compiles then Generics don't incur any performance penalty at run time.
Let's try and look at your example another way (let's use two types that extend Number but behave very differently). Consider the following program:
import java.math.BigDecimal;
import java.util.*;
public class q16449799<T extends Number> {
public T val;
public static void main(String ... args) {
q16449799<BigDecimal> t = new q16449799<>();
t.val = new BigDecimal(Math.PI);
List<q16449799<BigDecimal>> l = Collections.singletonList(t);
for(q16449799<BigDecimal> i : l) {
System.out.println(i.val);
}
}
}
This outputs (as one would expect):
3.141592653589793115997963468544185161590576171875
Now assuming the code you presented didn't cause a compiler error:
import java.math.BigDecimal;
import java.util.concurrent.atomic.AtomicLong;
public class q16449799<T extends Number> {
public T val;
public static void main(String ... args) {
q16449799<BigDecimal> t = new q16449799<>();
t.val = new BigDecimal(Math.PI);
List<q16449799<AtomicLong>> l = Collections.singletonList(t);
for(q16449799<AtomicLong> i : l) {
System.out.println(i.val);
}
}
}
What would you expect the output to be? You can't reasonably cast a BigDecimal to an AtomicLong (you could construct an AtomicLong from the value of a BigDecimal, but casting and constructing are different things and Generics are implemented as compile time sugar to make sure casts are successful). As for @KennyTM's comment, a concrete type is seeking in when you initial example but try and compile this:
import java.math.BigDecimal;
import java.util.*;
public class q16449799<T> {
public T val;
public static void main(String ... args) {
q16449799<? extends Number> t = new q16449799<BigDecimal>();
t.val = new BigDecimal(Math.PI);
List<q16449799<? extends Number>> l = Collections.<q16449799<? extends Number>>singletonList(t);
for(q16449799<? extends Number> i : l) {
System.out.println(i.val);
}
}
}
This will error out the moment you try and set a value to t.val
.