As a matter of Factory — Part 3 (Method Chaining)

Donald Raab
Oracle Developers
Published in
5 min readFeb 19, 2019

Learn how to add or remove elements from Java collections fluently by leveraging a few lesser known APIs in Eclipse Collections.

Let’s take a look under the hood at some lesser known APIs in Eclipse Collections.

Static Factories

In the first and second installment of the “As a matter of Factory” blog series, I described how to use the methods available on the Lists, Sets, and Maps classes to create and initialize mutable and immutable collections. For example, there are static instances of MutableListFactory and ImmutableListFactory on the Lists class, stored in the public static final variables named mutable and immutable.

MutableList<String> list = 
Lists.mutable.with("1", "2", "3");

ImmutableList<String> list =
Lists.immutable.with("1", "2", "3");

The method with is overloaded on the MutableListFactory class. One version of the with method takes no parameters, and returns an empty MutableList. The other version of the method takes a varargs parameter which will construct a MutableList with the parameters specified.

Fluent Mutable Interfaces

The static factory methods are not the only way you can initialize a List, Set, Bag or Map. There are methods available directly on the mutable interfaces that can be used to mutate collections fluently. Each method will mutate the underlying collection by adding or removing an element and then returning the same collection.

Fluent methods available on Mutable interfaces for mutating collections

Equivalent Methods from JDK interfaces

  • with -> add
  • without -> remove
  • withAll -> addAll
  • withoutAll -> removeAll
  • withKeyValue -> put
  • withoutKey -> removeKey

Fluently building Maps

The with method on MutableListFactory can construct a MutableList with a variable number of items, but the same is not true for MutableMapFactory. The with method on MutableMapFactory is overloaded five times.

<K, V> MutableMap<K, V> with();
<K, V> MutableMap<K, V> with(K key, V value);
<K, V> MutableMap<K, V> with(K key1, V value1, K key2, V value2);
<K, V> MutableMap<K, V> with(K key1, V value1, K key2, V value2, K key3, V value3);
<K, V> MutableMap<K, V> with(K key1, V value1, K key2, V value2, K key3, V value3, K key4, V value4);

You can only use the with method method to create a MutableMap with up to four keys and values.

MutableMap<Integer, String> map =
Maps.mutable.with(
1, "One",
2, "Two",
3, "Three",
4, "Four");

What do you do if you need more than four keys and values?

There is a method named withKeyValue on MutableMap which can be used to fluently add an unlimited number of key and value pairs by leveraging method chaining.

Fluently building a MutableMap using withKeyValue

MutableMap<Integer, String> numbers =
Maps.mutable.<Integer, String>empty()
.withKeyValue(0, "Zero")
.withKeyValue(1, "One")
.withKeyValue(2, "Two")
.withKeyValue(3, "Three")
.withKeyValue(4, "Four")
.withKeyValue(5, "Five")
.withKeyValue(6, "Six")
.withKeyValue(7, "Seven")
.withKeyValue(8, "Eight")
.withKeyValue(9, "Nine")
.withKeyValue(10, "Ten");

Assert.assertEquals("Zero", numbers.get(0));
Assert.assertEquals("One", numbers.get(1));
Assert.assertEquals("Ten", numbers.get(10));

The withKeyValue method

public MutableMap<K, V> withKeyValue(K key, V value)
{
this.put(key, value);
return this;
}

Fluently building an ImmutableMap

ImmutableMap<Integer, String> numbers =
Maps.mutable.<Integer, String>empty()
.withKeyValue(0, "Zero")
.withKeyValue(1, "One")
.withKeyValue(2, "Two")
.withKeyValue(3, "Three")
.withKeyValue(4, "Four")
.withKeyValue(5, "Five")
.withKeyValue(6, "Six")
.withKeyValue(7, "Seven")
.withKeyValue(8, "Eight")
.withKeyValue(9, "Nine")
.withKeyValue(10, "Ten")
.toImmutable();

Assert.assertEquals("Zero", numbers.get(0));
Assert.assertEquals("One", numbers.get(1));
Assert.assertEquals("Ten", numbers.get(10));

Fluently building an Immutable object / primitive Map

ObjectIntMap<String> numbers =
ObjectIntMaps.mutable.<String>empty()
.withKeyValue("Zero", 0)
.withKeyValue("One", 1)
.withKeyValue("Two", 2)
.withKeyValue("Three", 3)
.withKeyValue("Four", 4)
.withKeyValue("Five", 5)
.withKeyValue("Six", 6)
.withKeyValue("Seven", 7)
.withKeyValue("Eight", 8)
.withKeyValue("Nine", 9)
.withKeyValue("Ten", 10)
.toImmutable();

Assert.assertEquals(0, numbers.get("Zero"));
Assert.assertEquals(1, numbers.get("One"));
Assert.assertEquals(10, numbers.get("Ten"));

Fluently building an Immutable primitive / object Map

IntObjectMap<String> numbers =
IntObjectMaps.mutable.<String>empty()
.withKeyValue(0, "Zero")
.withKeyValue(1, "One")
.withKeyValue(2, "Two")
.withKeyValue(3, "Three")
.withKeyValue(4, "Four")
.withKeyValue(5, "Five")
.withKeyValue(6, "Six")
.withKeyValue(7, "Seven")
.withKeyValue(8, "Eight")
.withKeyValue(9, "Nine")
.withKeyValue(10, "Ten")
.toImmutable();

Assert.assertEquals("Zero", numbers.get(0));
Assert.assertEquals("One", numbers.get(1));
Assert.assertEquals("Ten", numbers.get(10));

Fluently building an Immutable primitive / primitive Map

IntCharMap numbers = IntCharMaps.mutable.empty()
.withKeyValue(0, '0')
.withKeyValue(1, '1')
.withKeyValue(2, '2')
.withKeyValue(3, '3')
.withKeyValue(4, '4')
.withKeyValue(5, '5')
.withKeyValue(6, '6')
.withKeyValue(7, '7')
.withKeyValue(8, '8')
.withKeyValue(9, '9')
.toImmutable();

Assert.assertEquals('0', numbers.get(0));
Assert.assertEquals('1', numbers.get(1));
Assert.assertEquals('9', numbers.get(9));

Fluently building Lists, Sets and Bags

Fluently building a Map using method chaining is often more useful than fluently building a List, Set, or Bag. Still the functionality exists if and when you need it.

Fluently building a List

ImmutableList<Integer> numbers =
Lists.mutable.with(1)
.with(2)
.with(3)
.with(4)
.withAll(Interval.fromTo(5, 10))
.toImmutable();

Assert.assertEquals(Interval.oneTo(10), numbers);

Notice that I used two methods to fluently build the MutableList. The method with takes a single argument and the method withAll takes an Iterable.

The with method

public MutableList<T> with(T element)
{
this.add(element);
return this;
}

The withAll method

public MutableList<T> withAll(Iterable<? extends T> elements)
{
this.addAllIterable(elements);
return this;
}

Fluently building a Set

ImmutableSet<Integer> numbers =
Sets.mutable.with(1)
.with(2)
.with(3)
.with(4)
.withAll(Interval.fromTo(5, 10))
.toImmutable();

Assert.assertEquals(Interval.oneTo(10).toSet(), numbers);

Fluently building a Bag

ImmutableBag<Integer> numbers =
Bags.mutable.with(1)
.with(2)
.with(3)
.with(4)
.withAll(Interval.fromTo(5, 10))
.toImmutable();

Assert.assertEquals(Interval.oneTo(10).toBag(), numbers);

Fluently building primitive Lists, Sets and Bags

ImmutableIntList list =
IntLists.mutable.with(1)
.with(2)
.with(3)
.with(4)
.withAll(IntInterval.fromTo(5, 10))
.toImmutable();

Assert.assertEquals(IntInterval.oneTo(10), list);

ImmutableIntSet set =
IntSets.mutable.with(1)
.with(2)
.with(3)
.with(4)
.withAll(IntInterval.fromTo(5, 10))
.toImmutable();

Assert.assertEquals(IntInterval.oneTo(10).toSet(), set);

ImmutableIntBag bag =
IntBags.mutable.with(1)
.with(2)
.with(3)
.with(4)
.withAll(IntInterval.fromTo(5, 10))
.toImmutable();

Assert.assertEquals(IntInterval.oneTo(10).toBag(), bag);

Using withAll in Collectors2

The withAll method is very useful for building Collectors which return collections. The withAll method is used repeatedly as a method reference in the Collectors2 class of Eclipse Collections.

Collectors2.toList()

public static <T> Collector<T, ?, MutableList<T>> toList()
{
return Collector.of(
Lists.mutable::empty,
MutableList::add,
MutableList::withAll,
EMPTY_CHARACTERISTICS);
}

Collectors2.toImmutableList()

public static <T> Collector<T, ?, ImmutableList<T>> toImmutableList()
{
return Collector.<T, MutableList<T>, ImmutableList<T>>of(
Lists.mutable::empty,
MutableList::add,
MutableList::withAll,
MutableList::toImmutable,
EMPTY_CHARACTERISTICS);
}

Collectors2.countBy(Function)

public static <T, K> Collector<T, ?, MutableBag<K>> 
countBy(Function<? super T, ? extends K> function)
{
return Collector.of(
Bags.mutable::empty,
(bag, each) -> bag.with(function.valueOf(each)),
MutableBag::withAll,
EMPTY_CHARACTERISTICS);
}

With or Without

We like to provide good symmetry in Eclipse Collections. If you can fluently add items to a collection, then it would make sense to be be able to remove items from a collection fluently.

The without method on UnifiedSet

public UnifiedSet<T> without(T element)
{
this.remove(element);
return this;
}

Here’s an example combining some of the fluent aspects of the Eclipse Collections factories.

ImmutableSet<String> strings =
Sets.mutable.with("or", "without", "you")
.with("or")
.without("you")
.toImmutable();

Assert.assertEquals(Sets.mutable.with("or", "without"), strings);

Final Thoughts

Hopefully you found the information and examples in this blog useful. This blog was inspired by a recent pull request submitted to Eclipse Collections. There are a lot of hidden gems inside of Eclipse Collections. I like to write about them when it becomes clear they are not so easily discovered.

I am a Project Lead and Committer for the Eclipse Collections OSS project at the Eclipse Foundation. Eclipse Collections is open for contributions. If you like the library, you can let us know by starring it on GitHub.

--

--

Donald Raab
Oracle Developers

Java Champion. Creator of the Eclipse Collections OSS Java library (https://github.com/eclipse/eclipse-collections). Inspired by Smalltalk. Opinions are my own.