原文地址:http://users.mafr.de/~matthias/articles/google-collections.html
Google Collections is a library complementing Java’s Collections Framework. It adds new collection types not present in the JDK and provides lots of static factory methods and utilities that simplify Java development, making the code more concise and readable. In contrast to commons-collections, Google Collections is targeted at JDK 1.5 and makes full use of generics.
After about four years of development, Google Collections has recently reached version 1.0 and is already used extensively in Google’s products. The library is released under the Apache License 2.0.
This article gives a quick introduction with lots of links to the Javadocs where you should find the answers to all of your questions. If have a bit more time you can watch the Google Tech Talk videos (slides, part 1, part 2, 94 minutes total). Some examples are outdated now, but it’s still a great introduction.
Table of Contents
Add the following dependency to your POM:
1 2 3 4 5 |
<dependency>
<groupId>com.google.collections</groupId>
<artifactId>google-collections</artifactId>
<version>1.0</version>
</dependency>
|
Google Collections offers specialized immutable collections (ImmutableCollection et al) that are safe, high performance, and easy to use. To prevent subclassing (which could circumvent immutability), there are no public constructors, so all collection instances are created via static factory methods.
Unlike the Collections.unmodifiableList() etc. wrappers, there is no chance that they are modified later, making them very useful for defensive copying:
1 2 3 4 5 6 7 |
class Foo {
private Set<String> data;
public Foo(Set<String> data) {
this.data = ImmutableSet.copyOf(data);
}
}
|
Note that the copyOf() methods will return the same collection if they get an immutable collection. None of these collections accept null values, which is what you want in most cases.
The of() methods are implemented using overloading and are very useful for test cases:
1 |
Map<String, Integer> testMap = ImmutableMap.of("foo", 5, "bar", 6);
|
This only works for up to five mappings. The Builder static inner classes are useful if you need more mappings (or higher flexibility):
1 2 3 4 5 6 |
Map<String, Integer> testMap
= new ImmutableMap.Builder<String, Integer>()
.putAll(otherMap)
.put("foo", 5)
.put("bar", 6)
.build();
|
You can return ImmutableMap and friends from a method which makes the contract explicit: Your called will definitely be getting an immutable collection.
A Multiset works similar to a Set, but allows for duplicates (and counts them!). If you find yourself creating a Map<K, Integer> where the values act as counters then you have the perfect use case for a Multiset. One example is creating histograms (perhaps for tags used on a blog):
1 2 3 4 5 6 7 8 9 10 11 |
Multiset<String> tags = HashMultiset.create();
for (BlogPost post : getAllBlogPosts()) {
tags.addAll(post.getTags());
}
tags.elementSet(); // distinct elements
tags.count("java"); // occurrences for element "java"
tags.size(); // total element occurrences, *not* distinct elements!
tags.entrySet(); // returns element, count tuples
// etc.
|
There’s also a ConcurrentMultiset, a LinkedHashMultiset for predictable iteration order, etc.
A Multimap is a data structure that works like a Map, but allows for multiple values per key. Instead of abusing Maps like Map<K, List<V>>, Map<K, Set<V>>, or Map<K, SortedSet<V>>, you could just use a Multimap. A common use case is for URL parameters (one parameter may occur multiple times):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
// instead of this
Map<String, List<String> paramsOld = new HashMap<String, List<String>();
// use this
Multimap<String, String> params = ArrayListMultimap.create();
params.put("foo", "bar");
params.put("foo", "baz");
// and back again, sort of
Map<String, Collection<String>> old = params.asMap();
// many powerful views, like this one
params.values();
|
If values have to be unique per key, there’s also SetMultimap and SortedSetMultimap.
A BiMap is a bidirectional map, where both keys and values have to be unique. You can invert it to create a new BiMap:
1 2 |
BiMap<String, Integer> map = HashBiMap.create(ImmutableMap.of("one", 1, "two", 2);
BiMap<Integer, String> map2 = biMap.invert();
|
For all collection classes, there are proxy classes that simplify creating decorators around them.
A builder for creating all kinds of Maps (this is harder than it sounds). From the JavaDocs:
1 2 3 4 5 6 7 8 9 10 11 |
ConcurrentMap<Key, Graph> graphs = new MapMaker()
.concurrencyLevel(32)
.softKeys()
.weakValues()
.expiration(30, TimeUnit.MINUTES)
.makeComputingMap(
new Function<Key, Graph>() {
public Graph apply(Key key) {
return createExpensiveGraph(key);
}
});
|
Like the JDK’s Comparator (which is easy to implement), there’s Ordering which is much more powerful:
1 2 3 4 5 6 |
Ordering<String> o = Ordering.from(someComparator);
// or implement it directly
o.min(iterable);
o.max(iterable);
o.isOrdered(iterable);
|
We can even create compound instances that fall back to a secondary Ordering in case two items are equal according to the primary Ordering.
Iterators/Iterables are accepted throughout the library. This is very useful for data sets that don’t fit into memory. All iterator implementations are lazy:
1 |
Set<String> data = Sets.newHashSet(someIterable);
|
You can chain multiple iterators together as well as filter() and transform() iterables. This doesn’t turn Java into a functional programming language though but it helps creating pipes and filters architectures.
The following is useful for test cases:
1 2 |
// throws an exception if there's more or less than one element!
Iterables.getOnlyElement(iterable);
|
Checking preconditions is good style, especially in public methods. The Preconditions class contains several handy checks that are very concise when used via static imports:
1 2 3 4 5 6 |
public void doSomething(String foo, int bar) {
checkNotNull(foo, "foo");
checkArgument(bar > 0, "%s must be positive", bar);
// more
}
|
Since the checkNotNull() method returns the argument it checks, it’s useful in assignments, too.
The Objects class helps in creating slightly better looking equals/hashCode methods than the ones typically generated by IDEs:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
public String equals(Object other) {
if (other instanceof MyClass) {
MyClass that = (MyClass) other;
return Objects.equal(foo, that.foo)
&& Objects.equal(bar, that.bar)
&& Objects.equal(baz, that.baz);
} else {
return false;
}
}
public int hashCode() {
return Objects.hashCode(foo, bar, baz);
}
|
Well, at least hashCode() looks better.
Finally, there’s a simple way of joining things together:
1 2 3 |
Joiner.on(", ").join(iterable);
Joiner.on(", ").skipNulls().join(iterable);
Joiner.on(", ").useForNull("<null>").join(iterable);
|
The first line throws a NullPointerException in case the iterable returns a null element. Apart from returning strings, Joiner can also append to all kinds of Appendable things.
There’s also a Joiner.MapJoiner for Map instances:
1 |
Joiner.on(", ").withKeyValueSeparator("=").join(iterable);
|
Java doesn’t have type inference for constructors. That’s why there are static factory methods that save some typing:
1 2 3 |
List<String> foo = Lists.newArrayList();
Set<String> bar = Set.newHashSet();
Map<String, Integer> baz = Maps.newHashMap();
|
There are union, intersection, and difference methods in Sets and Maps, that are easier to understand than their JDK counterparts:
1 |
Sets.intersection(set1, set2);
|
This returns an unmodifyable view.
Matthias Friedrich <matt@mafr.de> |
2010-01-10 (last updated 2010-01-16) |