Filter Nested Collections with Stream in Java
The Java Stream API offers powerful filtering capabilities for elements within a Stream. However, when these stream elements include nested collections, we may need to filter the elements within those nested collections as well. Let us delve into understanding how to filter nested collections using the Stream API.
1. Filter Nested Collections with Stream in Java
In Java, working with nested collections can sometimes be challenging, especially when you need to filter data based on certain conditions. The Stream API, introduced in Java 8, provides a powerful way to process collections in a declarative manner.
1.1 Understanding Nested Collections
A nested collection is a collection that contains other collections. For instance, a list of lists, a list of maps, or a map of lists are all examples of nested collections. To effectively filter nested collections, we often need to flatten them first.
1.2 Example Scenario
Let’s consider a scenario where we have a list of Department
objects, and each Department
contains a list of Employee
objects. We want to filter out all employees who have a salary greater than a certain threshold.
import java.util.List; class Employee { private String name; private double salary; public Employee(String name, double salary) { this.name = name; this.salary = salary; } public String getName() { return name; } public double getSalary() { return salary; } @Override public String toString() { return "Employee{name='" + name + "', salary=" + salary + "}"; } } class Department { private String name; private List<Employee> employees; public Department(String name, List<Employee> employees) { this.name = name; this.employees = employees; } public String getName() { return name; } public List<Employee> getEmployees() { return employees; } }
1.2.1 Filter employees using flatMap() and filter()
To filter the employees with a salary greater than a specified threshold, we will use the Stream API. Here’s how you can do it:
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class FilterNestedCollections { public static void main(String[] args) { List<Employee> empList1 = Arrays.asList( new Employee("John Doe", 60000), new Employee("Jane Smith", 75000) ); List<Employee> empList2 = Arrays.asList( new Employee("Jack White", 50000), new Employee("Jill Green", 90000) ); List<Department> departments = Arrays.asList( new Department("HR", empList1), new Department("IT", empList2) ); double salaryThreshold = 65000; List<Employee> filteredEmployees = departments.stream() .flatMap(department -> department.getEmployees().stream()) .filter(employee -> employee.getSalary() > salaryThreshold) .collect(Collectors.toList()); filteredEmployees.forEach(System.out::println); } }
Here’s a code breakdown:
flatMap:
This method is used to flatten the nested collections. In this case, we flatten the list of employees from each department into a single stream of employees.filter:
This method is used to filter the employees based on the salary condition.collect:
This method collects the filtered employees into a list.
The code output is:
Employee{name='Jane Smith', salary=75000.0} Employee{name='Jill Green', salary=90000.0}
1.2.2 Filter employees using mapMulti()
With the addition of mapMulti
in Java 16, filtering and flattening nested collections has become even more streamlined. Here’s how you can do it:
import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; public class FilterNestedCollections { public static void main(String[] args) { List<Employee> empList1 = Arrays.asList( new Employee("John Doe", 60000), new Employee("Jane Smith", 75000) ); List<Employee> empList2 = Arrays.asList( new Employee("Jack White", 50000), new Employee("Jill Green", 90000) ); List<Department> departments = Arrays.asList( new Department("HR", empList1), new Department("IT", empList2) ); double salaryThreshold = 65000; List<Employee> filteredEmployees = departments.stream() .mapMulti((department, downstream) -> department.getEmployees().stream() .filter(employee -> employee.getSalary() > salaryThreshold) .forEach(downstream::accept)) .collect(Collectors.toList()); filteredEmployees.forEach(System.out::println); } }
Here’s a code breakdown:
mapMulti:
This method is used to transform and flatten the stream in a single step. It takes a BiConsumer that accepts each element from the original stream and a downstream consumer to which the transformed elements are passed.filter:
This method is used within themapMulti
method to filter the employees based on the salary condition.collect:
This method collects the filtered employees into a list.
The code output is:
Employee{name='Jane Smith', salary=75000.0} Employee{name='Jill Green', salary=90000.0}
2. Conclusion
The Stream API in Java provides a convenient and powerful way to process and filter nested collections. By using methods like flatMap
and filter
, you can easily handle complex data structures and extract the necessary information with minimal effort.