The crux of the question is, why does this cause a compile-time error?
List<Collection> raws = new ArrayList<Collection>();
List<Collection<?>> c = raws; // error
I understand why generics aren't covariant in general. If we could assign List<Integer>
to List<Number>
, we'd expose ourselves to ClassCastExceptions:
List<Integer> ints = new ArrayList<Integer>();
List<Number> nums = ints; // compile-time error
nums.add(Double.valueOf(1.2));
Integer i = ints.get(0); // ClassCastException
We get a compile-time error at line 2 to save us from a run-time error at line 4. That makes sense.
List<C>
to List<C<?>>
But how about this:
List<Collection> rawLists = new ArrayList<Collection>();
List<Collection<?>> wildLists = rawLists; // compile-time error
// scenario 1: add to raw and get from wild
rawLists.add(new ArrayList<Integer>());
Collection<?> c1 = wildLists.get(0);
Object o1 = c1.iterator().next();
// scenario 2: add to wild and get from raw
wildLists.add(new ArrayList<String>());
Collection c2 = rawLists.get(0);
Object o2 = c2.iterator().next();
In both scenarios, ultimately I get only get Object
elements without casting, so I can't get a "mysterious" ClassCastException.
The section in the JLS that corresponds to this is §4.10.2, so I understand why the compiler is giving me the error; what I don't get is why the spec was written this way, and (to ward off speculative/opinion-based answers), whether it actually provides me any compile-time safety.
In case you're wondering, here's (a stripped-down version of) the use case:
public Collection<T> readJsons(List<String> jsons, Class<T> clazz) {
List<T> list = new ArrayList<T>();
for (String json : jsons) {
T elem = jsonMapper.readAs(json, clazz);
list.add(elem);
}
return list;
}
// call site
List<GenericFoo<?>> foos = readJsons(GenericFoo.class); // error
The error is because GenericFoo.class
has type Class<GenericFoo>
, not Class<GenericFoo<?>>
(§15.8.2). I'm not sure why that is, though I suspect it's a related reason; but regardless, that wouldn't be a problem if Class<GenericFoo>
could be casted — either implicitly or explicitly — to Class<GenericFoo<?>>
.
First of all, raw type and wildcard type are quite different. For one, raw type completely erases all generic information.
So we have List<x>
and List<y>
where x is not y. This is certainly not subtype relationship.
You can, nevertheless, ask the casting to be allowed. But please read JLS 5.5.1 , and tell me you want to add something more to it:) Browse the whole page, actually, it's a great wall of text just for casting.
And remember this is just the first ripple in the whole effect. What about List<List<x>>
and List<List<y>>
, etc.
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