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.

5 Answers
5

The FAQ provides the answer. In short, they saw a potential combinatorial explosion of needed interfaces with modifiable, unmodifiable, delete-only, add-only, immutable (for threading), and so on for each implmentation.

All of this would have been avoided if Java had a const keyword like C++.
–
Etienne de MartelMay 20 '11 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 GMay 20 '11 at 20:54

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.

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.
–
supercatJul 16 '12 at 15:47

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.

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 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 asking it for 1,000,013 items and ignoring all but the last one.

@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.
–
supercatOct 13 '14 at 17:47

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.
–
kevin clineOct 13 '14 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...
–
supercatOct 13 '14 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...
–
supercatOct 13 '14 at 18:54

...that it may be enumerated an unlimited number of times while always returning the same items. If a wrapped collection implements "popsicle immunity", the wrapper's AsImmutableCollection method might return a new immutable copy of the wrapped collection each time it is invoked before the collection is frozen, but construct an ImmutableCollectionWrapper around the wrapped collection the first time the collection reports that it has become immutable, and return references to the wrapper on all subsequent calls.
–
supercatOct 13 '14 at 18:59

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.

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.
–
Etienne de MartelMay 20 '11 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.
–
JbergMay 20 '11 at 18:05

2

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.
–
Etienne de MartelMay 20 '11 at 18:43