Java 8 - Convert Stream to Map

Introduction

In Java 8, the Stream API allows you to process data in a functional and efficient way. You can easily convert a Stream of elements into a Map using the Collectors.toMap() method. This is useful when you need to transform data from a list, set, or other collections into key-value pairs.

In this guide, we will learn how to convert a Stream to a Map using Java 8's Collectors.toMap() method, including handling duplicate keys and specifying custom merging strategies.

Solution Steps

  1. Create or Obtain a Stream: Generate or obtain a stream from a collection or other data source.
  2. Use Collectors.toMap(): Apply the toMap() method to define how to extract the key-value pairs from the stream elements.
  3. Handle Duplicate Keys: Optionally handle cases where duplicate keys exist by providing a merge function.
  4. Display or Use the Resulting Map: Use or print the resulting map.

Java Program

Example 1: Convert Stream of Strings to a Map

In this example, we convert a stream of strings to a map where the string is the key and its length is the value.

import java.util.stream.Stream;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamToMapExample {
    public static void main(String[] args) {
        // Step 1: Create a stream of strings
        Stream<String> fruitStream = Stream.of("Apple", "Banana", "Orange", "Mango");

        // Step 2: Convert the Stream to a Map (key: string, value: length of string)
        Map<String, Integer> fruitMap = fruitStream.collect(Collectors.toMap(
            fruit -> fruit,        // Key: the string itself
            fruit -> fruit.length() // Value: the length of the string
        ));

        // Step 3: Display the Map
        System.out.println(fruitMap);  // Output: {Apple=5, Banana=6, Orange=6, Mango=5}
    }
}

Output

{Apple=5, Banana=6, Orange=6, Mango=5}

Explanation

Step 1: Create a Stream of Strings

We create a stream of strings using Stream.of():

Stream<String> fruitStream = Stream.of("Apple", "Banana", "Orange", "Mango");

Step 2: Convert the Stream to a Map

We use Collectors.toMap() to convert the stream into a map where the key is the string itself, and the value is the length of the string:

Map<String, Integer> fruitMap = fruitStream.collect(Collectors.toMap(
    fruit -> fruit,        // Key: the string itself
    fruit -> fruit.length() // Value: the length of the string
));

Step 3: Display the Map

We print the resulting map:

System.out.println(fruitMap);

Example 2: Convert Stream of Custom Objects to a Map

In this example, we convert a stream of Employee objects to a map where the key is the employee's name, and the value is the employee's age.

import java.util.stream.Stream;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamToMapCustomObjects {
    public static void main(String[] args) {
        // Step 1: Create a stream of Employee objects
        Stream<Employee> employeeStream = Stream.of(
            new Employee("Ravi", 30),
            new Employee("Amit", 25),
            new Employee("Pooja", 35)
        );

        // Step 2: Convert the Stream to a Map (key: employee name, value: employee age)
        Map<String, Integer> employeeMap = employeeStream.collect(Collectors.toMap(
            Employee::getName,    // Key: employee name
            Employee::getAge      // Value: employee age
        ));

        // Step 3: Display the Map
        employeeMap.forEach((name, age) -> 
            System.out.println("Name: " + name + ", Age: " + age));
    }
}

class Employee {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

Output

Name: Ravi, Age: 30
Name: Amit, Age: 25
Name: Pooja, Age: 35

Explanation

Step 1: Create a Stream of Custom Objects

We create a stream of Employee objects using Stream.of():

Stream<Employee> employeeStream = Stream.of(
    new Employee("Ravi", 30),
    new Employee("Amit", 25),
    new Employee("Pooja", 35)
);

Step 2: Convert the Stream to a Map

We use Collectors.toMap() to convert the stream into a map where the key is the employee's name and the value is the employee's age:

Map<String, Integer> employeeMap = employeeStream.collect(Collectors.toMap(
    Employee::getName,    // Key: employee name
    Employee::getAge      // Value: employee age
));

Step 3: Display the Map

We print the resulting map:

employeeMap.forEach((name, age) -> 
    System.out.println("Name: " + name + ", Age: " + age));

Example 3: Handling Duplicate Keys in toMap()

If the stream contains elements that could generate duplicate keys, you need to provide a merge function to handle the key collisions. In this example, we merge employees with the same name by keeping the employee with the higher age.

import java.util.stream.Stream;
import java.util.Map;
import java.util.stream.Collectors;

public class StreamToMapWithDuplicates {
    public static void main(String[] args) {
        // Step 1: Create a stream of Employee objects with duplicate names
        Stream<Employee> employeeStream = Stream.of(
            new Employee("Ravi", 30),
            new Employee("Ravi", 35),  // Duplicate name with higher age
            new Employee("Amit", 25)
        );

        // Step 2: Convert the Stream to a Map, resolving duplicate keys by keeping the older employee
        Map<String, Integer> employeeMap = employeeStream.collect(Collectors.toMap(
            Employee::getName,    // Key: employee name
            Employee::getAge,     // Value: employee age
            (age1, age2) -> age1 > age2 ? age1 : age2  // Merge function
        ));

        // Step 3: Display the Map
        employeeMap.forEach((name, age) -> 
            System.out.println("Name: " + name + ", Age: " + age));
    }
}

class Employee {
    private String name;
    private int age;

    public Employee(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }
}

Output

Name: Ravi, Age: 35
Name: Amit, Age: 25

Explanation

Step 1: Create a Stream with Duplicate Keys

We create a stream of Employee objects where two employees have the same name, but different ages:

Stream<Employee> employeeStream = Stream.of(
    new Employee("Ravi", 30),
    new Employee("Ravi", 35),  // Duplicate name
    new Employee("Amit", 25)
);

Step 2: Provide a Merge Function for Duplicate Keys

We use Collectors.toMap() and provide a merge function to resolve duplicate keys by keeping the employee with the higher age:

(employee1, employee2) -> employee1.getAge() > employee2.getAge() ? employee1 : employee2;

Step 3: Display the Map

We print the resulting map:

employeeMap.forEach((name, age) -> 
    System.out.println("Name: " + name + ", Age: " + age));

Conclusion

In Java 8, converting a stream to a map is easy and flexible using the Collectors.toMap() method. You can define how keys and values are derived from the stream elements and handle duplicate keys with a custom merge function if necessary. This method is powerful and helps transform streams into key-value mappings in a concise and readable way.

Comments