As a matter of Factory — Part 3 (Method Chaining)
Learn how to add or remove elements from Java collections fluently by leveraging a few 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.
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.