Java Stream API: Lập trình hàm trong Java 8+
🎯 Tại sao cần Stream API?
Trước Java 8, để xử lý collection, chúng ta phải viết nhiều boilerplate code:
// Cách cũ: Tìm sinh viên có điểm > 8.0
List<Student> topStudents = new ArrayList<>();
for (Student student : students) {
if (student.getGrade() > 8.0) {
topStudents.add(student);
}
}
// Cách mới với Stream (Java 8+)
List<Student> topStudents = students.stream()
.filter(s -> s.getGrade() > 8.0)
.collect(Collectors.toList());
Ưu điểm:
- Code ngắn gọn, dễ đọc hơn
- Hỗ trợ parallel processing dễ dàng
- Functional programming style
1. Ba loại operations trong Stream
1.1. Intermediate Operations (Lazy)
Operations trả về Stream mới, chưa execute ngay:
Stream<Student> stream = students.stream()
.filter(s -> s.getAge() > 18) // Chưa chạy
.map(Student::getName) // Chưa chạy
.distinct(); // Chưa chạy
// Chỉ chạy khi gọi terminal operation
List<String> names = stream.collect(Collectors.toList()); // Execute!
1.2. Terminal Operations
Operations kết thúc stream và trả về kết quả:
students.stream()
.filter(s -> s.getGrade() > 8.0)
.count(); // Terminal → return long
students.stream()
.filter(s -> s.getGrade() > 8.0)
.forEach(System.out::println); // Terminal → return void
1.3. Short-circuiting Operations
Operations dừng sớm khi tìm được kết quả:
boolean hasTopStudent = students.stream()
.anyMatch(s -> s.getGrade() > 9.5); // Dừng ngay khi tìm thấy
Optional<Student> first = students.stream()
.filter(s -> s.getAge() < 18)
.findFirst(); // Dừng sau khi tìm được 1 phần tử
2. Các operations phổ biến
map() - Transform elements
// Lấy danh sách tên sinh viên
List<String> names = students.stream()
.map(Student::getName)
.collect(Collectors.toList());
// Chuyển sang uppercase
List<String> upperNames = students.stream()
.map(Student::getName)
.map(String::toUpperCase)
.collect(Collectors.toList());
filter() - Lọc elements
// Sinh viên > 20 tuổi
List<Student> adults = students.stream()
.filter(s -> s.getAge() > 20)
.collect(Collectors.toList());
// Chain nhiều filters
List<Student> filtered = students.stream()
.filter(s -> s.getAge() > 18)
.filter(s -> s.getGrade() > 7.0)
.filter(s -> s.getMajor().equals("IT"))
.collect(Collectors.toList());
reduce() - Aggregate values
// Tính tổng điểm
double totalGrade = students.stream()
.map(Student::getGrade)
.reduce(0.0, Double::sum);
// Tìm max
Optional<Double> maxGrade = students.stream()
.map(Student::getGrade)
.reduce(Double::max);
// Concatenate strings
String allNames = students.stream()
.map(Student::getName)
.reduce("", (a, b) -> a + ", " + b);
3. Collectors - Xử lý kết quả
Grouping
// Group by major
Map<String, List<Student>> byMajor = students.stream()
.collect(Collectors.groupingBy(Student::getMajor));
// Count by major
Map<String, Long> countByMajor = students.stream()
.collect(Collectors.groupingBy(
Student::getMajor,
Collectors.counting()
));
Partitioning
// Chia thành 2 nhóm: pass/fail
Map<Boolean, List<Student>> passFail = students.stream()
.collect(Collectors.partitioningBy(s -> s.getGrade() >= 5.0));
List<Student> passed = passFail.get(true);
List<Student> failed = passFail.get(false);
Custom Collectors
// Joining với delimiter
String names = students.stream()
.map(Student::getName)
.collect(Collectors.joining(", ", "[", "]"));
// Result: "[Nam, Hân, Trung]"
// Statistics
DoubleSummaryStatistics stats = students.stream()
.collect(Collectors.summarizingDouble(Student::getGrade));
System.out.println("Average: " + stats.getAverage());
System.out.println("Max: " + stats.getMax());
4. Parallel Streams
// Sequential (default)
long count = students.stream()
.filter(s -> s.getGrade() > 8.0)
.count();
// Parallel - sử dụng multiple threads
long count = students.parallelStream()
.filter(s -> s.getGrade() > 8.0)
.count();
⚠️ Cảnh báo:
- Chỉ dùng parallel với operations stateless và no side-effects
- Không tự ý dùng parallel, benchmark trước!
5. Common Pitfalls
❌ KHÔNG làm thế này:
// Modify external state (side effect)
List<String> result = new ArrayList<>();
students.stream()
.forEach(s -> result.add(s.getName())); // BAD!
// Correct way
List<String> result = students.stream()
.map(Student::getName)
.collect(Collectors.toList()); // GOOD!
❌ Reuse stream:
Stream<Student> stream = students.stream();
stream.forEach(System.out::println);
stream.count(); // IllegalStateException!
// Stream can only be used once!
6. Kết luận
Khi nào nên dùng Stream:
- ✅ Operations đơn giản: filter, map, collect
- ✅ Cần parallel processing
- ✅ Code ngắn gọn, readable
Khi nào KHÔNG nên dùng:
- ❌ Operations phức tạp với nhiều side effects
- ❌ Performance critical với small collections
- ❌ Debugging khó (stack trace không rõ ràng)
Rule of thumb: Nếu for-loop đơn giản hơn, hãy dùng for-loop!
