Warm-up

Break toMap three ways

10 minjunior18 yrs

One concept, guided. Near-impossible to fail.

What this teaches

  • toMap throws IllegalStateException on a duplicate key
  • toMap throws NullPointerException on a null value, unlike the map it builds
  • groupingBy cannot have either problem, because its value is a collection
  • The three-argument form makes the collision rule explicit

Starter

Starter.java
import java.util.*;
import java.util.stream.*;

/**
 * Warm-up: make toMap fail on purpose, twice.
 *
 * toMap and groupingBy build a similar map from a stream and fail completely
 * differently. Knowing the two failure modes by heart is the whole of this
 * question in an interview, and the fastest way to know them is to cause them.
 */
public class Starter {

    record Employee(String name, String dept, Integer bonus) { }

    static final List<Employee> STAFF = List.of(
        new Employee("Ana", "eng", 500),
        new Employee("Bo", "eng", null),
        new Employee("Cy", "sales", 250));

    public static void main(String[] args) {

        // TODO 1: predict what this does. Two employees, one department.
        try {
            Map<String, String> byDept = STAFF.stream()
                .collect(Collectors.toMap(Employee::dept, Employee::name));
            System.out.println("byDept : " + new TreeMap<>(byDept));
        } catch (RuntimeException e) {
            System.out.println("byDept : " + e.getClass().getSimpleName() + " — " + e.getMessage());
        }

        // TODO 2: fix it with the three-argument toMap. Write the merge
        // function two ways — keep the first, and combine both — and say which
        // one the business would actually want here.

        // TODO 3: predict this one. Bo's bonus is null, and a HashMap would
        // accept that happily.
        try {
            Map<String, Integer> bonuses = STAFF.stream()
                .collect(Collectors.toMap(Employee::name, Employee::bonus));
            System.out.println("bonuses: " + new TreeMap<>(bonuses));
        } catch (RuntimeException e) {
            System.out.println("bonuses: " + e.getClass().getSimpleName());
        }

        // TODO 4: prove to yourself that a plain HashMap.put accepts the same
        // null. Then explain the difference — the answer is one method name.

        // TODO 5: rewrite the first one as groupingBy so it cannot fail, and
        // say in one sentence why the failure mode does not exist there.

        // TODO 6: groupingBy(Employee::dept) gives you Map<String,
        // List<Employee>>. Use a downstream collector to make it
        // Map<String, Long> of headcount instead, without a second pass.
    }
}

Run it locally:

cd exercises/java/java8/collectors-and-grouping/01-warmup
javac Starter.java -d /tmp/out && java -cp /tmp/out Starter

Done when

  • You produced both exceptions deliberately and read the messages
  • You fixed the duplicate with a merge function and said what it chose
  • You can explain why toMap rejects a null that HashMap.put accepts
  • You rewrote one of the three as groupingBy and said why it cannot fail

← Back to How do Collectors.groupingBy and toMap differ in failure modes?