29

Why this code does not compile (Parent is an interface)?

List<? extends Parent> list = ...
Parent p = factory.get();   // returns concrete implementation
list.set(0, p);   // fails here: set(int, ? extends Parent) cannot be applied to (int, Parent)
Sergey Mikhanov
  • 8,880
  • 9
  • 44
  • 54
  • 11
    You might think `List extends Parent>` is "a list of things that extends `Parent`", but that's not actually what it means. It means "there exists some type `C`, such that `C` extends `Parent`, and this is a List of that `C`." Since we don't know what that `C` is, we don't know that a Parent is-a `C`, so you can't put a `Parent` in a `List`. (C here is called a _capture type_.) But, you can take one out, because you get `C` out of the List, and `C` is-a `Parent`, so that's good. – Brian Goetz Dec 03 '18 at 14:08

4 Answers4

51

It's doing that for the sake of safety. Imagine if it worked:

List<Child> childList = new ArrayList<Child>();
childList.add(new Child());

List<? extends Parent> parentList = childList;
parentList.set(0, new Parent());

Child child = childList.get(0); // No! It's not a child! Type safety is broken...

The meaning of List<? extends Parent> is "The is a list of some type which extends Parent. We don't know which type - it could be a List<Parent>, a List<Child>, or a List<GrandChild>." That makes it safe to fetch any items out of the List<T> API and convert from T to Parent, but it's not safe to call in to the List<T> API converting from Parent to T... because that conversion may be invalid.

Jon Skeet
  • 1,421,763
  • 867
  • 9,128
  • 9,194
  • 3
    Bad example of polymorphism, I think? A "child" is not (always) a "parent". – justhalf Mar 24 '17 at 07:14
  • 2
    @justhalf: `Parent` was in the question, and suggests that `Child` is a natural example of a subclass. Not the class names I'd have chosen from scratch, but clear enough in terms of the context the question is about. – Jon Skeet Mar 24 '17 at 08:21
  • Yeah, I understand, but I think it'd have been better if the example here uses "SingleParent" or "NewParent" as the subclass =) – justhalf Mar 24 '17 at 08:41
  • But what if just made the 'Parent' class final and created the list as: List extends Parent> parentList = new ArrayList<>(); (not much point I know but...) would the compiler not know that subclasses of 'Parent' can't exist and therefore be able to infer the runtime type of said List? If so, why doesn't it allow us to add a new 'Parent' object in this case? (it can guarantee the type no?) What am I missing?! Thanks – Zippy Apr 02 '18 at 14:11
  • @Zippy: I wouldn't expect that to work, because I wouldn't expect the language designers to want to try to build the rules into the language specification. Generics is already really tricky - trying to add corner cases to help in relatively unusual situations is almost certainly not worth the extra complexity. – Jon Skeet Apr 02 '18 at 15:09
18
List<? super Parent>

PECS - "Producer - Extends, Consumer - Super". Your List is a consumer of Parent objects.

Michael
  • 41,989
  • 11
  • 82
  • 128
Bozho
  • 588,226
  • 146
  • 1,060
  • 1,140
3

Here's my understanding.

Suppose we have a generic type with 2 methods

type L<T>
    T get();
    void set(T);

Suppose we have a super type P, and it has sub types C1, C2 ... Cn. (for convenience we say P is a subtype of itself, and is actually one of the Ci)

Now we also got n concrete types L<C1>, L<C2> ... L<Cn>, as if we have manually written n types:

type L_Ci_
    Ci get();
    void set(Ci);

We didn't have to manually write them, that's the point. There are no relations among these types

L<Ci> oi = ...;
L<Cj> oj = oi; // doesn't compile. L<Ci> and L<Cj> are not compatible types. 

For C++ template, that's the end of story. It's basically macro expansion - based on one "template" class, it generates many concrete classes, with no type relations among them.

For Java, there's more. We also got a type L<? extends P>, it is a super type of any L<Ci>

L<Ci> oi = ...;
L<? extends P> o = oi; // ok, assign subtype to supertype

What kind of method should exist in L<? extends P>? As a super type, any of its methods must be hornored by its subtypes. This method would work:

type L<? extends P>
    P get();

because in any of its subtype L<Ci>, there's a method Ci get(), which is compatible with P get() - the overriding method has the same signature and covariant return type.

This can't work for set() though - we cannot find a type X, so that void set(X) can be overridden by void set(Ci) for any Ci. Therefore set() method doesn't exist in L<? extends P>.

Also there's a L<? super P> which goes the other way. It has set(P), but no get(). If Si is a super type of P, L<? super P> is a super type of L<Si>.

type L<? super P>
    void set(P);

type L<Si>
    Si get();
    void set(Si);

set(Si) "overrides" set(P) not in the usual sense, but compiler can see that any valid invocation on set(P) is a valid invocation on set(Si)

irreputable
  • 44,725
  • 9
  • 65
  • 93
1

This is because of "capture conversion" that happens here.

Every time the compiler will see a wildcard type - it will replace that by a "capture" (seen in compiler errors as CAP#1), thus:

List<? extends Parent> list

will become List<CAP#1> where CAP#1 <: Parent, where the notation <: means subtype of Parent (also Parent <: Parent).

java-12 compiler, when you do something like below, shows this in action:

List<? extends Parent> list = new ArrayList<>();
list.add(new Parent());

Among the error message you will see:

.....
CAP#1 extends Parent from capture of ? extends Parent
.....

When you retrieve something from list, you can only assign that to a Parent. If, theoretically, java language would allow to declare this CAP#1, you could assign list.get(0) to that, but that is not allowed. Because CAP#1 is a subtype of Parent, assigning a virtual CAP#1, that list produces, to a Parent (the super type) is more that OK. It's like doing:

String s = "s";
CharSequence s = s; // assign to the super type

Now, why you can't do list.set(0, p)? Your list, remember, is of type CAP#1 and you are trying to add a Parent to a List<CAP#1>; that is you are trying to add super type to a List of subtypes, that can't work.

Eugene
  • 117,005
  • 15
  • 201
  • 306