<? 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
CodePudding user response:
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.
CodePudding user response:
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
Collections.fill(List<? super T> list, T obj)
Collections.addAll(Collection<? super T> c, T... elements)
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.