4

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?

jscs
  • 63,694
  • 13
  • 151
  • 195
user1944408
  • 509
  • 3
  • 12
  • 1
    That seems kind of hacky, the error message would be cryptic, and not a clear indication that it fails because the list is unmodifiable. – Jorn Vernee Aug 09 '16 at 15:57
  • 1
    Please refer to http://meta.stackoverflow.com/questions/293815/is-it-subjective-to-ask-about-why-something-wasnt-implemented-in-the-language and http://meta.stackoverflow.com/questions/323334/is-asking-why-on-language-specifications-still-considered-as-primarily-opinio, this is an opinion-based question. – Tunaki Aug 09 '16 at 15:57
  • 1
    Well that wouldn't prevent you deleting from the list, swapping elements around, adding `null`, or casting it and *then* trying to add to it. You still need the code to throw exceptions in those cases. What you're suggesting would not obviate the need for the code to throw exceptions. – khelwood Aug 09 '16 at 16:04
  • 7
    I actually think this is a good question -- clear, and with a focused, objective answer (albeit one that only a few people will be able to provide, so there's not a great chance of it being answered -- but that's not a reason to mark it as OT). If I had to _guess_, I'd guess that `? extends T` is really a fairly poor marker of immutability, since it doesn't preclude a lot of mutations: adding `null`, clearing, removal via iterator, etc. So, rather than providing a half-baked notion of immutability at compile-time, they just left it to nothing at compile time, docs for everything. – yshavit Aug 09 '16 at 16:08
  • I agree with @yshavit. This is a decent question.` List extends Something>` is not *immutable* it is a *read only* list. We can still perform operations like removal of element. Whereas `unmodifiableList()` returns a List on which we cannot call add / remove etc. The former was never meant to be *immutable* – TheLostMind Aug 09 '16 at 16:13
  • @khelwood Of course it would not prevent runtime exceptions in all cases but it will catch more bugs in compile time. – user1944408 Aug 09 '16 at 16:14
  • @yshavit Thank you for your comment. I agree that `? extends T` is a poor marker of immutability. Unmodifiable list is also a poor marker of read only list because it doesn't check your code in compile time but returning `? extends T` would help catching some errors in compile time. I don't understand why was this question downvoted, if the downvoter explains it would help me to put more qualitative and better phrased questions in the future. Thank you everyone. – user1944408 Aug 09 '16 at 16:21
  • For one thing, it would cause unnecessary clutter when declaring a variable or return type. And for all that, it only offers limited and incomplete compile-time protection, as others have pointed out. Good question though. +1 – shmosel Aug 09 '16 at 20:40
  • Related:PECS: http://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super – Jayan Aug 10 '16 at 04:44
  • That is another reason why ? extends T would be a good returning type. Unmodifiable list is a produces and 'producer extends' so it is easier to see from the type of your variable that the list is a producer. – user1944408 Aug 10 '16 at 10:43

1 Answers1

8

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.

Stuart Marks
  • 127,867
  • 37
  • 205
  • 259
  • I would say that frequency function is not defined correctly and that T should not extend T and it should be an Object. The same line of reasoning was used for get method of Map class. https://stackoverflow.com/questions/857420/what-are-the-reasons-why-map-getobject-key-is-not-fully-generic What if elements of the lists are another lists with different implementations? In Java these 2 lists can still be equal and Apache's frequency method should work but it does not. So your answer is not supportive of not having **unmodifiableList** returning **List extends T>**. – CodesInTheDark Jan 20 '18 at 03:30
  • @CodesInTheDark The choice of whether the parameter to `Map.get` (and similar methods) should be generic or take an `Object` is a design decision with various tradeoffs. You might prefer one over the other, depending upon how you value the different tradeoffs. Apache Commons made a different choice from the JDK. You might not prefer it, but that doesn't mean it's incorrect. Having `Collections.unmodifiableList` return `List extends T>`, however, is semantically incorrect. – Stuart Marks Jan 24 '18 at 05:09
  • Why? ? extends T means that it can be T. On the other hand frequency method does not work correctly when elements are Lists or Maps. – CodesInTheDark Jan 24 '18 at 07:02
  • Also if you make other methods to confirm with PECS principle then you cannot use your unmodifiableList as consumer parameter, only as producer parameter, which is the semantic that you want, right? – CodesInTheDark Jan 24 '18 at 07:40
  • I agree that there is a drawback in returning wildcards. It does make it harder to use in client's code. Ideally unmodifiableList should be a different class immutable class. – CodesInTheDark Jan 24 '18 at 08:17
  • @CodesInTheDark A value of type `? extends T` *can be* but isn't *necessarily* of type T; it's some unknown subtype of T. This means that you can never write an expression that matches `? extends T` (except for null). That's exactly the drawback you mention in your last comment. In general, suppose you have a method ` void foo(Bar bar, E e)` and E ends up being a wildcard. The only legal thing you can pass as the second arg is null. // Not sure what the problem is with `frequency`. Works for me. Maybe you should ask a separate question. – Stuart Marks Jan 24 '18 at 18:53
  • You are correct that the expression `? extends T` would not work for the method ` void foo(Bar bar, E e)`, but my point is that signature of that method is wrong because if bar is a consumer then it should be defined according to PECS principle. That is also good as code documentation because from the signature of the method you can see if bar is a consumer or producer. For your other question about problem with the frequency, the problem is that frequency does not work when E is a List because of the way it was defined. – CodesInTheDark Jun 17 '19 at 01:50
  • For example, it will not accept arguments List> and ArrayList while it should work because of the contract for the method equals for Lists: "In other words, two lists are defined to be equal if they contain the same elements in the same order. This definition ensures that the equals method works properly across different implementations of the List interface." – CodesInTheDark Jun 17 '19 at 01:59