69

During my first implementation extending the Java collection framework, I was quite surprised to see that the collection interface contains methods declared as optional. The implementer is expected to throw UnsupportedOperationExceptions if unsupported. This immediately struck me as a poor API design choice.

After reading much of Joshua Bloch's excellent "Effective Java" book, and later learning he may be responsible for these decisions, it didn't seem to gel with the principles espoused in the book. I would think declaring two interfaces: Collection, and MutableCollection which extends Collection with the "optional" methods would have led to much more maintainable client code.

There's an excellent summary of the issues here.

Was there a good reason why optional methods were chosen instead of the two interfaces implementation?

glenviewjeff
  • 1,395

4 Answers4

30

The FAQ provides the answer. In short, they saw a potential combinatorial explosion of needed interfaces with modifiable, unmodifiable view, delete-only, add-only, fixed-length, immutable (for threading), and so on for each possible set of implemented option methods.

ratchet freak
  • 25,986
8

It sounds to me like the Interface Segregation Principle wasn't as well explored back then as it is now; that way of doing things (i.e. your interface includes all the possible operations and you have "degenerate" methods that throw exceptions for the ones you don't need) was popular before SOLID and ISP became the de-facto standard for quality code.

Wayne Molina
  • 15,712
4

While some people might loathe "optional methods", they may in many cases offer better semantics than highly-segregated interfaces. Among other things, they allow for the possibilities that an object might gain abilities or characteristics in its lifetime, or that an object (especially a wrapper object) might not know when it is constructed what exact abilities it should report.

While I will hardly call the Java collection classes paragons of good design, I would suggest that a good collections framework should include at its foundation a large number of optional methods along with ways of asking a collection about its characteristics and abilities. Such a design will allow a single wrapper class to be used with a large variety of collections without accidentally obscuring abilities the underlying collection might possess. If methods weren't optional, then it would be necessary to have a different wrapper class for every combination of features that collections might support, or else have some wrappers be unusable in some situations.

For example, if a collection supports writing an item by index, or appending items at the end, but does not support inserting items in the middle, then code wanting to encapsulate it in wrapper which would log all actions performed on it would need a version of the logging wrapper which provided for the exact combination of supported abilities, or if none was available would have to use a wrapper which supported either append or write-by-index but not both. If, however, a unified collection interface provided all three methods as "optional", but then included methods to indicate which of the optional methods would be usable, then a single wrapper class could handle collections which implement any combination of features. When asked what features it supports, a wrapper could simply report whatever the encapsulated collection supports.

Note that the existence of "optional abilities" may in some cases allow aggregated collections to implement certain functions in ways that were much more efficient than would be possible if abilities were defined by the existence of implementations. For example, suppose a concatenate method was used to form a composite collection out of two others, the first of which happened to be an ArrayList with 1,000,000 elements and the last of which was a twenty-element collection which could only be iterated from the start. If the composite collection were asked for the 1,000,013th element (index 1,000,012), it could ask the ArrayList how many items it contained (i.e. 1,000,000), subtract that from the requested index (yielding 12), read and skip twelve elements from the second collection, and then return the next element.

In such a situation, even though the composite collection would not have an instantaneous way of returning an item by index, asking the composite collection for the 1,000,013th item would still be much faster than reading 1,000,013 items from it individually and ignoring all but the last one.

supercat
  • 8,629
-2

I would attribute it to the original developers just not knowing better back then. We've come a long way in OO design since 1998 or so when Java 2 and Collections were first released. What seems like obvious bad design now wasn't so obvious in the early days of OOP.

But it may have been done to prevent extra casting. If it was a second interface you'd have to cast your collections instances to call those optional methods which is also kind of ugly. As it is now you'd catch an UnsupportedOperationException right away and fix your code. But if there were two interfaces you'd to have to use instanceof and casting all over the place. Perhaps they considered that a valid tradeoff. Also back in the early Java 2 days instanceof was heavily frowned upon due to it's slow performance, they might have been trying to prevent over use of it.

Of course this is all wild speculation, I doubt we could ever answer this for sure unless one of the original collections architects chimes in.

Jberg
  • 113