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?

5
  • IMO, there is not a good reason. The C++ STL was created by Stepanov in the early 80's. Although it's usability was limited by the awkward C++ template syntax, it is a paragon of consistency and usability when compared to the Java collection classes. Commented May 21, 2011 at 3:35
  • There was a C++ STL 'porting' to Java. But it was 5 times bigger in class count. With this mind I don't buy it that the larger one is more consistent and usable. docs.oracle.com/javase/6/docs/technotes/guides/collections/…
    – Random42
    Commented Oct 12, 2014 at 13:54
  • @m3th0dman It's much larger in Java because Java doesn't have anything equivalent in power to C++ templates. Commented Oct 13, 2014 at 16:35
  • 1
    Maybe it's just some weird style I have developed, but my code tends to treat all collections as "read only", (more precisely, just something which you count or over which you iterate) except for the few methods that actually create the collections. Probably good practice anyway (esp. for concurrency). And the optional methods that so many complain about have never been a real issue for me. Nor have the Generics nightmares with "super" and "extends" ever been (much) of an issue. Just wondering if others use this general practice?
    – user949300
    Commented May 6, 2015 at 16:44
  • I like your comment @user949300. I'm going to take more active note but this rings true to me as well. Commented Mar 17, 2021 at 9:23

4 Answers 4

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.

7
  • 6
    All of this would have been avoided if Java had a const keyword like C++. Commented May 20, 2011 at 15:17
  • @Etienne: Better would be metaclasses like Python. Then you could programmatically build the combinatorial number of interfaces. The problem with const is that it only gives you one or two dimensions: vector<int>, const vector<int>, vector<const int>, const vector<const int>. So far so good, but then you try implementing graphs, and you want to make the graph structure constant, but the node attributes modifiable, etc.
    – Neil G
    Commented May 20, 2011 at 20:54
  • 2
    @Etienne, yet another reason to bump up "Learn Scala" on my to-do list! Commented May 21, 2011 at 2:30
  • 2
    What I don't understand is, why they didn't make a can method that would test if an operation is possible? It would keep the interface simple and fast.
    – user541686
    Commented May 21, 2011 at 4:18
  • 3
    @Etienne de Martel Nonsense. How would that help in the combinatorial explosion? Commented May 21, 2011 at 20:53
9

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.

2
  • 2
    Why a downvote? Somebody doesn't like the ISP? Commented May 21, 2011 at 12:11
  • It's also worth noting that in frameworks that support variance there is a HUGE advantage to segregating those aspects of an interface which can be covariant or contravariant from those which are fundamentally invariant. Even absent such support, in frameworks that don't use type erasure there would value in segregating aspects of an interface which are type-independent (e.g. allowing one get the Count of a collection without having to worry about what types of items it contains), but in type-erasure-based frameworks like Java that's not such an issue.
    – supercat
    Commented Jul 16, 2012 at 15:47
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.

8
  • 1
    @kevincline: Do you disagree with the quoted wording? I would consider the design of the means by which interface implementations can describe their essential characteristics and abilities to be one of the most important aspects of interface design, even though it doesn't often get much attention. If different implementations of an interface will have different optimal ways of accomplishing certain common tasks, there should be a means for clients that care about performance to select the best approach in scenarios where it would matter.
    – supercat
    Commented Oct 13, 2014 at 17:47
  • 1
    sorry, incomplete comment. I was about to say that " 'ways of asking about a collection' ..." moves what should be a compile-time check to run-time. Commented Oct 13, 2014 at 18:05
  • @kevincline: In cases where a client needs to have a certain ability and can't live without it, compile-time checks can be helpful. On the other hand, there are many situations where collections could have abilities beyond those which they can be compile-time-guaranteed to have. In some cases, it may make sense to have a derived interface whose contract specifies that all legitimate implementations of the derived interface must support particular methods which are optional in the base interface, but in cases where code will be able to handle something whether or not it has some feature...
    – supercat
    Commented Oct 13, 2014 at 18:47
  • ...but will benefit from the feature of it exists, it's better to have the code ask the object whether it supports the feature than to ask the type system whether the object's type promises to support the feature. If a particular "specific" interface will be widely used, one might include an AsXXX method in the base interface which will return the object upon which it is invoked if it implements that interface, return a wrapper object which supports that interface if possible, or throw an exception if not. For example, an ImmutableCollection interface might require by contract...
    – supercat
    Commented Oct 13, 2014 at 18:54
  • 2
    There is a problem with your proposal: an "ask then do" pattern has a concurrency issue if the capabilities of an object can change during its lifetime. (If you are going to have to risk an exception anyway, you might as well not bother asking...)
    – jhominal
    Commented Apr 24, 2015 at 13:37
-1

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.

4
  • 7
    What casting? If a method returns you a Collection and not a MutableCollection, it's a clear sign that it is not meant to be modified. I don't know why anybody would need to cast them. Having separate interfaces means you'll get those kind of errors at compile time instead of getting an exception at run time. The earlier you get the error, the better. Commented May 20, 2011 at 15:15
  • 1
    Because that's way less flexible, one of the largest benefits to the collection library is that you can return the high level interfaces everywhere and not worry about the actual implementation. If you're using two interfaces you're now more tightly coupled. In most cases you'd simply want to return List, and not ImmutableList because you usually want to leave that to the calling class to determine.
    – Jberg
    Commented May 20, 2011 at 18:05
  • 6
    If I give you a read only collection, that's because I don't want it to be modified. Throwing an exception and relying on documentation feels a hell of a lot like a patch. In C++, I would simply return a const object, instantly telling the user that the object cannot be modified. Commented May 20, 2011 at 18:43
  • 7
    1998 wasn't "early days of OO design".
    – quant_dev
    Commented May 21, 2011 at 9:21

Not the answer you're looking for? Browse other questions tagged or ask your own question.