Collectors.toMap() vs Collectors.groupingBy() in Java Streams
Java Streams offer powerful ways to process collections of elements. Two common operations involve transforming a stream into a map: Collectors.toMap() vs Collectors.groupingBy(). While both can be used to achieve similar results, they cater to different scenarios based on how we want to structure our resulting map. This article will delve into the differences between these collectors and explore when each is more appropriate.
1. Collectors.toMap()
The Collectors.toMap()
collector is used to collect elements of a stream into a Map
. It requires two functions:
- Key Mapper Function: Maps each element to a key in the resulting map.
- Value Mapper Function: Maps each element to a value in the resulting map.
Additionally, it allows specifying how to handle collisions when two elements map to the same key.
Syntax:
Map<K, V> map = stream.collect(Collectors.toMap(keyMapper, valueMapper, mergeFunction));
Examples:
Product.java
public class Product { private int id; private String name; private double price; private String category; // Getters and setters omitted for brevity }
This Product
class has basic fields like id
, name
, price
and category
for our examples.
ToMapExample.java
public class ToMapExample { public static void main(String[] args) { List<Product> products = List.of( new Product(1, "Shirt", 1200.0, "Clothing"), new Product(2, "Laptop", 2000.0, "Electronics"), new Product(3, "Keyboard", 80.0, "Accesories") ); // Example 1: Basic usage Map<Integer, String> productMap = products.stream().collect(Collectors.toMap( Product::getId, // keyMapper Product::getName // valueMapper )); System.out.println(productMap); } }
This block of code demonstrates the basic usage of Collectors.toMap()
to convert a list of Product
objects into a map. The products
list contains three Product
objects. Using a stream, each Product
object is mapped to an entry in the resulting map, where the key is the product’s id (obtained using Product::getId
) and the value is the product’s name (obtained using Product::getName
).
The resulting map output is:
{1=Shirt, 2=Laptop, 3=Keyboard}
1.1 Handling Duplicates
Handling duplicates with the merge function in Collectors.toMap()
is crucial when dealing with scenarios where multiple elements might map to the same key. Consider the following example:
public class ToMapExample { public static void main(String[] args) { // Example 2: Handling duplicates with merge function List<Product> products = List.of( new Product(1, "Shirt", 1200.0, "Clothing"), new Product(2, "Laptop", 2000.0, "Electronics"), new Product(3, "Keyboard", 80.0, "Accesories"), new Product(4, "Laptop", 2500.0, "Electronics") // Duplicate key with different price ); Map<String, Double> productPriceMap = products.stream() .collect(Collectors.toMap(Product::getName, Product::getPrice, (existing, replacement) -> existing)); System.out.println("Product Price Map: " + productPriceMap); } }
This code example code demonstrates handling duplicates with Collectors.toMap()
while converting the list of Product
objects into a map. The products
list contains four Product
objects, including two Products
(laptops) with different prices, causing a key collision.
Using a stream, each Product
object is mapped to an entry in the resulting map, where the key is the product’s name (Product::getName
) and the value is the product’s price (Product::getPrice
). The merge function (existing, replacement) -> existing
resolves key collisions by retaining the existing value, ensuring that the first encountered price for each product name is kept.
The resulting map, which pairs product names with their corresponding prices, is then printed to the console and the output is:
2. Collectors.groupingBy()
The Collectors.groupingBy()
collector is used to group elements of a stream based on a classifier function. It divides the elements into groups (represented by keys) and collects elements into a map where each key maps to a list of elements.
Syntax:
Map<K, List<T>> map = stream.collect(Collectors.groupingBy(classifier));
Example:
GroupingByExample.java
public class GroupingByExample { public static void main(String[] args) { List<Product> products = List.of( new Product(1, "Shirt", 1200.0, "Clothing"), new Product(2, "Laptop", 2000.0, "Electronics"), new Product(3, "Keyboard", 80.0, "Accesories") ); // Example 1: Grouping by product name Map<String, List<Product>> productGroups = products.stream() .collect(Collectors.groupingBy(Product::getName)); // Example 2: Grouping by price category Map<String, List<Product>> productByCategory = products.stream() .collect(Collectors.groupingBy(Product::getCategory)); // Example 3: Grouping by price range Map<String, List<Product>> priceRangeGroups = products.stream() .collect(Collectors.groupingBy( p -> { if (p.getPrice() < 50.0) { return "Cheap"; } else if (p.getPrice() < 200.0) { return "Moderate"; } else { return "Expensive"; } } )); } }
This block of code illustrates the use of Collectors.groupingBy()
to categorize a list of Product
objects in various ways. The products
list contains three items: a shirt, a laptop, and a keyboard.
- In the first example, the products are grouped by their names, resulting in a map where each key is a product name and the corresponding value is a list of products with that name.
- In the second example, products are grouped by their category, creating a map where each key is a category and the value is a list of products within that category.
- The third example demonstrates grouping products by price range based on specified price thresholds.
3. Choosing Between toMap and groupingBy
- Use
Collectors.toMap()
when:- You want a simple mapping of stream elements to a map where each element contributes exactly one key-value pair.
- You need control over how to resolve collisions (when multiple elements map to the same key).
- Use
Collectors.groupingBy()
when:- You need to categorize elements based on some criteria (like grouping words by their lengths or grouping objects by their attributes).
- You expect multiple elements to map to the same key and want them to be collected into a list or another collection.
4. Conclusion
In this article, we explored two powerful collectors in Java Streams for transforming elements into maps: Collectors.toMap()
and Collectors.groupingBy()
. In summary, Collectors.toMap()
is suitable for direct mapping of stream elements to a map, while Collectors.groupingBy()
is ideal for grouping elements based on common attributes or criteria, often resulting in a map where each key maps to a list of elements.
5. Download the Source Code
This article compares Java Map collectors: toMap vs groupingBy.
You can download the full source code of this example here: Java Map Collectors tomap vs groupingBy