-
Notifications
You must be signed in to change notification settings - Fork 10.9k
CollectionHelpersExplained
Sometimes you need to write your own collection extensions. Perhaps you want to
add special behavior when elements are added to a list, or you want to write an
Iterable
that's actually backed by a database query. Guava provides a number
of utilities to make these tasks easier for you, and for us. (We are, after
all, in the business of extending the collections framework ourselves.)
For all the various collection interfaces, Guava provides Forwarding
abstract
classes to simplify using the decorator pattern.
The Forwarding
classes define one abstract method, delegate()
, which you
should override to return the decorated object. Each of the other methods
delegate directly to the delegate: so, for example, ForwardingList.get(int)
is
simply implemented as delegate().get(int)
.
By subclassing ForwardingXXX
and implementing the delegate()
method, you can
override only selected methods in the targeted class, adding decorated
functionality without having to delegate every method yourself.
Additionally, many methods have a standardMethod
implementation which you can
use to recover expected behavior, providing some of the same benefits as e.g.
extending AbstractList
or the other skeleton classes in the JDK.
Let's do an example. Suppose you wanted to decorate a List
so that it logged
all elements added to it. Of course, we want to log elements no matter which
method is used to add them -- add(int, E)
, add(E)
, or
addAll(Collection)
-- so we have to override all of these methods.
class AddLoggingList<E> extends ForwardingList<E> {
final List<E> delegate; // backing list
@Override protected List<E> delegate() {
return delegate;
}
@Override public void add(int index, E elem) {
log(index, elem);
super.add(index, elem);
}
@Override public boolean add(E elem) {
return standardAdd(elem); // implements in terms of add(int, E)
}
@Override public boolean addAll(Collection<? extends E> c) {
return standardAddAll(c); // implements in terms of add
}
}
Remember, by default, all methods forward directly to the delegate, so
overriding ForwardingMap.put
will not change the behavior of
ForwardingMap.putAll
. Be careful to override every method whose behavior must
be changed, and make sure that your decorated collection satisfies its contract.
Generally, most methods provided by the abstract collection skeletons like
AbstractList
are also provided as standard
implementations in the
Forwarding
decorators.
Interfaces that provide special views sometimes provide Standard
implementations of those views. For example, ForwardingMap
provides
StandardKeySet
, StandardValues
, and StandardEntrySet
classes, each of
which delegate their methods to the decorated map whenever possible, or
otherwise, they leave methods that can't be delegated as abstract.
Interface | Forwarding Decorator |
---|---|
Collection |
ForwardingCollection |
List |
ForwardingList |
Set |
ForwardingSet |
SortedSet |
ForwardingSortedSet |
Map |
ForwardingMap |
SortedMap |
ForwardingSortedMap |
ConcurrentMap |
ForwardingConcurrentMap |
Map.Entry |
ForwardingMapEntry |
Queue |
ForwardingQueue |
Iterator |
ForwardingIterator |
ListIterator |
ForwardingListIterator |
Multiset |
ForwardingMultiset |
Multimap |
ForwardingMultimap |
ListMultimap |
ForwardingListMultimap |
SetMultimap |
ForwardingSetMultimap |
Sometimes, the normal Iterator
interface isn't enough.
Iterators
supports the method Iterators.peekingIterator(Iterator)
, which
wraps an Iterator
and returns a PeekingIterator
, a subtype of Iterator
that lets you peek()
at the element that will be returned by the next call
to next()
.
Note: the PeekingIterator
returned by Iterators.peekingIterator
does not
support remove()
calls after a peek()
.
Let's do an example: copying a List
while eliminating consecutive duplicate
elements.
List<E> result = Lists.newArrayList();
PeekingIterator<E> iter = Iterators.peekingIterator(source.iterator());
while (iter.hasNext()) {
E current = iter.next();
while (iter.hasNext() && iter.peek().equals(current)) {
// skip this duplicate element
iter.next();
}
result.add(current);
}
The traditional way to do this involves keeping track of the previous element,
and falling back under certain conditions, but that's a tricky and bug-prone
business. PeekingIterator
is comparatively straightforward to understand and
use.
Implementing your own Iterator
? AbstractIterator
can make your life
easier.
It's easiest to explain with an example. Let's say we wanted to wrap an iterator so as to skip null values.
public static Iterator<String> skipNulls(final Iterator<String> in) {
return new AbstractIterator<String>() {
protected String computeNext() {
while (in.hasNext()) {
String s = in.next();
if (s != null) {
return s;
}
}
return endOfData();
}
};
}
You implement one method, computeNext()
, that just computes the next value.
When the sequence is done, just return endOfData()
to mark the end of the
iteration.
Note: AbstractIterator
extends UnmodifiableIterator
, which forbids the
implementation of remove()
. If you need an iterator that supports remove()
,
you should not extend AbstractIterator
.
Some iterators are more easily expressed in other ways.
AbstractSequentialIterator
provides another way of expressing an iteration.
Iterator<Integer> powersOfTwo = new AbstractSequentialIterator<Integer>(1) { // note the initial value!
protected Integer computeNext(Integer previous) {
return (previous == 1 << 30) ? null : previous * 2;
}
};
Here, we implement the method computeNext(T)
, which accepts the previous
value as an argument.
Note that you must additionally pass an initial value, or null
if the iterator
should end immediately. Note that computeNext
assumes that a null
value
implies the end of iteration -- AbstractSequentialIterator
cannot be used to
implement an iterator which may return null
.
- Introduction
- Basic Utilities
- Collections
- Graphs
- Caches
- Functional Idioms
- Concurrency
- Strings
- Networking
- Primitives
- Ranges
- I/O
- Hashing
- EventBus
- Math
- Reflection
- Releases
- Tips
- Glossary
- Mailing List
- Stack Overflow
- Android Overview
- Footprint of JDK/Guava data structures