Image for post
Image for post
Photo by Paweł Czerwiński on Unsplash

Introduction

Classification of predefined collectors

Collectors to collections

Regular collectors to collections

List<Integer> list = Stream.of(1, 2, 3)
.collect(toList());
assertThat(list)
.hasSize(3)
.containsOnly(1, 2, 3);
Set<Integer> set = Stream.of(1, 1, 2, 2, 3, 3)
.collect(toSet());
assertThat(set)
.hasSize(3)
.containsOnly(1, 2, 3);
List<Integer> list = Stream.of(1, 2, 3)
.collect(toCollection(ArrayList::new));
assertThat(list)
.hasSize(3)
.containsOnly(1, 2, 3)
.isExactlyInstanceOf(ArrayList.class);

Collectors to unmodifiable collections

List<Integer> unmodifiableList = Stream.of(1, 2, 3)
.collect(toUnmodifiableList());
assertThat(unmodifiableList)
.hasSize(3)
.containsOnly(1, 2, 3);
assertThatThrownBy(unmodifiableList::clear)
.isInstanceOf(UnsupportedOperationException.class);
Set<Integer> unmodifiableSet = Stream.of(1, 1, 2, 2, 3, 3)
.collect(toUnmodifiableSet());
assertThat(unmodifiableSet)
.hasSize(3)
.containsOnly(1, 2, 3);
assertThatThrownBy(unmodifiableSet::clear)
.isInstanceOf(UnsupportedOperationException.class);

Downstream-designed collectors

Analogs of stream intermediate operations

List<Integer> listOfOddNumbers = Stream.of(1, 2, 3)
.collect(filtering(i -> i % 2 != 0, toList()));
assertThat(listOfOddNumbers)
.hasSize(2)
.containsOnly(1, 3);
List<Integer> listOfSquares = Stream.of(1, 2, 3)
.collect(mapping(i -> i * i, toList()));
assertThat(listOfSquares)
.hasSize(3)
.containsOnly(1, 4, 9);
List<Integer> list = Stream.of(
List.of(1, 2),
List.of(3, 4))
.collect(flatMapping(List::stream, toList()));
assertThat(list)
.hasSize(4)
.containsOnly(1, 2, 3, 4);

Analogs of stream terminal operations

double average = Stream.of(1, 2, 3)
.collect(averagingInt(i -> i));
assertThat(average).isEqualTo(2);
long count = Stream.of(1, 2, 3)
.collect(counting());
assertEquals(3L, count);
Optional<Integer> max = Stream.of(1, 2, 3)
.collect(maxBy(Comparator.naturalOrder()));
assertThat(max)
.isNotEmpty()
.hasValue(3);
Optional<Integer> min = Stream.of(1, 2, 3)
.collect(minBy(Comparator.naturalOrder()));
assertThat(min)
.isNotEmpty()
.hasValue(1);
int sum = Stream.of(1, 2, 3)
.collect(summingInt(i -> i));
assertThat(sum).isEqualTo(6);
IntSummaryStatistics iss = Stream.of(1, 2, 3)
.collect(summarizingInt(i -> i));
assertThat(iss.getAverage()).isEqualTo(2);
assertThat(iss.getCount()).isEqualTo(3);
assertThat(iss.getMax()).isEqualTo(3);
assertThat(iss.getMin()).isEqualTo(1);
assertThat(iss.getSum()).isEqualTo(6);

Analogs of stream reduce operations

Optional<Integer> sumOptional = Stream.of(1, 2, 3)
.collect(reducing(Integer::sum));
assertTrue(sumOptional.isPresent());
assertThat(sumOptional.get()).isEqualTo(6);
Integer sum = Stream.of(1, 2, 3)
.collect(reducing(0, Integer::sum));
assertThat(sum).isEqualTo(6);
Integer sumOfSquares = Stream.of(1, 2, 3)
.collect(reducing(0, element -> element * element, Integer::sum));
assertThat(sumOfSquares).isEqualTo(14);

Collectors to maps

“To-map” collectors to maps

Regular collectors to maps

Map<Character, String> map = Stream.of("Alpha", "Bravo", "Charlie")
.collect(toMap(s -> s.charAt(0), Function.identity()));
assertThat(map)
.hasSize(3)
.containsEntry('A', "Alpha")
.containsEntry('B', "Bravo")
.containsEntry('C', "Charlie");
assertThrows(IllegalStateException.class, () -> {
Stream.of(
"Amsterdam", "Baltimore", "Casablanca",
"Alpha", "Bravo", "Charlie")
.collect(toMap(s -> s.charAt(0), Function.identity()));
});
Map<Character, String> map = Stream.of(
"Amsterdam", "Baltimore", "Casablanca",
"Alpha", "Bravo", "Charlie")
.collect(toMap(s -> s.charAt(0), Function.identity(), (v1, v2) -> v2));
assertThat(map)
.hasSize(3)
.containsEntry('A', "Alpha")
.containsEntry('B', "Bravo")
.containsEntry('C', "Charlie");
SortedMap<Character, String> map = Stream.of(
"Amsterdam", "Baltimore", "Casablanca",
"Alpha", "Bravo", "Charlie")
.collect(toMap(s -> s.charAt(0), Function.identity(), (v1, v2) -> v2, TreeMap::new));
assertThat(map)
.hasSize(3)
.containsEntry('A', "Alpha")
.containsEntry('B', "Bravo")
.containsEntry('C', "Charlie")
.isExactlyInstanceOf(TreeMap.class);

Collectors to unmodifiable maps

Map<Character, String> unmodifiableMap = Stream.of("Alpha", "Bravo", "Charlie")
.collect(toUnmodifiableMap(s -> s.charAt(0), Function.identity()));
assertThat(unmodifiableMap)
.hasSize(3)
.containsEntry('A', "Alpha")
.containsEntry('B', "Bravo")
.containsEntry('C', "Charlie");
assertThatThrownBy(unmodifiableMap::clear)
.isInstanceOf(UnsupportedOperationException.class);
assertThrows(IllegalStateException.class, () -> {
Stream.of(
"Amsterdam", "Baltimore", "Casablanca",
"Alpha", "Bravo", "Charlie")
.collect(toUnmodifiableMap(s -> s.charAt(0), Function.identity()));
});
Map<Character, String> unmodifiableMap = Stream.of(
"Amsterdam", "Baltimore", "Casablanca",
"Alpha", "Bravo", "Charlie")
.collect(toUnmodifiableMap(s -> s.charAt(0), Function.identity(), (v1, v2) -> v2));
assertThat(unmodifiableMap)
.hasSize(3)
.containsEntry('A', "Alpha")
.containsEntry('B', "Bravo")
.containsEntry('C', "Charlie");
assertThatThrownBy(unmodifiableMap::clear)
.isInstanceOf(UnsupportedOperationException.class);

Concurrent collectors to maps

ConcurrentMap<Character, String> map = Stream.of("Alpha", "Bravo", "Charlie")
.parallel()
.collect(toConcurrentMap(s -> s.charAt(0), Function.identity()));
assertThat(map)
.hasSize(3)
.containsEntry('A', "Alpha")
.containsEntry('B', "Bravo")
.containsEntry('C', "Charlie");
assertThrows(IllegalStateException.class, () -> {
Stream.of(
"Amsterdam", "Baltimore", "Casablanca",
"Alpha", "Bravo", "Charlie")
.parallel()
.collect(toConcurrentMap(s -> s.charAt(0), Function.identity()));
});
ConcurrentMap<Character, String> map = Stream.of(
"Amsterdam", "Baltimore", "Casablanca",
"Alpha", "Bravo", "Charlie")
.parallel()
.collect(toConcurrentMap(s -> s.charAt(0), Function.identity(), (v1, v2) -> v2));
assertThat(map)
.hasSize(3)
.containsKey('A')
.containsKey('B')
.containsKey('C');
ConcurrentMap<Character, String> map = Stream.of(
"Amsterdam", "Baltimore", "Casablanca",
"Alpha", "Bravo", "Charlie")
.parallel()
.collect(toConcurrentMap(s -> s.charAt(0), Function.identity(), (v1, v2) -> v2, ConcurrentHashMap::new));
assertThat(map)
.hasSize(3)
.containsKey('A')
.containsKey('B')
.containsKey('C')
.isExactlyInstanceOf(ConcurrentHashMap.class);

“Grouping-by” collectors to maps

Grouping collectors to maps

Map<Area, List<City>> citiesPerArea = USA.CITIES.stream()
.collect(groupingBy(City::getArea));
Map<Area, Set<City>> citiesPerArea = USA.CITIES.stream()
.collect(groupingBy(City::getArea, toSet()));
EnumMap<Area, List<City>> citiesPerArea = USA.CITIES.stream()
.collect(groupingBy(City::getArea, () -> new EnumMap<>(Area.class), toList()));

Partitioning collectors to maps

Map<Boolean, List<Integer>> reminderFromDivisionBy2IsZero = Stream.of(1, 2, 3)
.collect(partitioningBy(i -> i % 2 == 0));
assertThat(reminderFromDivisionBy2IsZero)
.hasSize(2)
.containsEntry(false, List.of(1, 3))
.containsEntry(true, List.of(2));
Map<Boolean, Set<Integer>> reminderFromDivisionBy4IsZero = Stream.of(1, 2, 3)
.collect(partitioningBy(i -> i % 4 == 0, toSet()));
assertThat(reminderFromDivisionBy4IsZero)
.hasSize(2)
.containsEntry(false, Set.of(1, 2, 3))
.containsEntry(true, Set.of());

Concurrent grouping collectors to maps

ConcurrentMap<Area, List<City>> citiesPerArea = USA.CITIES
.parallelStream()
.collect(groupingByConcurrent(City::getArea));
ConcurrentMap<Area, Set<City>> citiesPerArea = USA.CITIES
.parallelStream()
.collect(groupingByConcurrent(City::getArea, toSet()));
ConcurrentMap<Area, List<City>> citiesPerArea = USA.CITIES
.parallelStream()
.collect(groupingByConcurrent(City::getArea, ConcurrentHashMap::new, toList()));

Other collectors

List<Integer> unmodifiableList = Stream.of(1, 2, 3)
.collect(collectingAndThen(toList(), Collections::unmodifiableList));
assertThat(unmodifiableList)
.hasSize(3)
.containsOnly(1, 2, 3);
assertThatThrownBy(unmodifiableList::clear)
.isInstanceOf(UnsupportedOperationException.class);
String result = Stream.of(1, 2, 3)
.map(String::valueOf)
.collect(joining());
assertThat(result).isEqualTo("123");
String result = Stream.of(1, 2, 3)
.map(String::valueOf)
.collect(joining(","));
assertThat(result).isEqualTo("1,2,3");
String result = Stream.of(1, 2, 3)
.map(String::valueOf)
.collect(joining(",", "[", "]"));
assertThat(result).isEqualTo("[1,2,3]");
Map.Entry<Optional<Integer>, Optional<Integer>> limits = Stream.of(1, 2, 3)
.collect(
teeing(
minBy(Integer::compareTo),
maxBy(Integer::compareTo),
AbstractMap.SimpleImmutableEntry::new
)
);
assertNotNull(limits);Optional<Integer> minOptional = limits.getKey();
assertThat(minOptional)
.isNotEmpty()
.hasValue(1);
Optional<Integer> maxOptional = limits.getValue();
assertThat(maxOptional)
.isNotEmpty()
.hasValue(3);

Conclusion

Written by

Senior Software Engineer

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