PUBLIC OBJECT

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();`</pre>

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.