Java Stream API: Core Concepts and Practical Usage
Required Imports and Data Model
import java.util.*;
import java.util.stream.Collectors;
class Employee {
private String empName;
private int yearsOfService;
// Constructors, getters, and setters omitted for brevity
}
1. Stream Creation
Multiple methods exist to creating stream instances from various data sources.
Creating Streams from Arrays and Collections
// Create from variable arguments
Stream<String> names = Stream.of("Alex", "Jamie", "Taylor");
// Create an empty stream
Stream<String> emptyStream = Stream.empty();
// Create from a list
List<Integer> numbers = Arrays.asList(1, 2, 3, 4);
Stream<Integer> numStream = numbers.stream();
Primitive Streams
// Integer streams
int[] scores = {85, 92, 78};
IntStream scoreStream = Arrays.stream(scores);
IntStream rangeStream = IntStream.range(1, 10); // 1 to 9
IntStream closedRangeStream = IntStream.rangeClosed(1, 10); // 1 to 10
// Long and Double streams
LongStream idStream = LongStream.of(1001L, 1002L, 1003L);
DoubleStream priceStream = DoubleStream.of(19.99, 29.99, 39.99);
// Random value streams
Random rand = new Random();
IntStream randomInts = rand.ints(5); // 5 random integers
DoubleStream randomDoubles = rand.doubles(3); // 3 random doubles
2. Filtering Elements
The filter operation selects elements based on a predicate.
List<Employee> staff = new ArrayList<>();
staff.add(new Employee("Alex", 3));
staff.add(new Employee("Jordan", 7));
staff.add(new Employee("Casey", 1));
// Filter employees with more than 2 years of service
List<Employee> experienced = staff.stream()
.filter(e -> e.getYearsOfService() > 2)
.collect(Collectors.toList());
3. Element Transformasion
Streams provide map and flatMap for data transformation.
Simple Mapping
// Extract employee names
List<String> employeeNames = staff.stream()
.map(Employee::getEmpName)
.collect(Collectors.toList());
Flattening Collections
List<String> phrases = Arrays.asList("java,stream,api", "filter,map,reduce");
// Using map (returns Stream<Stream<String>>)
List<Stream<String>> mapped = phrases.stream()
.map(p -> Arrays.stream(p.split(",")))
.collect(Collectors.toList());
// Using flatMap (returns flattened Stream<String>)
List<String> flattened = phrases.stream()
.flatMap(p -> Arrays.stream(p.split(",")))
.collect(Collectors.toList());
4. Data Grouping
The Collectors.groupingBy collector enables data aggregation.
Basic Grouping
List<Employee> team = Arrays.asList(
new Employee("Alex", "Engineering"),
new Employee("Jordan", "Marketing"),
new Employee("Taylor", "Engineering")
);
// Group by department
Map<String, List<Employee>> byDept = team.stream()
.collect(Collectors.groupingBy(Employee::getDepartment));
Advanced Grouping Operations
// Count employees per department
Map<String, Long> deptCount = team.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.counting()));
// Calculate average service years per department
Map<String, Double> avgService = team.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.averagingInt(Employee::getYearsOfService)));
// Find employee with maximum service in each department
Map<String, Optional<Employee>> seniorStaff = team.stream()
.collect(Collectors.groupingBy(Employee::getDepartment,
Collectors.maxBy(Comparator
.comparing(Employee::getYearsOfService))));
Complex Grouping with Mapping
// Group by service years, then map to employee names
Map<Integer, List<String>> namesByService = team.stream()
.collect(Collectors.groupingBy(Employee::getYearsOfService,
Collectors.mapping(Employee::getEmpName,
Collectors.toList())));
5. Reduction Operations
The reduce method aggregates stream elements to a single value.
List<Integer> monthlySales = Arrays.asList(15000, 22000, 18000);
// Using reduce with identity value (0 for sum)
int totalSales = monthlySales.stream()
.reduce(0, (subtotal, sale) -> subtotal + sale);
// Using reduce without identity (returns Optional)
Optional<Integer> maxSale = monthlySales.stream()
.reduce(Integer::max);
6. Stream Sorting
// Ascending order by years of service
List<Employee> sortedAsc = team.stream()
.sorted(Comparator.comparing(Employee::getYearsOfService))
.collect(Collectors.toList());
// Descending order by years of service
List<Employee> sortedDesc = team.stream()
.sorted(Comparator.comparing(Employee::getYearsOfService).reversed())
.collect(Collectors.toList());
// Alternative using Collections.sort
Collections.sort(team, Comparator.comparing(Employee::getEmpName));