EC by Example: Collectors2

Visualizing Collectors2

Anatomy of a Collector

One of the many great additions to Java 8 was the interface named Collector. A Collector can be used with the collect method on the Stream interface. The collect method will allow you to reduce a Stream to any type you want. Java 8 included a set of stock Collector implementations which are part of the Collectors utility class. Eclipse Collections includes another set of Collector implementations that return Eclipse Collections types. The name of the utility class in Eclipse Collections is Collectors2.

  • supplier → Supplier<A>
  • accumulator → BiConsumer<A, T>
  • combiner → BinaryOperator<A>
  • finisher → Function<A, R>
  • characteristics → Set<Characteristics>Enum(CONCURRENT, UNORDERED, IDENTITY_FINISH)
@Test
public void collector()
{
Collector<String, Set<String>, Set<String>> toCOWASet =
Collector.of(
HashSet::new, // supplier
Set::add, // accumulator
(set1, set2) -> { // combiner
set1.addAll(set2);
return set1;
},
CopyOnWriteArraySet::new); // finisher
List<String> strings = Arrays.asList("a", "b", "c");
Set<String> set =
strings.stream().collect(toCOWASet);
Assert.assertEquals(new HashSet<>(strings), set);
}

Building a reusable Collector

The Collector example above would not be very useful if it needed to be inlined directly in code as it is rather verbose. It would be much more useful if it could handle any type instead of just String. This can be done easily by moving the construction of this Collector to a static method and giving it a name like to CopyOnWriteArraySet.

public static <T> Collector<T, ?, Set<T>> toCopyOnWriteArraySet()
{
return Collector.<T, Set<T>, Set<T>>of(
HashSet::new, // supplier
Set::add, // accumulator
(set1, set2) -> { // combiner
set1.addAll(set2);
return set1;
},
CopyOnWriteArraySet::new, // finisher
Collector.Characteristics.UNORDERED); // characteristics
}

@Test
public void reusableCollector()
{
List<String> strings = Arrays.asList("a", "b", "c");
Set<String> set1 =
strings.stream().collect(toCopyOnWriteArraySet());
Verify.assertInstanceOf(CopyOnWriteArraySet.class, set1);
Assert.assertEquals(new HashSet<>(strings), set1);

List<Integer> integers = Arrays.asList(1, 2, 3);
Set<Integer> set2 =
integers.stream().collect(toCopyOnWriteArraySet());
Verify.assertInstanceOf(CopyOnWriteArraySet.class, set2);
Assert.assertEquals(new HashSet<>(integers), set2);
}

Filtering with Collectors2

In order to filter with Collectors2, you will need three things:

  • A select, reject, or partition Collector
  • A Predicate
  • A target collection Supplier
@Test
public void filtering()
{
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Predicate<Integer> evens = i -> i % 2 == 0;

MutableList<Integer> selectedList = list.stream().collect(
Collectors2.select(evens, Lists.mutable::empty));
MutableSet<Integer> selectedSet = list.stream().collect(
Collectors2.select(evens, Sets.mutable::empty));

MutableList<Integer> rejectedList = list.stream().collect(
Collectors2.reject(evens, Lists.mutable::empty));
MutableSet<Integer> rejectedSet = list.stream().collect(
Collectors2.reject(evens, Sets.mutable::empty));

PartitionList<Integer> partitionList = list.stream().collect(
Collectors2.partition(evens, PartitionFastList::new));
PartitionSet<Integer> partitionSet = list.stream().collect(
Collectors2.partition(evens, PartitionUnifiedSet::new));

Assert.assertEquals(selectedList, partitionList.getSelected());
Assert.assertEquals(rejectedList, partitionList.getRejected());

Assert.assertEquals(selectedSet, partitionSet.getSelected());
Assert.assertEquals(rejectedSet, partitionSet.getRejected());
}

Transforming with Collectors2

There are several methods which provide different transformations using Collectors2. The most basic transformation is available through the collect method. In order to use collect, you will need two things:

  • A Function
  • A target collection Supplier
@Test
public void transforming()
{
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
MutableList<String> strings = list.stream().collect(
Collectors2.collect(Object::toString,
Lists.mutable::empty));

String string = list.stream().collect(Collectors2.makeString());

Assert.assertEquals(string, strings.makeString());

MutableList<Pair<Integer, String>> zipped =
list.stream().collect(Collectors2.zip(strings));

Assert.assertEquals(Tuples.pair(1, "1"), zipped.getFirst());
Assert.assertEquals(Tuples.pair(5, "5"), zipped.getLast());

MutableList<ObjectIntPair<Integer>> zippedWithIndex =
list.stream().collect(Collectors2.zipWithIndex());

Assert.assertEquals(
PrimitiveTuples.pair(Integer.valueOf(1), 0),
zippedWithIndex.getFirst());
Assert.assertEquals(
PrimitiveTuples.pair(Integer.valueOf(5), 4),
zippedWithIndex.getLast());

MutableList<MutableList<Integer>> chunked =
list.stream().collect(Collectors2.chunk(2));

Assert.assertEquals(
Lists.mutable.with(1, 2), chunked.getFirst());
Assert.assertEquals(
Lists.mutable.with(5), chunked.getLast());

MutableList<Integer> flattened = chunked.stream().collect(
Collectors2.flatCollect(e -> e, Lists.mutable::empty));

Assert.assertEquals(list, flattened);
}

Converting with Collectors2

There are two sets of converting Collector implementations available in Collectors2. One set converts to MutableCollection types. The other converts to ImmutableCollection types.

Collectors converting to Mutable Collections

@Test
public void convertingToMutable()
{
List<Integer> source = Arrays.asList(2, 1, 4, 3, 5);
MutableBag<Integer> bag = source.stream().collect(
Collectors2.toBag());
MutableSortedBag<Integer> sortedBag = source.stream().collect(
Collectors2.toSortedBag());
Assert.assertEquals(
Bags.mutable.with(1, 2, 3, 4, 5), bag);
Assert.assertEquals(
SortedBags.mutable.with(1, 2, 3, 4, 5), sortedBag);

MutableSet<Integer> set = source.stream().collect(
Collectors2.toSet());
MutableSortedSet<Integer> sortedSet = source.stream().collect(
Collectors2.toSortedSet());
Assert.assertEquals(
Sets.mutable.with(1, 2, 3, 4, 5), set);
Assert.assertEquals(
SortedSets.mutable.with(1, 2, 3, 4, 5), sortedSet);

MutableList<Integer> list = source.stream().collect(
Collectors2.toList());
MutableList<Integer> sortedList = source.stream().collect(
Collectors2.toSortedList());
Assert.assertEquals(
Lists.mutable.with(2, 1, 4, 3, 5), list);
Assert.assertEquals(
Lists.mutable.with(1, 2, 3, 4, 5), sortedList);

MutableMap<String, Integer> map =
source.stream().limit(4).collect(
Collectors2.toMap(Object::toString, e -> e));
Assert.assertEquals(
Maps.mutable.with("2", 2, "1", 1, "4", 4, "3", 3),
map);

MutableBiMap<String, Integer> biMap =
source.stream().limit(4).collect(
Collectors2.toBiMap(Object::toString, e -> e));
Assert.assertEquals(
BiMaps.mutable.with("2", 2, "1", 1, "4", 4, "3", 3),
biMap);
}

Collectors converting to Immutable Collections

@Test
public void convertingToImmutable()
{
List<Integer> source = Arrays.asList(2, 1, 4, 3, 5);
ImmutableBag<Integer> bag = source.stream().collect(
Collectors2.toImmutableBag());
ImmutableSortedBag<Integer> sortedBag = source.stream().collect(
Collectors2.toImmutableSortedBag());
Assert.assertEquals(
Bags.immutable.with(1, 2, 3, 4, 5), bag);
Assert.assertEquals(
SortedBags.immutable.with(1, 2, 3, 4, 5), sortedBag);

ImmutableSet<Integer> set = source.stream().collect(
Collectors2.toImmutableSet());
ImmutableSortedSet<Integer> sortedSet = source.stream().collect(
Collectors2.toImmutableSortedSet());
Assert.assertEquals(
Sets.immutable.with(1, 2, 3, 4, 5), set);
Assert.assertEquals(
SortedSets.immutable.with(1, 2, 3, 4, 5), sortedSet);

ImmutableList<Integer> list = source.stream().collect(
Collectors2.toImmutableList());
ImmutableList<Integer> sortedList = source.stream().collect(
Collectors2.toImmutableSortedList());
Assert.assertEquals(
Lists.immutable.with(2, 1, 4, 3, 5), list);
Assert.assertEquals(
Lists.immutable.with(1, 2, 3, 4, 5), sortedList);

ImmutableMap<String, Integer> map =
source.stream().limit(4).collect(
Collectors2.toImmutableMap(
Object::toString, e -> e));
Assert.assertEquals(
Maps.immutable.with("2", 2, "1", 1, "4", 4, "3", 3),
map);

ImmutableBiMap<String, Integer> biMap =
source.stream().limit(4).collect(
Collectors2.toImmutableBiMap(
Object::toString, e -> e));
Assert.assertEquals(
BiMaps.immutable.with("2", 2, "1", 1, "4", 4, "3", 3),
biMap);
}
public static <T> Collector<T, ?, ImmutableList<T>> toImmutableList()
{
return Collector.<T, MutableList<T>, ImmutableList<T>>of(
Lists.mutable::empty, // supplier
MutableList::add, // accumulator
MutableList::withAll, // combiner
MutableList::toImmutable, // finisher
EMPTY_CHARACTERISTICS); // characteristics
}

Eclipse Collections API vs. Collectors2

My preference is always to use the Eclipse Collections API directly if I can. If I need to operate on a JDK Collection type or if I am only given a Stream, then I will use Collectors2. As you can see in the examples above, Collectors2 is a natural gateway to the Eclipse Collections types and their functional, fluent, friendly and fun APIs.

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Donald Raab

Donald Raab

Java Champion. Creator of the Eclipse Collections OSS Java library (http://www.eclipse.org/collections/). Inspired by Smalltalk. Opinions are my own.