Java 8 – How to Group By in Stream API

Introduction

Java 8 introduced the Stream API, which allows for functional-style operations on streams of elements, such as filtering, mapping, and reducing. One of the powerful features of the Stream API is the ability to group elements in a stream based on a certain criterion using the Collectors.groupingBy() method. This feature is particularly useful when you need to categorize data or aggregate values based on specific attributes.

In this guide, we’ll explore how to perform group-by operations using the Stream API in Java 8, including grouping by single and multiple fields and performing aggregation operations on the grouped data.

Table of Contents

  • Problem Statement
  • Solution Steps
  • Java Program
    • Example 1: Grouping by a Single Field
    • Example 2: Grouping by Multiple Fields
    • Example 3: Grouping and Counting Elements
    • Example 4: Grouping and Summing Values
    • Example 5: Grouping by Custom Collector
  • Conclusion

Problem Statement

When working with collections, you may need to group elements based on a specific attribute or set of attributes. The goal is to use the Stream API’s groupingBy() collector to perform these group-by operations in a concise and efficient manner.

Example:

  • Problem: Grouping a list of objects (e.g., employees, products) by a specific attribute (e.g., department, category).
  • Goal: Use the Stream API to group the elements based on the desired attribute(s) and optionally perform aggregations on the grouped data.

Solution Steps

  1. Use Collectors.groupingBy(): Learn how to use the groupingBy() method to group elements by a single attribute.
  2. Group by Multiple Fields: Extend the basic grouping to include multiple attributes.
  3. Perform Aggregations: Combine groupingBy() with other collectors like counting() and summingInt() to aggregate grouped data.
  4. Custom Collectors: Explore advanced grouping scenarios using custom collectors.

Java Program

Example 1: Grouping by a Single Field

Let’s start with a simple example where we group a list of Person objects by their city.

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Java 8 - Grouping by a Single Field
 * Author: https://www.rameshfadatare.com/
 */
public class GroupByExample1 {

    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("John", "New York"),
            new Person("Alice", "Los Angeles"),
            new Person("Bob", "New York"),
            new Person("Charlie", "Chicago"),
            new Person("David", "Los Angeles")
        );

        // Group people by city
        Map<String, List<Person>> peopleByCity = people.stream()
            .collect(Collectors.groupingBy(Person::getCity));

        // Print the result
        peopleByCity.forEach((city, persons) -> {
            System.out.println(city + ": " + persons);
        });
    }
}

class Person {
    private String name;
    private String city;

    public Person(String name, String city) {
        this.name = name;
        this.city = city;
    }

    public String getCity() {
        return city;
    }

    @Override
    public String toString() {
        return name;
    }
}

Output

New York: [John, Bob]
Los Angeles: [Alice, David]
Chicago: [Charlie]

Explanation

  • groupingBy(Person::getCity): Groups the Person objects by their city. The result is a map where the key is the city and the value is a list of people living in that city.

Example 2: Grouping by Multiple Fields

In this example, we group Person objects by both city and name.

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Java 8 - Grouping by Multiple Fields
 * Author: https://www.rameshfadatare.com/
 */
public class GroupByExample2 {

    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("John", "New York"),
            new Person("Alice", "Los Angeles"),
            new Person("Bob", "New York"),
            new Person("John", "Los Angeles"),
            new Person("Alice", "New York")
        );

        // Group people by city and then by name
        Map<String, Map<String, List<Person>>> peopleByCityAndName = people.stream()
            .collect(Collectors.groupingBy(Person::getCity, 
                     Collectors.groupingBy(Person::getName)));

        // Print the result
        peopleByCityAndName.forEach((city, names) -> {
            System.out.println(city + ": " + names);
        });
    }
}

class Person {
    private String name;
    private String city;

    public Person(String name, String city) {
        this.name = name;
        this.city = city;
    }

    public String getName() {
        return name;
    }

    public String getCity() {
        return city;
    }

    @Override
    public String toString() {
        return name;
    }
}

Output

New York: {John=[John], Alice=[Alice], Bob=[Bob]}
Los Angeles: {John=[John], Alice=[Alice]}

Explanation

  • groupingBy(Person::getCity, groupingBy(Person::getName)): This nested grouping groups people first by city and then by name within each city.

Example 3: Grouping and Counting Elements

In this example, we count the number of people in each city.

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Java 8 - Grouping and Counting Elements
 * Author: https://www.rameshfadatare.com/
 */
public class GroupByExample3 {

    public static void main(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("John", "New York"),
            new Person("Alice", "Los Angeles"),
            new Person("Bob", "New York"),
            new Person("Charlie", "Chicago"),
            new Person("David", "Los Angeles")
        );

        // Group people by city and count the number of people in each city
        Map<String, Long> peopleCountByCity = people.stream()
            .collect(Collectors.groupingBy(Person::getCity, Collectors.counting()));

        // Print the result
        peopleCountByCity.forEach((city, count) -> {
            System.out.println(city + ": " + count);
        });
    }
}

class Person {
    private String name;
    private String city;

    public Person(String name, String city) {
        this.name = name;
        this.city = city;
    }

    public String getCity() {
        return city;
    }
}

Output

New York: 2
Los Angeles: 2
Chicago: 1

Explanation

  • groupingBy(Person::getCity, counting()): Groups people by city and counts the number of people in each city.

Example 4: Grouping and Summing Values

Suppose we have a list of transactions, and we want to group them by type and calculate the total amount for each type.

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Java 8 - Grouping and Summing Values
 * Author: https://www.rameshfadatare.com/
 */
public class GroupByExample4 {

    public static void main(String[] args) {
        List<Transaction> transactions = Arrays.asList(
            new Transaction("Grocery", 50),
            new Transaction("Electronics", 200),
            new Transaction("Grocery", 30),
            new Transaction("Clothing", 100),
            new Transaction("Electronics", 150)
        );

        // Group transactions by type and sum the amounts
        Map<String, Integer> totalAmountByType = transactions.stream()
            .collect(Collectors.groupingBy(Transaction::getType, Collectors.summingInt(Transaction::getAmount)));

        // Print the result
        totalAmountByType.forEach((type, totalAmount) -> {
            System.out.println(type + ": $" + totalAmount);
        });
    }
}

class Transaction {
    private String type;
    private int amount;

    public Transaction(String type, int amount) {
        this.type = type;
        this.amount = amount;
    }

    public String getType() {
        return type;
    }

    public int getAmount() {
        return amount;
    }
}

Output

Grocery: $80
Electronics: $350
Clothing: $100

Explanation

  • groupingBy(Transaction::getType, summingInt(Transaction::getAmount)): Groups transactions by type and sums the amounts within each group.

Example 5: Grouping by Custom Collector

In this advanced example, we group people by city and create a string that lists all the names of people in each city.

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

/**
 * Java 8 - Grouping by Custom Collector
 * Author: https://www.rameshfadatare.com/
 */
public class GroupByExample5 {

    public static void main

(String[] args) {
        List<Person> people = Arrays.asList(
            new Person("John", "New York"),
            new Person("Alice", "Los Angeles"),
            new Person("Bob", "New York"),
            new Person("Charlie", "Chicago"),
            new Person("David", "Los Angeles")
        );

        // Group people by city and concatenate their names
        Map<String, String> namesByCity = people.stream()
            .collect(Collectors.groupingBy(Person::getCity, 
                     Collectors.mapping(Person::getName, Collectors.joining(", "))));

        // Print the result
        namesByCity.forEach((city, names) -> {
            System.out.println(city + ": " + names);
        });
    }
}

class Person {
    private String name;
    private String city;

    public Person(String name, String city) {
        this.name = name;
        this.city = city;
    }

    public String getName() {
        return name;
    }

    public String getCity() {
        return city;
    }
}

Output

New York: John, Bob
Los Angeles: Alice, David
Chicago: Charlie

Explanation

  • mapping(Person::getName, joining(", ")): Maps each Person to their name and then joins the names with a comma, resulting in a concatenated string of names for each city.

Conclusion

Grouping data is a common task when working with collections, and the Stream API in Java 8 provides a powerful and flexible way to perform these operations using Collectors.groupingBy(). Whether you need to group by a single field, multiple fields, or perform aggregations on grouped data, the Stream API makes it easy to write concise and readable code. By understanding and applying these concepts, you can effectively manage and transform your data using Java 8’s modern features.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top