Java 8 Collectors.toMap Tutorial with Examples
Introduction
This tutorial explains Collectors.toMap(), the Java 8 collector that turns a stream into aMap. We will start with how toMap() builds a map from a key extractor and a value extractor, and its three overloaded versions. We will then run an example covering the basic form, Function.identity(), what happens with duplicate keys, merge functions for combining values, choosing a TreeMap or LinkedHashMap, and the null value trap. Lastly, we will compare toMap() with groupingBy() and look at toConcurrentMap().(This tutorial is part of the Java 8 Collectors series. For the concept of a collector, see the Collector basics tutorialClick to read: Collector basics tutorial.)
How toMap() builds a map
For each element of the stream,toMap() calls a key mapper to get the map key and a value mapper to get the value, both FunctionsClick to read: Functions, and puts the pair into the map. The interesting question is what happens when a second element produces a key that is already in the map.
The three toMap() methods
Definition of thetoMap() overloads in java.util.stream.Collectors -toMap(Function<? super T,? extends K> keyMapper, Function<? super T,? extends U> valueMapper)
AND
toMap(keyMapper, valueMapper, BinaryOperator<U> mergeFunction)
AND
toMap(keyMapper, valueMapper, mergeFunction, Supplier<M> mapSupplier)keyMapperandvalueMapperproduce the key and the value from each element.mergeFunctionis a BinaryOperatorClick to read tutorial: Java 8 UnaryOperator and BinaryOperator Tutorial called with the existing value and the new value whenever a key repeats; its result is stored. Without it, a repeated key throws anIllegalStateException.mapSuppliercreates the map to fill, such asTreeMap::new. Without it, the result is aHashMap, whose iteration order is not defined.
Java 8 code example of Collectors.toMap()
The examples use a smallEmployee class with an id, a name, a department and a salary. Its toString() returns the name, to keep the output short.
Employee.java
//Employee.java
public class Employee {
private final String id;
private final String name;
private final String department;
private final double salary;
public Employee(String id, String name, String department, double salary) {
this.id = id;
this.name = name;
this.department = department;
this.salary = salary;
}
public String getId() { return id; }
public String getName() { return name; }
public String getDepartment() { return department; }
public double getSalary() { return salary; }
@Override
public String toString() { return name; }
}
Java 8 code showing Collectors.toMap with merge functions and map suppliers
//ToMapExamples.java
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.LinkedHashMap;
import java.util.TreeMap;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.stream.Collectors;
public class ToMapExamples {
public static void main(String[] args) {
List<Employee> staff = Arrays.asList(
new Employee("E-1003", "Ethan Hardy", "Sales", 6300),
new Employee("E-1001", "Tom Jones", "Finance", 5200),
new Employee("E-1004", "Nancy Smith", "Sales", 5800),
new Employee("E-1002", "Harry Major", "Finance", 4100),
new Employee("E-1005", "Deborah Sprightly", "IT", 7000));
// 1. Key and value extractors
Map<String, String> namesById = staff.stream()
.collect(Collectors.toMap(Employee::getId, Employee::getName));
System.out.println("1. names by id: " + namesById);
// 2. Function.identity() keeps the whole object as the value
Map<String, Employee> byId = staff.stream()
.collect(Collectors.toMap(Employee::getId, Function.identity()));
System.out.println("2. E-1004 is " + byId.get("E-1004").getName());
// 3. Duplicate keys throw an IllegalStateException...
try {
staff.stream()
.collect(Collectors.toMap(Employee::getDepartment, Employee::getName));
} catch (IllegalStateException e) {
System.out.println("3. duplicate department key -> IllegalStateException");
}
// 4. ...unless a merge function says how to combine the values
Map<String, Double> payrollByDept = staff.stream()
.collect(Collectors.toMap(Employee::getDepartment, Employee::getSalary,
Double::sum));
System.out.println("4. payroll by department: " + payrollByDept);
Map<String, Employee> topEarnerByDept = staff.stream()
.collect(Collectors.toMap(Employee::getDepartment, Function.identity(),
BinaryOperator.maxBy(Comparator.comparingDouble(Employee::getSalary))));
System.out.println(" top earner by department: " + topEarnerByDept);
// 5. A map supplier chooses the Map implementation
TreeMap<String, Double> sortedById = staff.stream()
.collect(Collectors.toMap(Employee::getId, Employee::getSalary,
(a, b) -> a, TreeMap::new));
System.out.println("5. TreeMap, sorted by key: " + sortedById);
LinkedHashMap<String, Double> inStreamOrder = staff.stream()
.collect(Collectors.toMap(Employee::getName, Employee::getSalary,
(a, b) -> a, LinkedHashMap::new));
System.out.println(" LinkedHashMap, stream order: " + inStreamOrder.keySet());
// 6. A null value throws a NullPointerException
try {
Arrays.asList("Tom Jones", "Harry Major").stream()
.collect(Collectors.toMap(Function.identity(), name -> (String) null));
} catch (NullPointerException e) {
System.out.println("6. null value -> NullPointerException");
}
}
}
1. names by id: {E-1005=Deborah Sprightly, E-1004=Nancy Smith, E-1003=Ethan Hardy, E-1002=Harry Major, E-1001=Tom Jones}
2. E-1004 is Nancy Smith
3. duplicate department key -> IllegalStateException
4. payroll by department: {Sales=12100.0, Finance=9300.0, IT=7000.0}
top earner by department: {Sales=Ethan Hardy, Finance=Tom Jones, IT=Deborah Sprightly}
5. TreeMap, sorted by key: {E-1001=5200.0, E-1002=4100.0, E-1003=6300.0, E-1004=5800.0, E-1005=7000.0}
LinkedHashMap, stream order: [Ethan Hardy, Tom Jones, Nancy Smith, Harry Major, Deborah Sprightly]
6. null value -> NullPointerException
Explanation of the code
- Part 1: the ids are unique, so the two-argument
toMap()works. The result is aHashMap, so the order of the printed entries is not the order of the stream - and may differ between Java versions. - Part 2:
Function.identity()is a function that returns its argument, so each employee becomes the value under its own id - a quick way to build a lookup table. - Part 3: two employees work in Sales and two in Finance, so using the department as the key throws
IllegalStateException. In Java 8 the message wrongly shows a value instead of the duplicate key; Java 9 fixed it to name the key and both values. - Part 4: with
Double::sumas the merge function, salaries in the same department are added, giving the payroll per department. The second map keeps the better-paid employee per department, usingBinaryOperator.maxBy()with a ComparatorClick to read: Comparator. - Part 5: the four-argument form fills a
TreeMap, sorted by key, or aLinkedHashMap, which keeps the order in which keys were first seen in the stream. A merge function must be given even when keys are unique;(a, b) -> ais the usual choice. - Part 6:
toMap()rejectsnullvalues with aNullPointerException, because it is built onMap.merge(), which does not accept null. Filter out nulls first, or wrap values in an OptionalClick to read: Optional.
toMap() or groupingBy()?
Both build a map, but they answer different questions -toMap()gives one value per key. Use it when keys are unique, or when you can say how to combine two values into one.groupingBy()gives a collection of elements per key, such asMap<String, List<Employee>>, and can then summarise each group with a downstream collector - see the groupingBy() tutorialClick to read: groupingBy() tutorial.- Part 4's payroll could also be written as
groupingBy(Employee::getDepartment, summingDouble(Employee::getSalary)). For sums and counts,groupingBy()usually reads better; for "one item per key" lookups and custom merges,toMap()does.
toConcurrentMap() for parallel streams
Collectors.toConcurrentMap() has the same three overloads, but collects into a ConcurrentHashMap (or the ConcurrentMap from the supplier). On a parallel streamClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples, all threads then insert into one shared concurrent map instead of building partial maps and merging them, which can be faster for large streams. It is an unordered collector, so the merge function may see values in any order.
Summary
In this tutorial we first saw howCollectors.toMap() turns each element into a key and a value, and its three overloads. Next we ran an example building a map by id, a lookup table with Function.identity(), the IllegalStateException for duplicate keys, merge functions that add salaries or keep the top earner, TreeMap and LinkedHashMap results, and the NullPointerException for null values. Lastly, we compared toMap() with groupingBy() and looked at toConcurrentMap().Java 8 Tutorials
Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial Java 8 CompletableFuture Tutorial with ExamplesClick to read tutorial: Java 8 CompletableFuture Tutorial with Examples Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Map merge, putIfAbsent and forEach with ExamplesClick to read tutorial: Java 8 Map merge, putIfAbsent and forEach with Examples Java 8 - How to Sort a Map by Key or ValueClick to read tutorial: Java 8 - How to Sort a Map by Key or Value Java 8 BiFunction, BiConsumer and BiPredicate TutorialClick to read tutorial: Java 8 BiFunction, BiConsumer and BiPredicate Tutorial
Java 8 Parallel Streams Tutorial with ExamplesClick to read tutorial: Java 8 Parallel Streams Tutorial with Examples Java 8 Custom Collector with Collector.of() TutorialClick to read tutorial: Java 8 Custom Collector with Collector.of() Tutorial Java 8 CompletableFuture Tutorial with ExamplesClick to read tutorial: Java 8 CompletableFuture Tutorial with Examples Java 8 CompletableFuture: Combining and Error HandlingClick to read tutorial: Java 8 CompletableFuture: Combining and Error Handling Java 8 Map merge, putIfAbsent and forEach with ExamplesClick to read tutorial: Java 8 Map merge, putIfAbsent and forEach with Examples Java 8 - How to Sort a Map by Key or ValueClick to read tutorial: Java 8 - How to Sort a Map by Key or Value Java 8 BiFunction, BiConsumer and BiPredicate TutorialClick to read tutorial: Java 8 BiFunction, BiConsumer and BiPredicate Tutorial