0

[Java Backend Zero to Hello] [Phase 2] BÀI 2.3: STREAM API

📚 Series: Java Backend Zero to Hello 📂 Phân đoạn: Phase 2: Java Core nâng cao 📖 Nội dung: BÀI 2.3: STREAM API 💡 Khóa học lập trình Backend Java & Spring Boot chuẩn doanh nghiệp từ con số 0.


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 bài học

⭐ Hãy bookmark (clip) lại series để tiện theo dõi các bài học tiếp theo nhé!


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí