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?

MirroredFate
  • 725
  • 8
  • 19
  • I do not know which JRE you are using, but my Oracle version 8 does not define `addAll` in `HashSet`. It defers to the default implementation in `AbstractCollection` which most certainly does _not_ throw `UnsupportedOperationException`. –  Jul 10 '15 at 17:48
  • @Snowman You're right. I missread the docs. I will edit my question. – MirroredFate Jul 10 '15 at 17:51
  • 1
    I like to start up Eclipse and look at the source code, bouncing around code references and definitions to make sure I have it right. As long as the JRE is linked to `src.zip` it works great. It helps to know exactly what code the JRE is running sometimes and not defer to the JavaDoc which can be a bit verbose. –  Jul 10 '15 at 17:54

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?

  • 1
    But if methods are optional, what place do they have in the interface? Shouldn't there be a separate interface containing the optional methods, for example `MutableCollection`? – MirroredFate Jul 10 '15 at 18:37
  • No. There is no way to have mutable and immutable objects in the same hierarchy in any meaningful way. There was a recent question that had a good diagram showing the complexity and an explanation of why that is a bad idea, but it is deleted. Maybe someone else knows of a question to help explain this, I cannot find anything. But I will update my answer to explain a little. –  Jul 10 '15 at 18:43
  • That's kind of a broad statement about immutable queues. I used [one](http://docs.scala-lang.org/overviews/collections/concrete-immutable-collection-classes.html#immutable_queues) just a couple days ago to solve [this problem](http://programmers.stackexchange.com/q/288962/3965). – Karl Bielefeldt Jul 10 '15 at 18:51
  • @Snowman But that seems to make out that mutable and immutable objects as opposites of each other. I think immutable object are really just objects lacking the capability to mutate. Honestly, the way it is right now is more complex and muddled, because you have to figure out what is a mutable implementation and what is not. It seems to me the only difference between putting all the methods in one interface as opposed to splitting out the mutable methods is one of clarity. – MirroredFate Jul 10 '15 at 18:57
  • @MirroredFate read my most recent edit. –  Jul 10 '15 at 18:58
  • @Snowman Your edit certainly clarifies things, thank you. However, the negatives about Liskov substitution in your first point are equally applicable to your last point. What happens when you pass a `Set` into a method that expects an immutable `Set`? The truth is, in both cases `Set` makes *no guarantee* of immutability; however, you *can* guarantee mutability at least in the first instance. – MirroredFate Jul 10 '15 at 19:16
  • @MirroredFate in the first case, `Set` is implicitly `ImmutableSet` because it offers no mutability methods. –  Jul 10 '15 at 19:18
  • @Snowman I disagree. One might assume that it `Set` is immutable because they don't see any mutators, but that would clearly be a bad assumption, as we know that `Set` does not guarantee immutability. Rather, you would want to assume that some concrete implementation (`AbstractImmutableSet` or something) is immutable, because it would explicitly guarantee immutability. I feel that assuming any object *implicitly* adheres to some characteristic is incredibly dangerous. Interfaces just guarantee existence of some functionality, they don't guarantee a *lack* of functionality. – MirroredFate Jul 10 '15 at 19:24
  • @MirroredFate See my second bullet point. That does not really change anything, it just breaks a different interface. –  Jul 10 '15 at 19:27
  • @Snowman But there is nothing wrong with mutually exclusive inheritance. The whole point of hierarchies is to allow certain functionality to branch in one direction, and other functionality to branch in different direction. I don't see how it actually *breaks* anything. If you use a `Set`, you just can't make any assumptions about immutability one way or the other. If you use an `ImmutableSet`, you know it's immutable, and if you use a `MutableSet`, you know it's mutable. – MirroredFate Jul 10 '15 at 19:37
  • @MirroredFate If you want to keep discussing this, please head over to **[The Whiteboard](http://chat.stackexchange.com/rooms/21/the-whiteboard)**. –  Jul 10 '15 at 19:45
  • Although I don't think the reasoning is sound (Java's, not Snowman's), I believe this question answers the underlying logic behind Java's design, so I'll mark it accepted. – MirroredFate Jul 15 '15 at 17:09
  • Clearly mutability should have been part of the contract of an interface explicitly rather than just inferable from specific operations. And also, not having two hierarchies because "it's too much work" seems to me to be premature optimization. I agree with @MirroredFate – interstar May 21 '20 at 20:28
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
  • 146,727
  • 38
  • 279
  • 479
  • Ok, that makes some sense. It still seems like it approaches the problem from the wrong direction, though. Why not start by defining immutable collection interfaces, and then define the mutable interfaces for the mutable collection implementations? – MirroredFate Jul 10 '15 at 18:44