1

This question is about the relation of an interface to the class which implements the interface. I can not see how this or this answers the question.

I created an interface Boxed and an abstract generic class Box, which implements the interface. Then I create two concrete classes IntegerBox and StringBox. Then I created a list of elements, which extend Box with an IntegerBox value and a StringBox value. So far so good.

Now I want to assign List<? extends Box> to List<Boxed>. My expectation is, that this should be valid, because whatever extends Box implements also Boxed. But the compiler does not allow me. This is the error:

main.java:29: error: incompatible types: List<CAP#1> cannot be converted to List<Boxed>
    List<Boxed> lb2 = laeb; // does not compile, although every value which extends Box implements Boxed
                      ^
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Box from capture of ? extends Box
1 error

I can copy the list:

List<? extends Box> laeb = List.of (new IntegerBox(42), new StringBox("answer"));
List<Boxed> lb1 = new ArrayList<> (laeb);

Every element of the type ? extends Box is used to create a value if the type Boxed. But the assignment reports an incompatible type. Why?

import java.util.List;
import java.util.ArrayList;

public class main
{
  static interface Boxed { }

  static abstract class Box<T> implements Boxed
  {
    T content;
    Box (T content) { this.content = content; }
  }

  static class IntegerBox extends Box<Integer> { IntegerBox (Integer content) { super (content); } }
  static class StringBox  extends Box<String>  { StringBox  (String content)  { super (content); } }

  public static void main (String ...arguments) throws Exception
  {
    IntegerBox i = new IntegerBox(42);
    StringBox  s = new StringBox("answer");

    List<? extends Box> laeb = List.of (i, s);

    Boxed b0 = i;  // => IntegerBox is compatible with Boxed
    Boxed b1 = s;  // => StringBox  is compatible with Boxed

    List<Boxed> lb1 = new ArrayList<> (laeb); // List<Boxed> can be created by values of "? extends Box"

    List<Boxed> lb2 = laeb; // does not compile, although every value which extends Box implements Boxed
  }
}
ceving
  • 21,900
  • 13
  • 104
  • 178

2 Answers2

2

Consider this:

List<StringBox> stringBoxList = new ArrayList<StringBox>();
List<? extends Box> boxList = stringBoxList; // works, StringBox extends Box

List<Boxed> boxedList = boxList; // suppose this *did* work
boxedList.add(new IntegerBox(42)); // this line definitely compiles, what does it do?

After the final line, stringBoxList would contain an IntegerBox, despite originally being an ArrayList<StringBox>. That's bad.

That's what the compiler is preventing.

To get around this is as simple as

List<? extends Boxed> boxedList = boxList;

boxedList.add(new IntegerBox(42));
// forbidden by the compiler, because IntegerBox is not necessarily the _same_ subclass    
// of Boxed as boxedList's elements

Or, alternately, you could write

List<Boxed> boxedList = Collections.unmodifiableList(boxList);

...because the problem goes away if you can't modify the list.

(But, bluntly, Is List<Dog> a subclass of List<Animal>? Why are Java generics not implicitly polymorphic? covers this, just not the direct case of ? extends.)

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
  • The conclusion `stringBoxList` contains a `IntegerBox` is wrong, because after the assignment `stringBoxList` is no `List` any more, it is now a `List`. and for `List` it is pretty fine to contain either `StringBox` or `IntegerBox`, because they loose their kind, when they become a `Boxed`. – ceving Sep 07 '20 at 07:19
  • @ceving: That's incorrect. `stringBoxList` _is still_ a `List`. A variable's type doesn't change when something else is assigned to it. There's still only one `ArrayList` object, and it is, and always was, an `ArrayList`. – Louis Wasserman Sep 07 '20 at 18:18
1

Now I want to assign List<? extends Box> to List<Boxed>.

You cannot do that, as List<Boxed> is not a supertype of your upper-bounded wildcard <? extends Box>.

Change:

List<Boxed> lb2 = laeb;

with:

List<? extends Boxed> lb2 = laeb;

List<Boxed> can be created by values of "? extends Box"

With:

List<Boxed> lb1 = new ArrayList<>(laeb);

you are not really creating the list with <? extends Box> type, but you rather populate the new list, with already defined immutable list you create above - List.of(i, s);


Note, that:

Note that, in this context, extends is used in a general sense to mean either "extends" (as in classes) or "implements" (as in interfaces).

The produced bytecode, therefore, contains only ordinary classes, interfaces, and methods.

Giorgi Tsiklauri
  • 9,715
  • 8
  • 45
  • 66
  • But `Boxed` is the interface. And the interface gets never "extended" in my example. – ceving Sep 07 '20 at 07:08
  • Why is it necessary to talk about "? extends Boxed" if this actually never happens in the code? Or did it happen anywhere, I can not see? Is the interface also a victim of type erasure? – ceving Sep 07 '20 at 07:35
  • 1. `extends` has the semantics of `extends` and `implements` in Generics; 2. Type erasure works for interfaces as well. See the updated answer. – Giorgi Tsiklauri Sep 07 '20 at 10:32