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
Generics enable the use of stronger type-checking, the elimination of casts, and the ability to develop generic algorithms. Without generics, many of the features that we use in Java today would not be possible.
A generic drug is a medication created to be the same as an already marketed brand-name drug in dosage form, safety, strength, route of administration, quality, performance characteristics, and intended use.
By using generics, programmers can implement generic algorithms that work on collections of different types, can be customized, and are type safe and easier to read.
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.
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.
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
). 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>>
.
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>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With