Skip to content
Lesson 6 of 7

Step 1 of 7 · Reading · ~3 min

Learn

Generics, Streams, and Functional Java

Streams API

In Java Intermediate you wrote source, filter, map and terminal pipelines, and counted with groupingBy(..., counting()). This lesson is about collectors: describing the whole result of a pipeline, grouped and summarised, as one value, and about the traps in ordering, laziness and parallelism. You will be able to turn a list of records into a sorted report without a single mutable variable.

Downstream collectors

groupingBy takes the classifier and, as a second argument, a downstream collector applied to each group. Three-argument form lets you pick the map type:

Map<String, Integer> cents = orders.stream().collect(
    Collectors.groupingBy(o -> o.city, TreeMap::new, Collectors.summingInt(o -> o.cents)));

TreeMap::new matters: the default is a HashMap, whose iteration order is unspecified, so printing it gives output that depends on hash codes. Useful downstream collectors:

CollectorResult per group
counting()Long
summingInt(f) / averagingInt(f)Integer / Double
summarizingInt(f)IntSummaryStatistics: count, sum (a long), min, max, average
mapping(f, downstream)apply f, then collect with downstream
maxBy(cmp) / minBy(cmp)Optional<T>
joining(", ")String (on a stream of strings)

summingInt returns an int and wraps silently past 2,147,483,647. summarizingInt keeps a long sum.

Two results from one pass: teeing

Collectors.teeing (Java 12) feeds every element to two collectors and merges their results, so you traverse the data once:

String span = orders.stream().collect(Collectors.teeing(
    Collectors.minBy(Comparator.comparingInt((Order o) -> o.cents)),
    Collectors.maxBy(Comparator.comparingInt((Order o) -> o.cents)),
    (lo, hi) -> lo.get().cents + ".." + hi.get().cents));

maxBy and minBy return an Optional because the stream may be empty. Ties go to whichever comparator order you give: maxBy picks the element the comparator ranks greatest, and when two compare equal it keeps the first one seen. To break ties yourself, chain thenComparing; Comparator.reverseOrder() on the second key makes the alphabetically smaller name rank higher.

Flattening and merging

flatMap replaces each element by a stream and concatenates them. toMap throws IllegalStateException on a duplicate key unless you pass a merge function:

Map<String, Integer> letters = Stream.of("to", "be", "or", "not")
    .flatMap(w -> w.chars().mapToObj(c -> String.valueOf((char) c)))
    .collect(Collectors.toMap(ch -> ch, ch -> 1, Integer::sum, TreeMap::new));

Laziness and infinite sources

Intermediate operations do nothing until a terminal operation pulls elements through, one at a time, and short-circuiting operations stop early. That is what makes infinite streams usable:

Stream.iterate(1, x -> x * 3).takeWhile(x -> x < 500).forEach(System.out::println);   // 1 3 9 27 81 243

takeWhile stops at the first failure; filter would keep testing forever. A stream can be consumed once; reusing it throws IllegalStateException.

Parallel streams

parallel() splits the work over the common ForkJoinPool. It is correct only when the lambdas are stateless, do not touch shared variables, and reductions are associative. forEach on a parallel stream has no order; forEachOrdered does, at a cost. Appending to a shared ArrayList from a parallel forEach loses elements. Measure before using it: small inputs run slower.

Common mistakes

  • Printing a HashMap from groupingBy. The order differs from the sorted order the task asks for; pass TreeMap::new.
  • summingInt on large values. The total wraps to a negative number.
  • Dividing two ints for an average. total / count truncates before it reaches the formatter.
  • Resolving a tie by accident. maxBy with a comparator that ignores the tie-break key returns the first seen, which depends on input order.

Your exercise

Build a per-department report from a list of employees in a single collect call. The parsing and printing are in the starter. The likely mistake is the tie rule for the top earner.

Up nextFunctional Interfaces and LambdasGenerics, Streams, and Functional Java

Discussion

Ask a question, share an insight, or help someone who’s stuck.

Sign in to post a comment or reply.

Loading…