Statements like
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.