the list of strings, and finding no more short ones, return false. When remove was invoked, it would
invoke remove on the underlying iterator, thereby removing the last (long) string that hasNext rejected.
That would be incorrect. Since the above code is valid, you cannot fix the problem by forbidding the sequence
of methods. You are effectively stuck. Because of this, you cannot build a filtering iterator on top of another
Iterator object. You can build one on top of a ListIterator though, since it allows you to back up to
the previously returned short string.
The methods of ListIterator have contracts similar to those of Iterator, as you have learned earlier
in this chapter. You can provide ListIterator objects in some circumstances where you might otherwise
write an Iterator. If you are writing a general utility class for others to use, you should implement
ListIterator instead of Iterator if possible.
Exercise 21.4: Write a version of ShortStrings that implements ListIterator to filter a
ListIterator object. Should your class extend ShortStrings?
21.14. Writing Collection Implementations
You will usually find that at least one of the collection implementations will satisfy your needs. If not, you
can implement relevant collection interfaces yourself to provide collections that satisfy your particular needs.
You will find skeletal implementations in the abstract classes AbstractCollection, AbstractSet,
AbstractList, AbstractSequentialList, AbstractQueue, and AbstractMap. You can
extend these classes to create your own collections, often with less work than starting from the interfaces
directly. The concrete collections shown in Figure 21-1 on page 568 each extend the appropriate abstract
collection type, as shown in the concrete type tree in Figure 21-1.
These abstract collection classes are designed to be helpful superclasses for your own collection
implementations. They are not requiredin some cases you will find it easier or more efficient to directly
implement the interfaces.
Each abstract collection class declares a few abstract methods and uses them in default implementations of
other methods of the collection type. For example, AbstractList has two abstract methods: size and
get. All other querying methods of AbstractList, including its iterators, are implemented by using these
methods. You need only write your own implementation of the other methods if you want to, typically to
either increase efficiency or to allow something disallowed by default. For example, if your list is modifiable,
your subclass of AbstractList will have to provide an overriding implementation of the set method,
which by default throws UnsupportedOperationException.
The root of the abstract collection types is AbstractCollection. If you need a collection that isn't a set,
list, or map you can subclass this directly. Otherwise, you will probably find it more useful to subclass one of
the more specific abstract collections classes.
If the Collection class you are creating is unmodifiable (if the modification methods of your collection
should throw UnsupportedOperationException), your subclass of AbstractCollection need
only provide an implementation of the size and iterator methods. This means you must at least write an
implementation of Iterator for your collection. If your collection is modifiable, you must also override the
default implementation of the add method (which throws UnsupportedOperationException) and
your iterator must support remove.
AbstractSet extends AbstractCollection, and the methods you must implement and can override
are the same, with the additional constraint that a subclass of AbstractSet must conform to the contract of
the Set interface. It also overrides the implemention of equals and hashCode from Object.