Java Streams have revolutionized the way we handle collections in Java, providing a much more expressive and flexible approach to processing data. Let’s dive deep into this concept using a real-world example that showcases the power and versatility of stream operations.
Introduction to Java Streams
Java Streams API, introduced in Java 8, allows for functional-style operations on streams of elements. It supports various operations, which can be pipelined to produce the desired result. These operations are either intermediate (returning a stream) or terminal (producing a result or side-effect).
To understand how streams can be used in real-world scenarios, let’s consider a scenario involving a collection of Employee
objects. These objects contain information about employees, such as their name, department, salary, age, and city.
The Employee Class
First, let’s define our Employee
class:
public class Employee {
private int id;
private String name;
private String department;
private double salary;
private int age;
private String city;
// Constructor, getters, setters, and toString() method omitted for brevity
}
Real-World Stream Operations
Let’s explore various stream operations using a list of Employee
objects.
Grouping Employees by Department
One common operation in data processing is grouping elements by a certain attribute. We can group our employees by department as follows:
Map<String, List<Employee>> employeesByDepartment = employees.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
Calculating Average Salary
Calculating averages is straightforward with streams. Here’s how you can find the average salary:
double averageSalary = employees.stream()
.collect(Collectors.averagingDouble(Employee::getSalary));
Sorting IT Employees by Salary
Sorting is another powerful feature of streams. If we need to list IT employees sorted by their salary:
List<Employee> itEmployeesSortedBySalary = employees.stream()
.filter(e -> "IT".equals(e.getDepartment()))
.sorted(Comparator.comparingDouble(Employee::getSalary))
.collect(Collectors.toList());
Finding the Highest Paid Employee
Finding the maximum or minimum is a common requirement. Here’s how to find the employee with the highest salary:
Employee highestPaidEmployee = employees.stream()
.max(Comparator.comparingDouble(Employee::getSalary))
.orElseThrow(NoSuchElementException::new);
Counting Employees in Each City
Counting occurrences is another useful operation. We can count the number of employees in each city like this:
Map<String, Long> employeeCountByCity = employees.stream()
.collect(Collectors.groupingBy(Employee::getCity, Collectors.counting()));
Filtering Employees Based on Conditions
Streams make filtering data based on conditions quite intuitive. For example, to find employees older than 30:
List<Employee> employeesOlderThan30 = employees.stream()
.filter(e -> e.getAge() > 30)
.collect(Collectors.toList());
Conclusion
Java Streams offer a powerful, flexible approach to processing collections. By leveraging the capabilities of the Streams API, we can perform complex data processing operations with minimal code, improving readability and maintainability. The examples provided here are just the tip of the iceberg. The more you explore Java Streams, the more you’ll appreciate their potential to simplify your code and solve complex problems efficiently.