Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why is Collections.unmodifiableList not defined to return List<? extends T> instead of List<T>?

Changing Collections.unmodifiableList to return List<? extends T> instead of List<T> would then prevent adding and removing an element in compile time instead of throwing a runtime exception.

Is there a critical problem caused by this alternative that would preclude it?

like image 951
user1944408 Avatar asked Mar 11 '23 11:03

user1944408


1 Answers

The fundamental reason that Collections.unmodifiableList doesn't return List<? extends T> is that it's the wrong return type. First a bit of background. The current declaration is

static <T> List<T> unmodifiableList(List<? extends T> list)

Note that this takes a List<? extends T> but returns List<T>. This conforms to Bloch's PECS principle (also known to Naftalin/Wadler as the put and get principle). Since the returned list is unmodifiable, you can only take things out of it, hence it's a "producer" of elements of type T.

This provides the caller a bit more flexibility as to the return type. For instance, one could do this:

List<Double> src = Arrays.asList(1.0, 2.0, 3.0);
List<Number> list1 = Collections.unmodifiableList(src);

This is useful in case there are already other functions, for example, that operate on List<Number>.

Now consider an alternative declaration:

static <T> List<? extends T> wildUnmodifiableList(List<? extends T> list)

This says something quite different. It means that it takes a list of some subtype of T and returns a list of some unknown subtype of T, which might differ from the first type. Since the returned list is an unmodifiable view of the first list, this doesn't make any sense. Let's illustrate with an example:

List<Double> src = Arrays.asList(1.0, 2.0, 3.0);
List<? extends Number> list2 = wildUnmodifiableList(src);

This means that we pass in a List<Double> but we might get back a list of some other type, perhaps List<Integer>. Again, that doesn't really make any sense.

You can see that this declaration is incorrect when you try to use it. Consider the IterableUtils.frequency method from Apache Commons Collections:

static <E,T extends E> int frequency(Iterable<E> iterable, T obj)

This works great with the current declaration:

List<Double> src = Arrays.asList(1.0, 2.0, 3.0);
List<Number> list1 = Collections.unmodifiableList(src);
int freq1 = frequency(list1, 0.0);

But it fails with the wildcard version:

List<Double> src = Arrays.asList(1.0, 2.0, 3.0);
List<? extends Number> list2 = wildUnmodifiableList(src);
int freq2 = frequency(list2, 0.0); // COMPILE-TIME ERROR

The reason is that the declaration of frequency requires the second argument to be a subtype of the element type of the first argument. In the first case it's a Double which is a subtype of Number. But in the second case, Double is not a subtype of ? extends Number, resulting in a type mismatch.

like image 86
Stuart Marks Avatar answered Apr 28 '23 10:04

Stuart Marks