13

Looking through the Java Collections Framework, I've noticed quite a few of the interfaces have the comment (optional operation). These methods allow implementing classes to through an UnsupportedOperationException if they just don't want to implement that method.

An example of this is the addAll method in the Set Interface.

Now, as stated in this series of questions, interfaces are a defining contract for what the use can expect.

Interfaces are important because they separate what a class does from how it does it. The contract defining what a client can expect leaves the developer free to implement it any way they choose, as long as they uphold the contract.

and

An interface is a description of the actions that an object can do... for example when you flip a light switch, the light goes on, you don't care how, just that it does. In Object Oriented Programming, an Interface is a description of all functions that an object must have in order to be an "X".

and

I think the interface-based approach is significantly nicer. You can then mock out your dependencies nicely, and everything is basically less tightly coupled.

What is the point of an interface?

What are interfaces?

Interface + Extension (mixin) vs Base Class

Given that the purpose of interfaces is to define a contract and make your dependencies loosely coupled, doesn't having some methods throw an UnsupportedOperationException kind of defeat the purpose? It means I can no longer be passed a Set and just use addAll. Rather, I have to know what implementation of Set I was passed, so I can know if I can use addAll or not. That seems pretty worthless to me.

So what's the point of UnsupportedOperationException? Is it just making up for legacy code, and they need to clean up their interfaces? Or does it have a more sensical purpose that I'm missing?

2 Answers2

12

Look at the following interfaces:

These interfaces all declare mutating methods as optional. This is implicitly documenting the fact that the Collections class is able to return implementations of those interfaces that are immutable: that is, those optional mutation operations are guaranteed to fail. However, per the contract in the JavaDoc, all implementations of those interfaces must allow the read operations. This includes the "normal" implementations such as HashSet and LinkedList as well as the immutable wrappers in Collections.

Contrast with the queue interfaces:

These interface do not specify any optional operations: a queue, by definition, is designed to offer and poll elements in a FIFO manner. An immutable queue is about as useful as a car without wheels.


One common idea that comes up repeatedly is having an inheritance hierarchy that has both mutable and immutable objects. However, these all have drawbacks. The complexity muddies the waters without actually solving the problem.

  • A hypothetical Set could have the read operations, and a subinterface MutableSet could have the write operations. Liskov tells us that a MutableSet could then be passed in to anything that needs a Set. At first this sounds okay, but consider a method that expects the underlying set will not be modified while read: it would be possible for two threads to use the same set and violate the invariant of the set not changing. This could cause a problem e.g. if a method reads an element from the set twice and it is there the first time but not the second time.

  • Set could have no direct implementations, instead having MutableSet and ImmutableSet as subinterfaces which are then used for implementing classes. This has the same issue as above: at some point in the hierarchy, an interface has conflicting invariants. One says "this set must be mutable" and the other says "this set cannot change."

  • There could be two completely separate hierarchies for mutable and immutable data structures. This adds a ton of extra complexity for what ends up being very little gain. This also has the specific weakness of methods that do not care about mutability (e.g. I just want to iterate a list) must now support two separate interfaces. Since Java is statically typed, this means extra methods to handle both interface hierarchies.

  • We could have a single interface and allow implementations to throw exceptions if a method is not applicable to it. This is the route Java took, and it makes the most sense. The number of interfaces is kept to a minimum, and there are no mutability invariants because the documented interface makes no guarantees about mutability either way. If an immutability invariant is required, use the wrappers in Collections. If a method does not need to change a collection, simply do not change it. The drawback is a method cannot guarantee a collection will not change in another thread if it is provided a collection from outside, but that is a concern of the calling method (or its calling method) anyway.


Related reading: Why doesn't Java 8 include immutable collections?

2

It's basically YAGNI. All the concrete collections in the standard library are mutable, implementing or inheriting the optional operations. They don't care about general purpose immutable collections and neither do the vast majority of Java developers. They're not going to create an entire interface hierarchy just for immutable collections, then not include any implementations.

On the other hand, there are a few special-purpose values or "virtual" collections that could be very useful as immutable, such as empty set, and nCopies. Also, there are third-party immutable collections (such as Scala's), which might want to call existing Java code, so they left the possibility for immutable collections open in the least disruptive way.

Karl Bielefeldt
  • 148,830