Skip to content

Lesson 03 · Reduction & Collectors

Objectives

After this lesson you will be able to:

  • Reduce a stream with reduce and the identity/accumulator forms.
  • Collect into lists, sets, and maps with Collectors.
  • Group and partition with downstream collectors.

reduce

reduce folds a stream to a single value. The common forms:

java
Optional<Integer> sum = Stream.of(1,2,3).reduce(Integer::sum);     // Optional[6]
int sum2 = Stream.of(1,2,3).reduce(0, Integer::sum);               // 6 (identity → no Optional)
String joined = Stream.of("a","b").reduce("", String::concat);     // "ab"

Gotcha

With an identity the result is the element type (no Optional); without one it returns Optional (empty stream → empty). The identity must be a true neutral element (0 for sum, 1 for product, "" for concat), or results are wrong.

collect and Collectors

collect accumulates into a container using a Collector:

java
List<String> list = stream.collect(Collectors.toList());   // or stream.toList() (immutable)
Set<String> set   = stream.collect(Collectors.toSet());
String csv = stream.collect(Collectors.joining(", ", "[", "]"));
Map<String,Integer> m = stream.collect(Collectors.toMap(String::toUpperCase, String::length));

Exam trap

Collectors.toMap throws IllegalStateException on a duplicate key unless you pass a merge function: toMap(k, v, (a, b) -> a). Also, Stream.toList() (Java 16+) returns an unmodifiable list, whereas Collectors.toList() makes no guarantee about mutability.

Grouping and partitioning

java
Map<Integer,List<String>> byLen =
    stream.collect(Collectors.groupingBy(String::length));

Map<Boolean,List<Integer>> parts =
    nums.collect(Collectors.partitioningBy(n -> n % 2 == 0));   // keys: true & false

Downstream collectors transform each group:

java
Map<Integer,Long> countByLen =
    stream.collect(Collectors.groupingBy(String::length, Collectors.counting()));

Map<Dept,Double> avgSalary =
    emps.collect(Collectors.groupingBy(Employee::dept,
                 Collectors.averagingDouble(Employee::salary)));

SDET note

partitioningBy always yields both true and false keys (even if a side is empty), while groupingBy only has keys that occur — a subtle difference when asserting map contents in a test.

Key Takeaways

  • reduce(identity, op) returns the element type; reduce(op) returns an Optional. The identity must be a real neutral element.
  • collect with Collectors: toList/toSet/joining/toMap. toMap needs a merge function for duplicate keys or it throws.
  • Stream.toList() is unmodifiable; Collectors.toList() is unspecified.
  • groupingBy (keys that occur) and partitioningBy (always true+false) accept downstream collectors like counting, mapping, averagingDouble.

Lesson Quiz

Lesson Quiz · Reduction & Collectors0 / 5
  1. What does Stream.of(1,2,3).reduce(Integer::sum) return?

    • A6
    • BOptional[6]
    • COptional.empty
    • DCompile error
  2. What does toMap do on a duplicate key with no merge function?

    • AKeeps the first
    • BKeeps the last
    • CIllegalStateException
    • DReturns null
  3. Which keys does partitioningBy(p) always produce?

    • AOnly matching keys
    • Btrue and false
    • COnly true
    • D0 and 1
  4. How do you count strings per length?

    • AgroupingBy(String::length)
    • BgroupingBy(String::length, counting())
    • CpartitioningBy(String::length)
    • DtoMap(String::length, counting())
  5. What is the result type of list.stream().toList()?

    • AA mutable ArrayList
    • BAn unmodifiable List
    • CA Set
    • DAn Optional

Next: Optional & Primitive Streams. Run the matching code in labs/src/main/java/com/jse21/m06_streams/.