Blog Java 8 Features

Mastering the Power of flatMap in Java 8 Streams

Introduction:

Java 8 introduced the Stream API, which provides a powerful way to process collections of data in a functional style. One of the most useful methods in the Stream API is flatMap. This blog post will explore what flatMap is, how it works, and how it differs from the map operation. We’ll also cover various use cases and examples to help you better understand and utilize flatMap in your Java code.

What is flatMap in Java 8?

The flatMap method is a part of the Java 8 Stream API. It is an intermediate operation that takes a function as an argument, applies that function to each element of the stream, and then flattens the resulting streams into a single stream.

How flatMap Works in Java 8?

The flatMap method combines two operations: mapping and flattening. First, it applies a provided function to each element in the stream, which produces a new stream for each element. Then, it concatenates or flattens all these resulting streams into a single flat stream.

The signature of the flatMap method is:

Java
<R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper)

  • T is the type of elements in the original stream.
  • R is the type of elements in the resulting flat stream.
  • The mapper function takes an element of type T and returns a stream of elements of type R.

Difference Between map and flatMap in Java 8?

The map and flatMap methods are both intermediate operations in the Stream API, but they serve different purposes.

The map operation applies a function to each element in the stream and returns a new stream with the transformed elements. It does not flatten nested structures.

On the other hand, flatMap not only applies a function to each element but also flattens any nested streams produced by that function into a single flat stream.

In simpler terms, map transforms elements, while flatMap transforms and flattens nested structures.

How to Use flatMap in Java Here’s a basic example that demonstrates how to use flatMap to flatten a list of lists into a single stream:

Java
List<List<Integer>> nestedList = Arrays.asList(
    Arrays.asList(1, 2),
    Arrays.asList(3, 4, 5),
    Arrays.asList(6)
);

Stream<Integer> flatStream = nestedList.stream()
                                       .flatMap(List::stream);

flatStream.forEach(System.out::println);

/*OUTPUT
1
2
3
4
5
6
*/

In this example, flatMap takes each inner list and converts it to a stream using List::stream. It then concatenates all these streams into a single flat stream of integers.

Java
List<List<Integer>> listOfList = Arrays.asList(
    Arrays.asList(1,2,3,4), 
    Arrays.asList(5,6,7,8), 
    Arrays.asList(9,10,11,12)
);
List<Integer> result = listOfList.stream()
       .flatMap(list -> list.stream())
       .collect(Collectors.toList());
System.out.println(result);
/*
OUTPUT
[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]
*/

In this example, list -> list.stream() is used to convert each List<Integer> in listOfLists into a Stream<Integer>. The flatMap operation then combines these individual streams into a single Stream<Integer>

Advanced Use Cases of flatMap

While flattening nested collections is a common use case, flatMap has many other applications, such as:

  • Generating streams from optional values
  • Combining multiple data sources
  • Processing hierarchical data structures
  • Performing complex data transformations

Complex FlatMap Examples

Complex example that demonstrates the power of flatMap in Java 8 Streams:

Suppose we have a list of customers, and each customer has a list of orders. We want to create a single flat stream of all order IDs across all customers. Here’s how we can achieve this using flatMap:

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

class Customer {
    int id;
    String name;
    List<Order> orders;

    Customer(int id, String name, List<Order> orders) {
        this.id = id;
        this.name = name;
        this.orders = orders;
    }
}

class Order {
    int id;
    double amount;

    Order(int id, double amount) {
        this.id = id;
        this.amount = amount;
    }
}

public class FlatMapExample {
    public static void main(String[] args) {
        Customer customer1 = new Customer(1, "John", Arrays.asList(
                new Order(101, 250.0),
                new Order(102, 175.5)
        ));

        Customer customer2 = new Customer(2, "Jane", Arrays.asList(
                new Order(201, 89.9),
                new Order(202, 399.0),
                new Order(203, 62.25)
        ));

        List<Customer> customers = Arrays.asList(customer1, customer2);

        // Get a flat stream of all order IDs across all customers
        List<Integer> orderIds = customers.stream()
                                          .flatMap(customer -> customer.orders.stream()
                                                                           .map(order -> order.id))
                                          .collect(Collectors.toList());

        System.out.println("Order IDs: " + orderIds);
    }
}

Output:

Java
Order IDs: [101, 102, 201, 202, 203]

In this example, we have two Customer objects, each with a list of Order objects. We want to create a single list of all order IDs across both customers.

To achieve this, we first create a stream of Customer objects using customers.stream(). Then, we apply flatMap to this stream, passing a lambda expression customer -> customer.orders.stream().map(order -> order.id).

Here’s what this lambda expression does:

  1. customer.orders.stream() creates a stream of Order objects for each Customer.
  2. .map(order -> order.id) converts each Order object in the stream to its id.

So, for each Customer, we create a stream of order IDs. The flatMap operation then concatenates all these streams of order IDs into a single flat stream.

Finally, we collect the resulting flat stream of order IDs into a list using collect(Collectors.toList()).

This example showcases how flatMap can be used to flatten nested data structures (in this case, a list of customers with lists of orders) and perform complex data transformations, such as extracting specific fields from nested objects.

Conclusion:

The flatMap method in Java 8 Streams is a powerful tool for working with nested data structures and performing complex data transformations. By understanding how it works and how it differs from the map operation, you can write more expressive and concise code when dealing with collections and streams in Java. Embrace the functional programming style introduced in Java 8, and leverage flatMap to simplify your data processing tasks.

Avatar

Neelabh

About Author

As Neelabh Singh, I am a Senior Software Engineer with 6.6 years of experience, specializing in Java technologies, Microservices, AWS, Algorithms, and Data Structures. I am also a technology blogger and an active participant in several online coding communities.

You may also like

Blog Design Pattern

Understanding the Builder Design Pattern in Java | Creational Design Patterns | CodeTechSummit

Overview The Builder design pattern is a creational pattern used to construct a complex object step by step. It separates
Blog Tech Toolkit

Base64 Decode

Base64 encoding is a technique used to encode binary data into ASCII characters, making it easier to transmit data over