Immutable Collections
public static final ImmutableSet<String> COLOR_NAMES = ImmutableSet.of(
"red",
"orange",
"yellow",
"green",
"blue",
"purple");
class Foo {
final ImmutableSet<Bar> bars;
Foo(Set<Bar> bars) {
this.bars = ImmutableSet.copyOf(bars); // defensive copy!
}
}
Why?
Immutable objects have many advantages, including:
- Safe for use by untrusted libraries.
- Thread-safe: can be used by many threads with no risk of race conditions.
- Doesn’t need to support mutation, and can make time and space savings with that assumption. All immutable collection implementations are more memory-efficient than their mutable siblings.
- Can be used as a constant, with the expectation that it will remain fixed.
When you don’t expect to modify a collection, or expect a collection to remain constant, it’s a good practice to defensively copy it into an immutable collection.
How?
An ImmutableXXX
collection can be created in several ways:
- using the copyOf
method.
- using the of
method.
- using a Builder
.
ImmutableSet.copyOf(set);
ImmutableSet.of("a", "b", "c");
ImmutableMap.of("a", 1, "b", 2);
public static final ImmutableSet<Color> GOOGLE_COLORS =
ImmutableSet.<Color>builder()
.addAll(WEBSAFE_COLORS)
.add(new Color(0, 191, 255))
.build();
copyOf
is smarter than you think
ImmutableSet<String> foobar = ImmutableSet.of("foo", "bar", "baz");
thingamajig(foobar);
void thingamajig(Collection<String> collection) {
ImmutableList<String> defensiveCopy = ImmutableList.copyOf(collection);
...
}
In this code, ImmutableList.copyOf(foobar)
will be smart enough to just return foobar.asList()
, which is a constant-time view of the ImmutableSet
asList
All immutable collections provide an ImmutableList
view via asList()
Details
Interface | JDK or Guava? | Immutable Version |
---|---|---|
Collection | JDK | ImmutableCollection |
List | JDK | ImmutableList |
Set | JDK | ImmutableSet |
SortedSet /NavigableSet | JDK | ImmutableSortedSet |
Map | JDK | ImmutableMap |
SortedMap | JDK | ImmutableSortedMap |
Multiset | Guava | ImmutableMultiset |
SortedMultiset | Guava | ImmutableSortedMultiset |
Multimap | Guava | ImmutableMultimap |
ListMultimap | Guava | ImmutableListMultimap |
SetMultimap | Guava | ImmutableSetMultimap |
BiMap | Guava | ImmutableBiMap |
ClassToInstanceMap | Guava | ImmutableClassToInstanceMap |
Table | Guava | ImmutableTable |
New Collection Types
MultiSet
ounting how many times a word occurs in a document is something like:
Map<String, Integer> counts = new HashMap<String, Integer>();
for (String word : words) {
Integer count = counts.get(word);
if (count == null) {
counts.put(word, 1);
} else {
counts.put(word, count + 1);
}
}
MultiSet
have two main ways:
- Like an ArrayList<E>
without an ordering constraint.
- Like a Map<E, Integer>
, with elements and counts.
Guava’s Multiset
API combines both ways of thinking about a Multiset, as follows:
- like an unordered ArrayList
- add(E)
- iterator()
- size()
- like a Map<E, Integer>
- count(Object)
- entrySet()
- elementSet()
Method | Description |
---|---|
count(E) | Count the number of occurrences of an element that have been added to this multiset. |
elementSet() | View the distinct elements of a Multiset<E> as a Set<E> . |
entrySet() | Similar to Map.entrySet() , returns a Set<Multiset.Entry<E>> , containing entries supporting getElement() and getCount() . |
add(E, int) | Adds the specified number of occurrences of the specified element. |
remove(E, int) | Removes the specified number of occurrences of the specified element. |
setCount(E, int) | Sets the occurrence count of the specified element to the specified nonnegative value. |
size() | Returns the total number of occurrences of all elements in the Multiset . |
Multiset Is Not A Map
Note that Multiset<E>
is not a Map<E, Integer>
, though that might be part of a Multiset
implementation.
Implementations
Map | Corresponding Multiset | Supports null elements |
---|---|---|
HashMap | HashMultiset | Yes |
TreeMap | TreeMultiset | Yes |
LinkedHashMap | LinkedHashMultiset | Yes |
ConcurrentHashMap | ConcurrentHashMultiset | No |
ImmutableMap | ImmutableMultiset | No |
SortedMultiset
SortedMultiset
is a variation on the Multiset
interface that supports efficiently taking sub-multisets on specified ranges.
TreeMultiset
implements the SortedMultiset
interface.
Multimap
implemented a Map<K, List<V>>
or Map<K, Set<V>>
In general, the Multimap
interface is best thought of in terms of the first view, but allows you to view it in either way with the asMap()
view, which returns a Map<K, Collection<V>>
.
You rarely use the Multimap
interface directly, however; more often you’ll use ListMultimap
or SetMultimap
, which map keys to a List
or a Set
respectively.
Construction
- using
MultimapBuilder
- use the
create()
methods
// creates a ListMultimap with tree keys and array list values
ListMultimap<String, Integer> treeListMultimap =
MultimapBuilder.treeKeys().arrayListValues().build();
// creates a SetMultimap with hash keys and enum set values
SetMultimap<Integer, MyEnum> hashEnumMultimap =
MultimapBuilder.hashKeys().enumSetValues(MyEnum.class).build();
Modifying
Multimap.get(key)
returns a view of the values associated with the specified key, even if there are none currently. For a ListMultimap
, it returns a List
, for a SetMultimap
, it returns a Set
.
Set<Person> aliceChildren = childrenMultimap.get(alice);
aliceChildren.clear();
aliceChildren.add(bob);
aliceChildren.add(carol);
Signature | Description | Equivalent |
---|---|---|
put(K, V) | Adds an association from the key to the value. | multimap.get(key).add(value) |
putAll(K, Iterable<V>) | Adds associations from the key to each of the values in turn. | Iterables.addAll(multimap.get(key), values) |
remove(K, V) | Removes one association from key to value and returns true if the multimap changed. | multimap.get(key).remove(value) |
removeAll(K) | Removes and returns all the values associated with the specified key. The returned collection may or may not be modifiable, but modifying it will not affect the multimap. (Returns the appropriate collection type.) | multimap.get(key).clear() |
replaceValues(K, Iterable<V>) | Clears all the values associated with key and sets key to be associated with each of values . Returns the values that were previously associated with the key. | multimap.get(key).clear(); Iterables.addAll(multimap.get(key), values) |
Views
Multimap
also supports a number of powerful views:
- asMap
views any Multimap<K, V>
as a Map<K, Collection<V>>
.
- entries
views the Collection<Map.Entry<K, V>>
of all entries in the Multimap
.
- keySet
views the distinct keys in the Multimap
as a Set
.
- keys
views the keys of the Multimap
as a Multiset
.
- values()
views all the values in the Multimap
as a “flattened” Collection<V>
, all as one collection.
Multimap Is Not A Map
A Multimap<K, V>
is not a Map<K, Collection<V>>
, though such a map might be used in a Multimap
implementation. Notable differences include:
- Multimap.get(key)
always returns a non-null, possibly empty collection.
- If you prefer the more Map
-like behavior of returning null
for keys that aren’t in the multimap, use the asMap()
view to get a Map<K, Collection<V>>
.
- Multimap.containsKey(key)
is true if and only if there are any elements associated with the specified key.
- Multimap.entries()
returns all entries for all keys in the Multimap
.
- Multimap.size()
returns the number of entries in the entire multimap, not the number of distinct keys.
Implementations
Implementation | Keys behave like… | Values behave like.. |
---|---|---|
ArrayListMultimap | HashMap | ArrayList |
HashMultimap | HashMap | HashSet |
LinkedListMultimap * | LinkedHashMap``* | LinkedList``* |
LinkedHashMultimap ** | LinkedHashMap | LinkedHashSet |
TreeMultimap | TreeMap | TreeSet |
ImmutableListMultimap | ImmutableMap | ImmutableList |
ImmutableSetMultimap | ImmutableMap | ImmutableSet |
Each of these implementations, except the immutable ones, support null keys and values.
*
LinkedListMultimap.entries()
preserves iteration order across non-distinct key values. See the link for details.
**
LinkedHashMultimap
preserves insertion order of entries, as well as the insertion order of keys, and the set of values associated with any one key.
If you need more customization, use Multimaps.newMultimap(Map, Supplier<Collection>)
or the list
and set
versions to use a custom collection, list, or set implementation to back your multimap.
BiMap
A BiMap<K, V>
is a Map<K, V>
that:
* allows you to view the “inverse” BiMap<V, K>
with inverse()
* ensures that values are unique, making values()
a Set
BiMap<String, Integer> userId = HashBiMap.create();
...
String userForId = userId.inverse().get(id);
Implementations
Key-Value Map Impl | Value-Key Map Impl | Corresponding BiMap |
---|---|---|
HashMap | HashMap | HashBiMap |
ImmutableMap | ImmutableMap | ImmutableBiMap |
EnumMap | EnumMap | EnumBiMap |
EnumMap | HashMap | EnumHashBiMap |
Table
Table<Vertex, Vertex, Double> weightedGraph = HashBasedTable.create();
weightedGraph.put(v1, v2, 4);
weightedGraph.put(v1, v3, 20);
weightedGraph.put(v2, v3, 5);
weightedGraph.row(v1); // returns a Map mapping v2 to 4, v3 to 20
weightedGraph.column(v3); // returns a Map mapping v1 to 20, v2 to 5
Table
supports a number of views to let you use the data from any angle, including:
* rowMap()
, which views a Table<R, C, V>
as a Map<R, Map<C, V>>
. Similarly, rowKeySet()
returns a Set<R>
.
* row(r)
returns a non-null Map<C, V>
. Writes to the Map
will write through to the underlying Table
.
* Analogous column methods are provided: columnMap()
, columnKeySet()
, and column(c)
. (Column-based access is somewhat less efficient than row-based access.)
* cellSet()
returns a view of the Table
as a set of ## ClassToInstanceMap
Table.Cell<R, C, V>
. Cell
is much like Map.Entry
, but distinguishes the row and column keys.
Several Table
implementations are provided, including:
- HashBasedTable
, which is essentially backed by a HashMap<R, HashMap<C, V>>
.
- TreeBasedTable
, which is essentially backed by a TreeMap<R, TreeMap<C, V>>
.
- ImmutableTable
- ArrayTable
, which requires that the complete universe of rows and columns be specified at construction time, but is backed by a two-dimensional array to improve speed and memory efficiency when the table is dense.
ClassToInstanceMap
Sometimes, your map keys aren’t all of the same type: they are types, and you want to map them to values of that type. Guava provides ClassToInstanceMap
for this purpose.
In addition to extending the Map
interface, ClassToInstanceMap
provides the methods T getInstance(Class<T>)
and T putInstance(Class<T>, T)
, which eliminate the need for unpleasant casting while enforcing type safety.
ClassToInstanceMap<Number> numberDefaults = MutableClassToInstanceMap.create();
numberDefaults.putInstance(Integer.class, Integer.valueOf(0));
Guava provides implementations helpfully named MutableClassToInstanceMap
and ImmutableClassToInstanceMap
.
RangeSet
A RangeSet
describes a set of disconnected, nonempty ranges.
RangeSet<Integer> rangeSet = TreeRangeSet.create();
rangeSet.add(Range.closed(1, 10)); // {[1, 10]}
rangeSet.add(Range.closedOpen(11, 15)); // disconnected range: {[1, 10], [11, 15)}
rangeSet.add(Range.closedOpen(15, 20)); // connected range; {[1, 10], [11, 20)}
rangeSet.add(Range.openClosed(0, 0)); // empty range; {[1, 10], [11, 20)}
rangeSet.remove(Range.open(5, 10)); // splits [1, 10]; {[1, 5], [10, 10], [11, 20)}
Views
RangeSet
implementations support an extremely wide range of views, including:
- complement()
:views the complement of the RangeSet
.
- subRangeSet(Range<C>)
: returns a view of the intersection of the RangeSet
with the specified Range
.
- asRanges()
: views the RangeSet
as a Set<Range<C>>
which can be iterated over.
- asSet(DiscreteDomain<C>)
(ImmutableRangeSet
only): Views the RangeSet<C>
as an ImmutableSortedSet<C>
, viewing the elements in the ranges instead of the ranges themselves.
Queries
to operations on its views:
* contains(C)
: the most fundamental operation on a RangeSet
, querying if any range in the RangeSet
contains the specified element.
* rangeContaining(C)
: returns the Range
which encloses the specified element, or null
if there is none.
* encloses(Range<C>)
: straightforwardly enough, tests if any Range
in the RangeSet
encloses the specified range.
* span()
: returns the minimal Range
that encloses
every range in this RangeSet
.
RangeMap
RangeMap
is a collection type describing a mapping from disjoint, nonempty ranges to values.
RangeMap<Integer, String> rangeMap = TreeRangeMap.create();
rangeMap.put(Range.closed(1, 10), "foo"); // {[1, 10] => "foo"}
rangeMap.put(Range.open(3, 6), "bar"); // {[1, 3] => "foo", (3, 6) => "bar", [6, 10] => "foo"}
rangeMap.put(Range.open(10, 20), "foo"); // {[1, 3] => "foo", (3, 6) => "bar", [6, 10] => "foo", (10, 20) => "foo"}
rangeMap.remove(Range.closed(5, 11)); // {[1, 3] => "foo", (3, 5) => "bar", (11, 20) => "foo"}
Views
RangeMap
provides two views:
* asMapOfRanges()
: views the RangeMap
as a Map<Range<K>, V>
.
* subRangeMap(Range<K>)
views the intersection of the RangeMap
with the specified Range
as a RangeMap
.
Collection Utilities
Interface | JDK or Guava? | Corresponding Guava utility class |
---|---|---|
Collection | JDK | Collections2 |
List | JDK | Lists |
Set | JDK | Sets |
SortedSet | JDK | Sets |
Map | JDK | Maps |
SortedMap | JDK | Maps |
Queue | JDK | Queues |
Multiset | Guava | Multisets |
Multimap | Guava | Multimaps |
BiMap | Guava | Maps |
Table | Guava | Tables |
Static constructors
List<TypeThatsTooLongForItsOwnGood> list = Lists.newArrayList();
Map<KeyType, LongishValueType> map = Maps.newLinkedHashMap();
Set<Type> copySet = Sets.newHashSet(elements);
List<String> theseElements = Lists.newArrayList("alpha", "beta", "gamma");
List<Type> exactly100 = Lists.newArrayListWithCapacity(100);
List<Type> approx100 = Lists.newArrayListWithExpectedSize(100);
Set<Type> approx100Set = Sets.newHashSetWithExpectedSize(100);
Multiset<String> multiset = HashMultiset.create();
Iterables
Guava prefers to provide utilities accepting an Iterable
rather than a Collection
.
As of Guava 12, Iterables
is supplemented by the FluentIterable
class, which wraps an Iterable and provides a “fluent” syntax for many of these operations.
General
Method | Description | See Also |
---|---|---|
concat(Iterable<Iterable>) | Returns a lazy view of the concatenation of several iterables. | concat(Iterable...) |
frequency(Iterable, Object) | Returns the number of occurrences of the object. | Compare Collections.frequency(Collection, Object) ; see Multiset |
partition(Iterable, int) | Returns an unmodifiable view of the iterable partitioned into chunks of the specified size. | Lists.partition(List, int) , paddedPartition(Iterable, int) |
getFirst(Iterable, T default) | Returns the first element of the iterable, or the default value if empty. | Compare Iterable.iterator().next() , FluentIterable.first() |
getLast(Iterable) | Returns the last element of the iterable, or fails fast with a NoSuchElementException if it’s empty. | getLast(Iterable, T default) , FluentIterable.last() |
elementsEqual(Iterable, Iterable) | Returns true if the iterables have the same elements in the same order. | Compare List.equals(Object) |
unmodifiableIterable(Iterable) | Returns an unmodifiable view of the iterable. | Compare Collections.unmodifiableCollection(Collection) |
limit(Iterable, int) | Returns an Iterable returning at most the specified number of elements. | FluentIterable.limit(int) |
getOnlyElement(Iterable) | Returns the only element in Iterable . Fails fast if the iterable is empty or has multiple elements. | getOnlyElement(Iterable, T default) |
Iterable<Integer> concatenated = Iterables.concat(
Ints.asList(1, 2, 3),
Ints.asList(4, 5, 6));
// concatenated has elements 1, 2, 3, 4, 5, 6
String lastAdded = Iterables.getLast(myLinkedHashSet);
String theElement = Iterables.getOnlyElement(thisSetIsDefinitelyASingleton);
// if this set isn't a singleton, something is wrong!
Collection-Like
Method | Analogous Collection method | FluentIterable equivalent |
---|---|---|
addAll(Collection addTo, Iterable toAdd) | Collection.addAll(Collection) | |
contains(Iterable, Object) | Collection.contains(Object) | FluentIterable.contains(Object) |
removeAll(Iterable removeFrom, Collection toRemove) | Collection.removeAll(Collection) | |
retainAll(Iterable removeFrom, Collection toRetain) | Collection.retainAll(Collection) | |
size(Iterable) | Collection.size() | FluentIterable.size() |
toArray(Iterable, Class) | Collection.toArray(T[]) | FluentIterable.toArray(Class) |
isEmpty(Iterable) | Collection.isEmpty() | FluentIterable.isEmpty() |
get(Iterable, int) | List.get(int) | FluentIterable.get(int) |
toString(Iterable) | Collection.toString() | FluentIterable.toString() |
FluentIterable
FluentIterable
has a few convenient methods for copying into an immutable collection:
Result Type | Method |
---|---|
ImmutableList | toImmutableList() |
ImmutableSet | toImmutableSet() |
ImmutableSortedSet | toImmutableSortedSet(Comparator) |
Lists
Method | Description |
---|---|
partition(List, int) | Returns a view of the underlying list, partitioned into chunks of the specified size. |
reverse(List) | Returns a reversed view of the specified list. Note: if the list is immutable, consider ImmutableList.reverse() instead. |
List<Integer> countUp = Ints.asList(1, 2, 3, 4, 5);
List<Integer> countDown = Lists.reverse(theList); // {5, 4, 3, 2, 1}
List<List<Integer>> parts = Lists.partition(countUp, 2); // {{1, 2}, {3, 4}, {5}}
Static Factories
Lists
provides the following static factory methods:
Implementation | Factories |
---|---|
ArrayList | basic, with elements, from Iterable , with exact capacity, with expected size, from Iterator |
LinkedList | basic, from Iterable |
Sets
Set-Theoretic Operations
provide a number of standard set-theoretic operations, implemented as views over the argument sets. These return a SetView
, which can be used:
- as a
Set
directly, since it implements theSet
interface - by copying it into another mutable collection with
copyInto(Set)
- by making an immutable copy with
immutableCopy()
Method |
---|
union(Set, Set) |
intersection(Set, Set) |
difference(Set, Set) |
symmetricDifference(Set, Set) |
Set<String> wordsWithPrimeLength = ImmutableSet.of("one", "two", "three", "six", "seven", "eight");
Set<String> primes = ImmutableSet.of("two", "three", "five", "seven");
SetView<String> intersection = Sets.intersection(primes, wordsWithPrimeLength); // contains "two", "three", "seven"
// I can use intersection as a Set directly, but copying it can be more efficient if I use it a lot.
return intersection.immutableCopy();
Other Set Utilities
Method | Description | See Also |
---|---|---|
cartesianProduct(List<Set>) | Returns every possible list that can be obtained by choosing one element from each set. | cartesianProduct(Set...) |
powerSet(Set) | Returns the set of subsets of the specified set. |
Set<String> animals = ImmutableSet.of("gerbil", "hamster");
Set<String> fruits = ImmutableSet.of("apple", "orange", "banana");
Set<List<String>> product = Sets.cartesianProduct(animals, fruits);
// {{"gerbil", "apple"}, {"gerbil", "orange"}, {"gerbil", "banana"},
// {"hamster", "apple"}, {"hamster", "orange"}, {"hamster", "banana"}}
Set<Set<String>> animalSets = Sets.powerSet(animals);
// {{}, {"gerbil"}, {"hamster"}, {"gerbil", "hamster"}}
Static Factories
Sets
provides the following static factory methods:
Implementation | Factories |
---|---|
HashSet | basic, with elements, from Iterable , with expected size, from Iterator |
LinkedHashSet | basic, from Iterable , with expected size |
TreeSet | basic, with Comparator , from Iterable |
Maps
uniqueIndex
Maps.uniqueIndex(Iterable, Function)
addresses the common case of having a bunch of objects that each have some unique attribute, and wanting to be able to look up those objects based on that attribute.
ImmutableMap<Integer, String> stringsByIndex = Maps.uniqueIndex(strings, new Function<String, Integer> () {
public Integer apply(String string) {
return string.length();
}
});
difference
Maps.difference(Map, Map)
allows you to compare all the differences between two maps. It returns a MapDifference
object, which breaks down the Venn diagram into:
Method | Description |
---|---|
entriesInCommon() | The entries which are in both maps, with both matching keys and values. |
entriesDiffering() | The entries with the same keys, but differing values. The values in this map are of type MapDifference.ValueDifference , which lets you look at the left and right values. |
entriesOnlyOnLeft() | Returns the entries whose keys are in the left but not in the right map. |
entriesOnlyOnRight() | Returns the entries whose keys are in the right but not in the left map. |
Map<String, Integer> left = ImmutableMap.of("a", 1, "b", 2, "c", 3);
Map<String, Integer> right = ImmutableMap.of("b", 2, "c", 4, "d", 5);
MapDifference<String, Integer> diff = Maps.difference(left, right);
diff.entriesInCommon(); // {"b" => 2}
diff.entriesDiffering(); // {"c" => (3, 4)}
diff.entriesOnlyOnLeft(); // {"a" => 1}
diff.entriesOnlyOnRight(); // {"d" => 5}
BiMap
utilities
The Guava utilities on BiMap
live in the Maps
class, since a BiMap
is also a Map
.
BiMap utility | Corresponding Map utility |
---|---|
synchronizedBiMap(BiMap) | Collections.synchronizedMap(Map) |
unmodifiableBiMap(BiMap) | Collections.unmodifiableMap(Map) |
Static Factories
Maps
provides the following static factory methods.
Implementation | Factories |
---|---|
HashMap | basic, from Map , with expected size |
LinkedHashMap | basic, from Map |
TreeMap | basic, from Comparator , from SortedMap |
EnumMap | from Class , from Map |
ConcurrentMap | basic |
IdentityHashMap | basic |
Multisets
Multisets
provides a number of operations that take into account element multiplicities in multisets.
Method | Explanation | Difference from Collection method |
---|---|---|
containsOccurrences(Multiset sup, Multiset sub) | Returns true if sub.count(o) <= super.count(o) for all o . | Collection.containsAll ignores counts, and only tests whether elements are contained at all. |
removeOccurrences(Multiset removeFrom, Multiset toRemove) | Removes one occurrence in removeFrom for each occurrence of an element in toRemove . | Collection.removeAll removes all occurences of any element that occurs even once in toRemove . |
retainOccurrences(Multiset removeFrom, Multiset toRetain) | Guarantees that removeFrom.count(o) <= toRetain.count(o) for all o . | Collection.retainAll keeps all occurrences of elements that occur even once in toRetain . |
intersection(Multiset, Multiset) | Returns a view of the intersection of two multisets; a nondestructive alternative to retainOccurrences . | Has no analogue. |
Multiset<String> multiset1 = HashMultiset.create();
multiset1.add("a", 2);
Multiset<String> multiset2 = HashMultiset.create();
multiset2.add("a", 5);
multiset1.containsAll(multiset2); // returns true: all unique elements are contained,
// even though multiset1.count("a") == 2 < multiset2.count("a") == 5
Multisets.containsOccurrences(multiset1, multiset2); // returns false
multiset2.removeOccurrences(multiset1); // multiset2 now contains 3 occurrences of "a"
multiset2.removeAll(multiset1); // removes all occurrences of "a" from multiset2, even though multiset1.count("a") == 2
multiset2.isEmpty(); // returns true
Other utilities in Multisets
include:
Method | Description |
---|---|
copyHighestCountFirst(Multiset) | Returns an immutable copy of the multiset that iterates over elements in descending frequency order. |
unmodifiableMultiset(Multiset) | Returns an unmodifiable view of the multiset. |
unmodifiableSortedMultiset(SortedMultiset) | Returns an unmodifiable view of the sorted multiset. |
Multiset<String> multiset = HashMultiset.create();
multiset.add("a", 3);
multiset.add("b", 5);
multiset.add("c", 1);
ImmutableMultiset<String> highestCountFirst = Multisets.copyHighestCountFirst(multiset);
// highestCountFirst, like its entrySet and elementSet, iterates over the elements in order {"b", "a", "c"}
Multimaps
index
The cousin to Maps.uniqueIndex
, Multimaps.index(Iterable, Function)
answers the case when you want to be able to look up all objects with some particular attribute in common, which is not necessarily unique.
ImmutableSet<String> digits = ImmutableSet.of(
"zero", "one", "two", "three", "four",
"five", "six", "seven", "eight", "nine");
Function<String, Integer> lengthFunction = new Function<String, Integer>() {
public Integer apply(String string) {
return string.length();
}
};
ImmutableListMultimap<Integer, String> digitsByLength = Multimaps.index(digits, lengthFunction);
/*
* digitsByLength maps:
* 3 => {"one", "two", "six"}
* 4 => {"zero", "four", "five", "nine"}
* 5 => {"three", "seven", "eight"}
*/
invertFrom
Since Multimap
can map many keys to one value, and one key to many values, it can be useful to invert a Multimap. Guava provides invertFrom(Multimap toInvert, Multimap dest)
to let you do this, without choosing an implementation for you.
ArrayListMultimap<String, Integer> multimap = ArrayListMultimap.create();
multimap.putAll("b", Ints.asList(2, 4, 6));
multimap.putAll("a", Ints.asList(4, 2, 1));
multimap.putAll("c", Ints.asList(2, 5, 3));
TreeMultimap<Integer, String> inverse = Multimaps.invertFrom(multimap, TreeMultimap.<String, Integer> create());
// note that we choose the implementation, so if we use a TreeMultimap, we get results in order
/*
* inverse maps:
* 1 => {"a"}
* 2 => {"a", "b", "c"}
* 3 => {"c"}
* 4 => {"a", "b"}
* 5 => {"c"}
* 6 => {"b"}
*/
forMap
Need to use a Multimap
method on a Map
? forMap(Map)
views a Map
as a SetMultimap
.
Map<String, Integer> map = ImmutableMap.of("a", 1, "b", 1, "c", 2);
SetMultimap<String, Inte### Wrappersger> multimap = Multimaps.forMap(map);
// multimap maps ["a" => {1}, "b" => {1}, "c" => {2}]
Multimap<Integer, String> inverse = Multimaps.invertFrom(multimap, HashMultimap.<Integer, String> create());
// inverse maps [1 => {"a", "b"}, 2 => {"c"}]
Wrappers
Multimaps
provides the traditional wrapper methods, as well as tools to get custom Multimap
implementations based on Map
and Collection
implementations of your choice.
Multimap type | Unmodifiable | Synchronized | Custom |
---|---|---|---|
Multimap | unmodifiableMultimap | synchronizedMultimap | newMultimap |
ListMultimap | unmodifiableListMultimap | synchronizedListMultimap | newListMultimap |
SetMultimap | unmodifiableSetMultimap | synchronizedSetMultimap | newSetMultimap |
SortedSetMultimap | unmodifiableSortedSetMultimap | synchronizedSortedSetMultimap | newSortedSetMultimap |
Note that the custom Multimap
methods expect a Supplier
argument to generate fresh new collections. Here is an example of writing a ListMultimap
backed by a TreeMap
mapping to LinkedList
.
ListMultimap<String, Integer> myMultimap = Multimaps.newListMultimap(
Maps.<String, Collection<Integer>>newTreeMap(),
new Supplier<LinkedList<Integer>>() {
public LinkedList<Integer> get() {
return Lists.newLinkedList();
}
});
Tables
customTable
Comparable to the Multimaps.newXXXMultimap(Map, Supplier)
utilities, Tables.newCustomTable(Map, Supplier<Map>)
allows you to specify a Table implementation using whatever row or column map you like.
// use LinkedHashMaps instead of HashMaps
Table<String, Character, Integer> table = Tables.newCustomTable(
Maps.<String, Map<Character, Integer>>newLinkedHashMap(),
new Supplier<Map<Character, Integer>> () {
public Map<Character, Integer> get() {
return Maps.newLinkedHashMap();
}
});
transpose
The transpose(Table<R, C, V>)
method allows you to view a Table<R, C, V>
as a Table<C, R, V>
.
Wrappers
using ImmutableTable
instead in most cases.
* unmodifiableTable
* unmodifiableRowSortedTable
Collection Helpers
Forwarding Decorators
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)
.
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
}
}
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 |
PeekingIterator
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()
.
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);
}
AbstractIterator
Implementing your own Iterator
? AbstractIterator
can make your life easier.
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();
}
};
}
AbstractSequentialIterator
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;
}
};