Core Java

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:

Figure 1: Example Output for Handling Duplicates with the Merge Function in Collectors.toMap() - Java Map Collectors: toMap() vs groupingBy()

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.

Download
You can download the full source code of this example here: Java Map Collectors tomap vs groupingBy

Omozegie Aziegbe

Omos holds a Master degree in Information Engineering with Network Management from the Robert Gordon University, Aberdeen. Omos is currently a freelance web/application developer who is currently focused on developing Java enterprise applications with the Jakarta EE framework.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button