6

When I dig into the gs-collection source for ImmutableList, it does not extends java.util.List. However the class javadoc mentioned that All ImmutableList implementations must implement the java.util.List.

Why must ask the implementation to implement java.util.List and not the ImmutableList itself to extend java.util.List?

Wins
  • 3,420
  • 4
  • 36
  • 70
  • 2
    What *does* it extend? – Platinum Azure Apr 08 '15 at 02:33
  • 1
    At its root, it extends `java.lang.Iterable` – Wins Apr 08 '15 at 02:35
  • Strange design. Instead of putting an unenforceable requirement into the Javadoc they could just have written `extends java.util.List` and have it enforced automatically. – user207421 Apr 08 '15 at 02:38
  • I agree, and this gives me headache because I don't want to use interface from gs-collections specific. I still want to use `java.util.List` without caring the underlying implementation. – Wins Apr 08 '15 at 02:40
  • 3
    Nevertheless the only person who is going to provide a useful answer is the author, and unless you get him here all you will get is more or less uninformed guesswork. – user207421 Apr 08 '15 at 02:44
  • Note: GS Collections has migrated to the Eclipse Foundation and is now Eclipse Collections - https://www.eclipse.org/collections/ – Donald Raab Feb 27 '17 at 05:33

1 Answers1

12

Why doesn't ImmutableList extend List?

ImmutableCollection doesn't extend java.util.Collection (and ImmutableList doesn't extend java.util.List) because Collection has mutating methods like add() and remove(). If immutable collections had these methods, they would always have to throw UnsupportedOperationException. For users of immutable collections, it would be strange to see add() and remove() in auto-complete choices as callable methods.

Why does the Javadoc impose a contract that all ImmutableList implementations also implement List?

It comes down to equality. An ImmutableList ought to equal a List, assuming both lists have the same contents in the same order. List.equals() imposes a Javadoc contract which states:

Returns true if and only if the specified object is also a list, both lists have the same size, and all corresponding pairs of elements in the two lists are equal.

What does it mean that "the specified object is also a list?" We can see in AbstractList.equals() that it means instanceof List.

public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof List))
        return false;
    ...
}

So all ImmutableList implementations must also implement List for equals() to work in a symmetric way. The immutable collection factories already hide implementation details like the fact that an immutable list with a single element is implemented by an ImmutableSingletonList. It also winds up hiding the List interface.

ImmutableList class diagram

Interop

A benefit of this design is that ImmutableList can be cast to List which is important for interop with existing APIs.

// Library method - cannot refactor the parameter type
public void printAll(List<?> list)
{
    for (Object each : list)
    {
        System.out.println(each);
    }
}

ImmutableList<Integer> immutableList = Lists.immutable.with(1, 2, 3);
List<Integer> castList = immutableList.castToList();
printAll(castList);
// also works
printAll((List<?>) immutableList);

// throws UnsupportedOperationException
castList.add(4);

Note: I am a developer on GS Collections.

Craig P. Motlin
  • 26,452
  • 17
  • 99
  • 126
  • 2
    Thanks for long answer, it makes sense, but I'm guessing that is one flavor of design. Personally I'd prefer to have the add() and remove() to throw UnsupportedOperationException rather than to call castToList(). Developer should know that if the code is using ImmutableList, add and remove won't work and that's developer's issue rather than design issue.But I guess that's just personal opinion. Thanks again for the answer – Wins Apr 09 '15 at 02:09
  • 2
    I think you might be interested in UnmodifiableMutableList, and its factory method MutableList.asUnmodifiable(). I kept this answer limited to ImmutableList but feel free to ask a new question about the differences and I'll come back and write more. – Craig P. Motlin Apr 09 '15 at 14:44
  • 2
    I understand UnmodifiableMutableList (the name is very explicit), but what I'm asking is why not making the ImmutableList to extend List, throw UnsupportedOperationException on add and remove, but retain the immutability of the list and the objects it contains. – Wins Apr 09 '15 at 15:11
  • 2
    What you're describing would certainly work. It came down to design tradeoffs. UnsupportedOperationExceptions are confusing and tough to deal with, though arguably no more confusing than the class diagram above. If we had mutating methods on ImmutableList, we might as well pull them up to the parent, ListIterable. Then MutableList and ImmutableList would become empty marker interfaces, which is odd. We could delete them, but then ListIterable.add() may or may not throw. The downside of the current design is interop. The upside is the ability to clearly express intent, especially for fields. – Craig P. Motlin Apr 09 '15 at 17:00