Atom Feed SITE FEED   ADD TO GOOGLE READER

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.
Should your very first example use LinkedHashSet, to make it behave similarly to ImmutableSet?

Also in the first example, I typically use Arrays.asList(...) rather than calling add(...) six times in a row. (although ImmutableSet is still FAR nicer!)
This post has been removed by the author.
Yup, the first example should use LinkedHashSet. And Arrays.asList() is a nice workaround, since it removes the need for the static initializer block. The before code is improved, but yeah, it's still pretty weak:

Before
public static finalSet<Integer> LUCKY_NUMBERS
= Collections.unmodifiableSet(new LinkedHashSet<Integer>(Arrays.asList(4, 8, 15, 16, 23, 42)));