[Java Backend Zero to Hello] BÀI 2.3: STREAM API
📚 Bài viết thuộc series Java Backend Zero to Hello 📌 Phần: Phase 2: Java Core Nâng Cao | Bài 18/86
BÀI 2.3: STREAM API
Mục tiêu
- Hiểu Stream và đặc điểm
- Thực hiện các thao tác: filter, map, reduce, collect
- Sử dụng Optional
- Parallel Stream
1. STREAM LÀ GÌ?
Stream là chuỗi các phần tử hỗ trợ các thao tác tổng hợp (aggregate operations) theo kiểu functional.
Đặc điểm
- Không lưu trữ dữ liệu - chỉ xử lý từ nguồn
- Functional - không thay đổi nguồn
- Lazy - thực thi khi cần
- Có thể dùng 1 lần - sau khi duyệt xong phải tạo stream mới
2. TẠO STREAM
// Từ Collection
List<String> list = Arrays.asList("A", "B", "C");
Stream<String> stream1 = list.stream();
Stream<String> parallelStream = list.parallelStream();
// Từ Array
String[] arr = {"A", "B", "C"};
Stream<String> stream2 = Arrays.stream(arr);
// Từ giá trị
Stream<Integer> stream3 = Stream.of(1, 2, 3, 4, 5);
// Stream rỗng
Stream<String> empty = Stream.empty();
// Stream vô hạn
Stream<Integer> infinite = Stream.iterate(0, n -> n + 1);
Stream<Double> random = Stream.generate(Math::random);
// Từ String
IntStream chars = "Hello".chars();
3. CÁC THAO TÁC TRUNG GIAN (INTERMEDIATE)
Trả về Stream mới, lazy - chưa thực thi ngay.
3.1 filter - Lọc phần tử
List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
List<Integer> evens = numbers.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
// [2, 4, 6]
3.2 map - Biến đổi phần tử
List<String> names = Arrays.asList("an", "binh", "chau");
List<String> upper = names.stream()
.map(String::toUpperCase)
.collect(Collectors.toList());
// [AN, BINH, CHAU]
List<Integer> lengths = names.stream()
.map(String::length)
.collect(Collectors.toList());
// [2, 4, 4]
3.3 flatMap - Làm phẳng
List<List<Integer>> nested = Arrays.asList(
Arrays.asList(1, 2),
Arrays.asList(3, 4),
Arrays.asList(5, 6)
);
List<Integer> flat = nested.stream()
.flatMap(list -> list.stream())
.collect(Collectors.toList());
// [1, 2, 3, 4, 5, 6]
3.4 distinct - Loại bỏ trùng
List<Integer> nums = Arrays.asList(1, 2, 2, 3, 3, 3);
List<Integer> unique = nums.stream()
.distinct()
.collect(Collectors.toList());
// [1, 2, 3]
3.5 sorted - Sắp xếp
List<Integer> nums = Arrays.asList(3, 1, 4, 1, 5);
List<Integer> sorted = nums.stream()
.sorted()
.collect(Collectors.toList());
// [1, 1, 3, 4, 5]
// Sắp xếp giảm dần
List<Integer> desc = nums.stream()
.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
3.6 limit, skip
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> first3 = nums.stream().limit(3).collect(Collectors.toList()); // [1, 2, 3]
List<Integer> skip2 = nums.stream().skip(2).collect(Collectors.toList()); // [3, 4, 5]
3.7 peek - Xem phần tử (debug)
List<Integer> result = nums.stream()
.peek(n -> System.out.println("Trước: " + n))
.map(n -> n * 2)
.peek(n -> System.out.println("Sau: " + n))
.collect(Collectors.toList());
4. CÁC THAO TÁC KẾT THÚC (TERMINAL)
Thực thi stream, trả về kết quả.
4.1 collect - Thu thập
List<String> list = stream.collect(Collectors.toList());
Set<String> set = stream.collect(Collectors.toSet());
Collection<String> collection = stream.collect(Collectors.toCollection(ArrayList::new));
// Join thành String
String joined = stream.collect(Collectors.joining(", "));
4.2 forEach - Duyệt
stream.forEach(System.out::println);
4.3 count - Đếm
long count = stream.count();
4.4 reduce - Rút gọn
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
// Tổng
Optional<Integer> sum = nums.stream().reduce((a, b) -> a + b);
int sum2 = nums.stream().reduce(0, Integer::sum);
// Tích
int product = nums.stream().reduce(1, (a, b) -> a * b);
// Max/Min
Optional<Integer> max = nums.stream().reduce(Integer::max);
4.5 anyMatch, allMatch, noneMatch
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5);
boolean hasEven = nums.stream().anyMatch(n -> n % 2 == 0); // true
boolean allEven = nums.stream().allMatch(n -> n % 2 == 0); // false
boolean noneNegative = nums.stream().noneMatch(n -> n < 0); // true
4.6 findFirst, findAny
Optional<Integer> first = nums.stream().findFirst();
Optional<Integer> any = nums.parallelStream().findAny();
5. COLLECTORS NÂNG CAO
5.1 toMap
Map<String, Integer> map = users.stream()
.collect(Collectors.toMap(User::getName, User::getAge));
5.2 groupingBy - Nhóm
Map<String, List<User>> byDept = users.stream()
.collect(Collectors.groupingBy(User::getDepartment));
// Đếm theo nhóm
Map<String, Long> countByDept = users.stream()
.collect(Collectors.groupingBy(User::getDepartment, Collectors.counting()));
5.3 partitioningBy - Phân vùng
Map<Boolean, List<Integer>> evenOdd = nums.stream()
.collect(Collectors.partitioningBy(n -> n % 2 == 0));
// {false=[1, 3, 5], true=[2, 4, 6]}
5.4 summarizing - Thống kê
IntSummaryStatistics stats = nums.stream()
.collect(Collectors.summarizingInt(Integer::intValue));
System.out.println("Count: " + stats.getCount());
System.out.println("Sum: " + stats.getSum());
System.out.println("Min: " + stats.getMin());
System.out.println("Max: " + stats.getMax());
System.out.println("Avg: " + stats.getAverage());
6. OPTIONAL
Đối tượng có thể chứa hoặc không chứa giá trị - tránh NullPointerException.
6.1 Tạo Optional
Optional<String> opt1 = Optional.of("Hello"); // Không được null
Optional<String> opt2 = Optional.ofNullable(null); // Có thể null
Optional<String> opt3 = Optional.empty(); // Rỗng
6.2 Kiểm tra và lấy giá trị
Optional<String> opt = Optional.ofNullable(getValue());
if (opt.isPresent()) {
String value = opt.get();
}
// Giá trị mặc định
String value = opt.orElse("Default");
String value2 = opt.orElseGet(() -> "Computed default");
String value3 = opt.orElseThrow(() -> new RuntimeException("Not found"));
6.3 Biến đổi
Optional<Integer> length = opt.map(String::length);
Optional<String> upper = opt.map(String::toUpperCase);
// Filter
Optional<String> filtered = opt.filter(s -> s.length() > 3);
6.4 Sử dụng với Stream
List<User> users = ids.stream()
.map(id -> findUserById(id)) // Trả về Optional<User>
.filter(Optional::isPresent) // Lọc Optional có giá trị
.map(Optional::get) // Lấy giá trị
.collect(Collectors.toList());
// Hoặc dùng flatMap (Java 9+)
List<User> users = ids.stream()
.map(this::findUserById)
.flatMap(Optional::stream)
.collect(Collectors.toList());
7. PARALLEL STREAM
Xử lý song song trên nhiều thread.
List<Integer> nums = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
int sum = nums.parallelStream()
.mapToInt(Integer::intValue)
.sum();
// Hoặc
int sum2 = nums.stream()
.parallel()
.mapToInt(Integer::intValue)
.sum();
Khi nào dùng Parallel Stream
- ✅ Tập dữ liệu lớn (>10,000 phần tử)
- ✅ Thao tác tốn CPU
- ❌ Thao tác I/O (database, network)
- ❌ Có thay đổi state chung
8. BÀI TẬP THỰC HÀNH
Bài 1: Thống kê điểm
List<Student> students = getStudents();
double avgGpa = students.stream()
.mapToDouble(Student::getGpa)
.average()
.orElse(0.0);
List<Student> topStudents = students.stream()
.filter(s -> s.getGpa() >= 3.5)
.sorted(Comparator.comparingDouble(Student::getGpa).reversed())
.limit(10)
.collect(Collectors.toList());
Bài 2: Nhóm theo tuổi
Map<Integer, List<Student>> byAge = students.stream()
.collect(Collectors.groupingBy(Student::getAge));
Bài 3: Tìm sản phẩm đắt nhất theo danh mục
Map<String, Optional<Product>> maxByCategory = products.stream()
.collect(Collectors.groupingBy(
Product::getCategory,
Collectors.maxBy(Comparator.comparingDouble(Product::getPrice))
));
Bài 4: Tổng hợp đơn hàng
double totalRevenue = orders.stream()
.filter(o -> o.getStatus() == OrderStatus.COMPLETED)
.flatMap(o -> o.getItems().stream())
.mapToDouble(item -> item.getPrice() * item.getQuantity())
.sum();
9. TÓM TẮT
| Thao tác | Loại | Mô tả |
|---|---|---|
filter |
Intermediate | Lọc phần tử |
map |
Intermediate | Biến đổi |
flatMap |
Intermediate | Làm phẳng |
sorted |
Intermediate | Sắp xếp |
distinct |
Intermediate | Loại trùng |
limit/skip |
Intermediate | Giới hạn |
collect |
Terminal | Thu thập |
forEach |
Terminal | Duyệt |
reduce |
Terminal | Rút gọn |
count |
Terminal | Đếm |
match |
Terminal | Kiểm tra |
Optional |
- | Tránh null |
Bài tiếp theo: 2.4 Optional
🧭 Điều Hướng Series
⬅️ Bài trước: BÀI 2.2: LAMBDA EXPRESSION
📋 Lộ trình tổng quan: Xem Toàn Bộ Series
➡️ Bài tiếp theo: BÀI 2.4: OPTIONAL
All rights reserved