Coding in the small with Google Collections: ImmutableSet
Part 15 in a Series.The Google Collections project has released an impressive new collection of features.
The new release has prompted me to continue this series. My favourite feature of the new release is ImmutableSet. It's a top-level implementation of
Set
that's full of features - this class alone justifies adding the entire .jar
to your application's libraries folder...For static constants
Before
public static final Set<Integer> LUCKY_NUMBERS;
static {
Set<Integer> luckyNumbersMutable = new HashSet<Integer>();
luckyNumbersMutable.add(4);
luckyNumbersMutable.add(8);
luckyNumbersMutable.add(15);
luckyNumbersMutable.add(16);
luckyNumbersMutable.add(23);
luckyNumbersMutable.add(42);
LUCKY_NUMBERS = Collections.unmodifiableSet(luckyNumbersMutable);
}
After:
public static final ImmutableSet<Integer> LUCKY_NUMBERS
= ImmutableSet.of(4, 8, 15, 16, 23, 42);
For defensive copies
Before:
private final Set<Integer> luckyNumbers;
public Hatch(Set<Integer> luckyNumbers) {
this.luckyNumbers = Collections.unmodifiableSet(new HashSet<Integer>(luckyNumbers));
}
public Set<Integer> getLuckyNumbers() {
return luckyNumbers;
}
After:
private final ImmutableSet<Integer> luckyNumbers;
public Foo(Set<Integer> luckyNumbers) {
this.luckyNumbers = ImmutableSet.copyOf(luckyNumbers);
}
public ImmutableSet<Integer> getLuckyNumbers() {
return luckyNumbers;
}
For more defensive copies
ImmutableSet.copyOf
has been heavily optimized when the argument is itself an ImmutableSet. It turns out that this method can no-op if the argument is itself an ImmutableSet
, since copying immutable objects is redundant. The more you use ImmutableSet
, the cheaper it is to do defensive copies!For predictable insertion order
ImmutableSet's elements iterate in the same order that they're added in. That means there's less unpredictable behaviour in your application's business logic. There's no need to worry about tests that pass on one JVM and fail with another because of
HashSet
's unspecified iteration order. ImmutableSet<Integer> numbers = ImmutableSet.of(8, 6, 7, 5, 3, 0, 9);
assertEquals("[8, 6, 7, 5, 3, 0, 9]", numbers.toString());
For self-documenting APIs
Before:
/**
* Returns numbers most likely to be selected in upcoming lottery drawings.
*
* @return an immutable, non-empty set
*/
public Set<Integer> getLuckyNumbers();
After:
/**
* Returns numbers most likely to be selected in upcoming lottery drawings.
*
* @return a non-empty set
*/
public ImmutableSet<Integer> getLuckyNumbers();
For less memory allocation
ImmutableSet allocates fewer objects and smaller objects than either
HashSet
or LinkedHashSet
. This makes your program's heap smaller. There's also less work for your garbage collector.For non-null data
ImmutableSet does not permit null elements. This is almost always what you want, and helps to detect bugs early:
Before:
public Hatch(Set<Integer> numbers) {
for (Integer number : numbers) {
if (number == null) {
throw new NullPointerException();
}
}
this.numbers = Collections.unmodifiableSet(new LinkedHashSet<Integer>(numbers));
}
After:
public Hatch(Set<Integer> numbers) {
this.numbers = ImmutableSet.copyOf(numbers);
}
ImmutableSet is my new default collection, replacing
ArrayList
. It's that good. And as a special treat, when I do need a List, Google Collections also includes ImmutableList.