6

ImmutableSet implements the Set interface. The functions that don't make sense to an ImmutableSet are now called "Optional Operations" for Set. I assume for situations like this. So ImmutableSet now throws an UnsupportedOperationException for many Optional Operations.

This seems backwards to me. I was taught that an Interface was a contract so that you could use impose functionality across different implementations. The approach of Optional Operations seem to fundamentally change(contradict?) what Interfaces are meant to do. Implementing this today I would have the Set Interface broken into two interfaces: one for one for immutable operations and a second extending those operations for mutators. (Very quick, off the cuff solution)

I understand that technology changes. I'm not saying It should be done one way or another. My question is, does this change reflect a change in some underlying philosophy for Java? Is it just more of a bandaid to make things backwards compatible? Did I have an incomplete understanding of Interfaces?

Carlos Bribiescas
  • 4,197
  • 9
  • 35
  • 66
  • 1
    *"Interface was a contract so that you could use impose functionality across different implementations"* - Isn't that something the collection interfaces do successfully in the end? Using exceptions as part of the functionality is maybe unpopular but it is a language feature and there is nothing optional when using or implementing a collection correctly. You have to throw / expect exceptions. I have wished for thinner interfaces or at least methods like `.supportsRemoval()` often enough though. `ImmutableSet` extends `Set` in that it specifies which methods are now guaranteed to throw. – zapl Oct 30 '14 at 19:46

1 Answers1

8

The Java Collections API Design FAQ answers this question in detail:

Q: Why don't you support immutability directly in the core collection interfaces so that you can do away with optional operations (and UnsupportedOperationException)?

A: This is the most controversial design decision in the whole API. Clearly, static (compile time) type checking is highly desirable, and is the norm in Java. We would have supported it if we believed it were feasible. Unfortunately, attempts to achieve this goal cause an explosion in the size of the interface hierarchy, and do not succeed in eliminating the need for runtime exceptions (though they reduce it substantially).

Doug Lea, who wrote a popular Java collections package that did reflect mutability distinctions in its interface hierarchy, no longer believes it is a viable approach, based on user experience with his collections package. In his words (from personal correspondence) "Much as it pains me to say it, strong static typing does not work for collection interfaces in Java."

To illustrate the problem in gory detail, suppose you want to add the notion of modifiability to the Hierarchy. You need four new interfaces: ModifiableCollection, ModifiableSet, ModifiableList, and ModifiableMap. What was previously a simple hierarchy is now a messy heterarchy. Also, you need a new Iterator interface for use with unmodifiable Collections, that does not contain the remove operation. Now can you do away with UnsupportedOperationException? Unfortunately not.

Consider arrays. They implement most of the List operations, but not remove and add. They are "fixed-size" Lists. If you want to capture this notion in the hierarchy, you have to add two new interfaces: VariableSizeList and VariableSizeMap. You don't have to add VariableSizeCollection and VariableSizeSet, because they'd be identical to ModifiableCollection and ModifiableSet, but you might choose to add them anyway for consistency's sake. Also, you need a new variety of ListIterator that doesn't support the add and remove operations, to go along with unmodifiable List. Now we're up to ten or twelve interfaces, plus two new Iterator interfaces, instead of our original four. Are we done? No.

Consider logs (such as error logs, audit logs and journals for recoverable data objects). They are natural append-only sequences, that support all of the List operations except for remove and set (replace). They require a new core interface, and a new iterator.

And what about immutable Collections, as opposed to unmodifiable ones? (i.e., Collections that cannot be changed by the client AND will never change for any other reason). Many argue that this is the most important distinction of all, because it allows multiple threads to access a collection concurrently without the need for synchronization. Adding this support to the type hierarchy requires four more interfaces.

Now we're up to twenty or so interfaces and five iterators, and it's almost certain that there are still collections arising in practice that don't fit cleanly into any of the interfaces. For example, the collection-views returned by Map are natural delete-only collections. Also, there are collections that will reject certain elements on the basis of their value, so we still haven't done away with runtime exceptions.

When all was said and done, we felt that it was a sound engineering compromise to sidestep the whole issue by providing a very small set of core interfaces that can throw a runtime exception.

In short, having interfaces like Set with optional operations was done to prevent an exponential explosion in the number of different interfaces needed. It is not as simple as just "immutable" and "mutable". Guava's ImmutableSet then had to implement Set to be interoperable with all other code which uses Sets. It's not ideal but there is really no better way to do it.

Community
  • 1
  • 1
Boann
  • 48,794
  • 16
  • 117
  • 146
  • Wouldn't it have been more intuitive to make Collection extend ImmutableCollection (or to give it a better name, ReadableCollection), so that methods that changed the Collection could be kept out of the base interface? As things stand, it breaks the Liskov Substitution Principle. – Breandán Dalton Oct 30 '14 at 15:58
  • 1
    @B.Dalton Even if it is modifiable in principle, Collection would still have optional operations, due to type, value, or capacity constraints, or the delete-only collection views returned by Map, as mentioned above. Meanwhile, the ReadableCollection interface would be useless: You couldn't rely on a ReadableCollection to be immutable, because it could be a mutable subclass, and you couldn't pass it around to other code expecting it to be unmodifiable by them, because such code could cast it to Collection. So the separation wouldn't achieve anything. – Boann Oct 30 '14 at 16:19
  • While I don't know of a better solution, it seems like Interfaces have(had?) a purpose and they are shoehorning the problem into the wrong solution. Do you know of any other paradigms that better address the issue? – Carlos Bribiescas Oct 30 '14 at 18:10