6

<? extends T> makes for a read-only collection

<? super T> makes for a write-only collection

I somehow get why use a read-only collection,for instance to use it in a multithreaded environment (any other cases?)

But why use a write-only collection? What's the point if you cannot read from it and use its values at some point? I know that you can get an Object out of it but that defies type safety.

Edit: @Thomas the linked question (Difference between <? super T> and <? extends T> in Java) does show how to make a write only collection but does not answer 'why' would you need one in the first place.So it's not a duplicate

microwth
  • 1,016
  • 1
  • 14
  • 27
  • 2
    Generics isn't only about collections. – talex Sep 27 '22 at 08:18
  • Does this answer your question? [Difference between super T> and extends T> in Java](https://stackoverflow.com/questions/4343202/difference-between-super-t-and-extends-t-in-java) – Thomas Kläger Sep 27 '22 at 08:25
  • @Thomas the linked question does show how to make a write only collection but does not answer 'why' would you need one in the first place.So it's not a duplicate. – microwth Sep 27 '22 at 08:27
  • @talex Can you elaborate? – microwth Sep 27 '22 at 08:36
  • 1
    The answer tells you why: if your method needs a collection so that it can add elements to it your method is fine with a "write only collection" (from the point of your method). For the caller it is any collection - specifically for the caller the collection is not "write only". – Thomas Kläger Sep 27 '22 at 08:37
  • @microwth Look at the `copy` method at the bottom of the linked answer and assume that `T` is `Integer`. If `dest` was defined as `List`, than a caller could only pass `List` as an argument for `dest`. But because it's defined as `List super T>` a caller can also pass `List` or `List`. – Alex R Sep 27 '22 at 08:43
  • @Thomas but what is the use case? In what scenario would you need such a write-only collection? Is there any practical use case or is it just theory? – microwth Sep 27 '22 at 08:44

2 Answers2

9

Statements like

  • <? extends T> makes for a read-only collection

  • <? super T> makes for a write-only collection

are just wrong. Wildcard element types do not say anything about the ability to read or write.

To show counter examples:

static <T> void modify(List<? extends T> l) {
    l.sort(Comparator.comparing(Object::toString));
    l.remove(l.size() - 1);
    Collections.swap(l, 0, l.size() - 1);
    l.add(null);
    duplicateFirst(l);
}
static <U> void duplicateFirst(List<U> l) {
    U u = l.get(0);
    l.add(u);
}

shows quite some modifications possible for the List<? extends T>, without problems.

Likewise, you can read a List<? super T>.

static <T> void read(List<? super T> l) {
    for(var t: l) System.out.println(t);
}

Usage restrictions imposed by ? extends T or ? super T are only in relation to T. You can not take an object of type T, e.g. from another method parameter, and add it to a List<? extends T>, because the list’s actual type might be a subtype of T. Likewise, you can not assume the elements of a List<? super T> to be of type T, because the list’s actual type might be a supertype of T, so the only assumption you can make, is that the elements are instances of Object, as every object is.

So when you have a method like

public static <T> void copy(List<? super T> dest, List<? extends T> src)

the method can not take elements from dest and add them to src (in a typesafe way), but only the other way round.

It’s important to emphasize that unlike other programming languages, Java has use site variance, so the relationship between the two list described above only applies to the copy method declaring this relationship. The lists passed to this method do not have to be “consumer of T” and “producer of T” throughout their entire lifetime.

So you can use the method like

List<Integer> first = List.of(0, 1, 2, 3, 7, 8, 9);
List<Number> second = new ArrayList<>(Collections.nCopies(7, null));
Collections.copy(second, first);
List<Object> third = new ArrayList<>(Collections.nCopies(11, " x "));
Collections.copy(third.subList(2, 9), second);
System.out.println(third);

Yes, copy was a real life example. Online demo

Note how the second list changes its role from consumer of Integer to producer of Object for the two copy invocations while its actual element type is Number.

Other examples for ? super T


To sum it up, in Java, rules like PECS are relevant for the declaration of methods, to determine the (typical) roles of the arguments within the method itself. This raises the flexibility for the caller, as it allows combining different invariant types, like the example of copying from a List<Integer> to a List<Number>.

But never assume that the generic types tell anything about the ability to read or write a collection.

Holger
  • 285,553
  • 42
  • 434
  • 765
6

Note that "write only collection" depends on the point of view.

Lets write a method that adds a bunch of numbers to a collection:

public static void addNumbers(List<? super Integer> target, int count) {
    for (int i = 0; i < count; i++) {
        target.add(i);
    }
}

For this method the list target is a write only list: the method can only add numbers to it, it can not use the values that it added to the list.

On the other side there is the caller:

public static void caller() {
    List<Number> myList = new ArrayList<>();
    addNumbers(myList, 10);
    double sum = 0;
    for (Number n: myList) {
        sum += n.doubleValue();
    }
    System.out.println(sum);
}

This method works with a specific list (myList) and therefore can read the values that addNumbers stuffed into it.

For this method the list is not a write only list, for this method it is an ordinary list.

Thomas Kläger
  • 17,754
  • 3
  • 23
  • 34
  • you have to re-write the text books! In every article I've read something like that is never mentioned.Just the same dull Consumer can make only a write-only collection – microwth Sep 27 '22 at 09:05