0

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

Java Backend Zero to Hello

📚 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

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í