16

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

Background

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.

Motivating example

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<?>>.

yshavit
  • 42,327
  • 7
  • 87
  • 124
  • 2
    Related: http://stackoverflow.com/questions/26766704/cannot-convert-from-listlist-to-listlist/ http://stackoverflow.com/questions/30090242/java-lang-class-generics-and-wildcards (But I don't think you're asking about spec here, right? Anyway, I don't think there is a practical reason the conversion is not allowed, personally.) – Radiodef May 21 '15 at 22:57
  • Thanks for those links @Radiodef. But yeah, I'm not asking about the spec so much as the theory, as it were -- whether the current state of affairs actually buys us some protections, or whether it's just an edge case that the type system happens not to cover. – yshavit May 21 '15 at 23:11
  • 1
    Really your question boils down to "why doesn't `Collection>` contain `Collection`?" (in the JLS sense of containment). We'd need to dig through discussions of the JLS authors to get the real answer as to their intentions. However I'll speculate on an answer: because the current containment rules are easier to implement without special exceptions like this. In other words there's no reason the convention couldn't be changed it would just be a special exception in a language spec that tries to avoid special exceptions. But that's just a guess. – sprinter May 21 '15 at 23:38
  • @sprinter Well, and as I pointed out a little bit in the latter of my links above, it plays a funny game with the type system. `Class` is a supertype of `Class>`, but `List` would be a subtype of `List>`. – Radiodef May 21 '15 at 23:40
  • @Radiodef yes agreed. The JLS does a pretty good job in most cases of avoiding these types of funny games. In this case the spec could be changed (with corresponding effort for implements but without a great deal of danger to coders) but given there's other solutions to all the problems it might solve, why increase complexity for little gain? – sprinter May 21 '15 at 23:43

1 Answers1

2

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.

yshavit
  • 42,327
  • 7
  • 87
  • 124
ZhongYu
  • 19,446
  • 5
  • 33
  • 61
  • Not sure that I agree that complicating the specification is a problem. Look at chapter 18. That's all brand-new. But the ripple effect is real. Of course this could all be fixed (and the spec *simplified*...) by removing raw types altogether. – Radiodef May 22 '15 at 00:10
  • @Radiodef - not a problem? easy for you to say:) chapter 18 is a modern marvel, it's amazing that it's done, and mostly by one person. There were probably only 3 guys and 1 girl who understood it; and I'm sure they no longer do after they finished coding the compilers, LOL. – ZhongYu May 22 '15 at 00:13
  • I'm also not sure that making the spec a bit more complex, if it makes the language arguably _simpler_, is a terrible thing. But the ripple effect you mention is a good counter-argument to this actually making the language simpler. – yshavit May 22 '15 at 18:21