Được giới thiệu từ Java 8, Stream API cách mạng hóa cách Java developer làm việc với collection. Thay vì viết vòng lặp for dài dòng, bạn có thể xử lý dữ liệu theo phong cách functional programming — ngắn gọn, dễ đọc và dễ test hơn nhiều.
Một Stream không phải là cấu trúc dữ liệu — nó là một luồng xử lý dữ liệu theo chuỗi các bước (pipeline).
Cấu Trúc Của Một Stream Pipeline
- Nguồn (Source): Collection, array, hoặc I/O channel
- Intermediate Operations: Lười biếng (lazy), trả về Stream —
filter(),map(),sorted()… - Terminal Operation: Kích hoạt thực thi, trả về kết quả —
collect(),count(),forEach()…
10 Ví Dụ Stream API Thực Tế
1. Lọc Danh Sách (filter)
List<String> ten = List.of("An", "Bình", "Cường", "Dung", "Dương");
List<String> tenBatDauBangD = ten.stream()
.filter(t -> t.startsWith("D"))
.collect(Collectors.toList());
// Kết quả: [Dung, Dương]
2. Chuyển Đổi Dữ Liệu (map)
List<String> tenHoaHet = ten.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// Kết quả: [AN, BÌNH, CƯỜNG, DUNG, DƯƠNG]
3. Tính Tổng (reduce)
List<Integer> soNguyen = List.of(1, 2, 3, 4, 5);
int tong = soNguyen.stream()
.reduce(0, Integer::sum);
// Kết quả: 15
4. Sắp Xếp (sorted)
List<String> tenSapXep = ten.stream()
.sorted()
.collect(Collectors.toList());
// Kết quả: [An, Bình, Cường, Dung, Dương]
5. Đếm Phần Tử (count)
long soTenDai = ten.stream()
.filter(t -> t.length() > 3)
.count();
// Kết quả: 3 (Bình, Cường, Dương)
6. Tìm Phần Tử Đầu Tiên (findFirst)
Optional<String> dauTien = ten.stream()
.filter(t -> t.contains("u"))
.findFirst();
dauTien.ifPresent(System.out::println); // Cường
7. Gom Nhóm (groupingBy)
List<String> danhSach = List.of("An", "Bình", "Cường", "An", "Bình");
Map<String, Long> tanSuat = danhSach.stream()
.collect(Collectors.groupingBy(
t -> t, Collectors.counting()
));
// {An=2, Bình=2, Cường=1}
8. Làm Phẳng Danh Sách Lồng (flatMap)
List<List<Integer>> lồng = List.of(
List.of(1, 2), List.of(3, 4), List.of(5)
);
List<Integer> phang = lồng.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
// [1, 2, 3, 4, 5]
9. Kiểm Tra Điều Kiện (anyMatch / allMatch / noneMatch)
boolean coTenDai = ten.stream().anyMatch(t -> t.length() > 4); // true
boolean tatCaDai = ten.stream().allMatch(t -> t.length() > 1); // true
boolean khongCoSo = ten.stream().noneMatch(Character::isDigit); // ERROR: cần sửa
// Đúng: .noneMatch(t -> t.chars().anyMatch(Character::isDigit))
10. Lấy Thống Kê Số (IntStream Statistics)
IntSummaryStatistics stats = soNguyen.stream()
.mapToInt(Integer::intValue)
.summaryStatistics();
System.out.println("Max: " + stats.getMax()); // 5
System.out.println("Min: " + stats.getMin()); // 1
System.out.println("Avg: " + stats.getAverage()); // 3.0
Lưu Ý Quan Trọng Khi Dùng Stream
- Mỗi Stream chỉ dùng được một lần — sau terminal operation sẽ bị đóng
- Stream không thay đổi collection gốc
- Dùng
parallelStream()khi xử lý dữ liệu lớn, nhưng cẩn thận với side-effects - Không nên dùng Stream cho logic phức tạp nhiều nhánh điều kiện — vòng lặp truyền thống rõ ràng hơn
Kết Luận
Stream API là một trong những tính năng mạnh mẽ nhất của Java hiện đại. Hãy bắt đầu áp dụng vào code hàng ngày từ những thao tác đơn giản như filter và map, rồi dần làm quen với các operation phức tạp hơn. Code của bạn sẽ ngắn gọn và dễ đọc hơn rất nhiều.