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>();