# Bài 7: Sử dụng Predicate và Function trong Stream

### **Mục tiêu:**

* Hướng dẫn chi tiết cách sử dụng **Predicate** và **Function** trong Stream API của Java 8.
    
* Giải thích cách kết hợp `Predicate` và `Function` để lọc và biến đổi dữ liệu trong Stream.
    
* Cung cấp ví dụ minh họa chi tiết để áp dụng vào thực tế.
    

---

### **Dàn ý chi tiết:**

#### **1\. Predicate trong Stream**

`Predicate<T>` là một Functional Interface được sử dụng để kiểm tra một điều kiện trên đối tượng kiểu `T`. Nó thường được sử dụng trong các thao tác lọc dữ liệu (filtering) trong Stream.

1. **Cú pháp cơ bản:**
    
    * Phương thức `test(T t)` của `Predicate` trả về giá trị boolean.
        
    * Ví dụ:
        
        ```java
        Predicate<Integer> isEven = num -> num % 2 == 0;
        System.out.println(isEven.test(4)); // true
        ```
        
2. **Sử dụng** `Predicate` trong Stream:
    
    * Phương thức `filter(Predicate<T>)` trong Stream dùng để lọc các phần tử thỏa mãn điều kiện.
        
    * Ví dụ: Lọc các số chẵn từ một danh sách:
        
        ```java
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        List<Integer> evenNumbers = numbers.stream()
                                           .filter(n -> n % 2 == 0) // Sử dụng Predicate
                                           .collect(Collectors.toList());
        System.out.println(evenNumbers); // [2, 4, 6]
        ```
        
3. **Kết hợp nhiều Predicate:**
    
    * Sử dụng các phương thức `and()`, `or()`, và `negate()` để kết hợp nhiều điều kiện.
        
    * Ví dụ: Lọc các số chẵn và lớn hơn 3:
        
        ```java
        Predicate<Integer> isEven = num -> num % 2 == 0;
        Predicate<Integer> isGreaterThan3 = num -> num > 3;
        List<Integer> result = numbers.stream()
                                      .filter(isEven.and(isGreaterThan3))
                                      .collect(Collectors.toList());
        System.out.println(result); // [4, 6]
        ```
        

---

#### **2\. Function trong Stream**

`Function<T, R>` là một Functional Interface được sử dụng để biến đổi dữ liệu từ kiểu `T` sang kiểu `R`. Nó thường được sử dụng trong các thao tác biến đổi dữ liệu (mapping) trong Stream.

1. **Cú pháp cơ bản:**
    
    * Phương thức `apply(T t)` của `Function` trả về giá trị kiểu `R`.
        
    * Ví dụ:
        
        ```java
        Function<String, Integer> stringLength = str -> str.length();
        System.out.println(stringLength.apply("Java")); // 4
        ```
        
2. **Sử dụng** `Function` trong Stream:
    
    * Phương thức `map(Function<T, R>)` trong Stream dùng để biến đổi từng phần tử.
        
    * Ví dụ: Chuyển đổi các chuỗi thành chữ in hoa:
        
        ```java
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        List<String> upperCaseNames = names.stream()
                                           .map(String::toUpperCase) // Sử dụng Function
                                           .collect(Collectors.toList());
        System.out.println(upperCaseNames); // [ALICE, BOB, CHARLIE]
        ```
        
3. **Kết hợp nhiều Function:**
    
    * Sử dụng phương thức `andThen()` hoặc `compose()` để kết hợp nhiều `Function`.
        
    * Ví dụ: Chuyển đổi chuỗi thành chữ in hoa và sau đó lấy độ dài:
        
        ```java
        Function<String, String> toUpperCase = String::toUpperCase;
        Function<String, Integer> getLength = String::length;
        Function<String, Integer> transform = toUpperCase.andThen(getLength);
        
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        List<Integer> lengths = names.stream()
                                     .map(transform)
                                     .collect(Collectors.toList());
        System.out.println(lengths); // [5, 3, 7]
        ```
        

---

#### **3\. Kết hợp Predicate và Function trong Stream**

`Predicate` và `Function` có thể được kết hợp để thực hiện các thao tác phức tạp hơn, chẳng hạn như lọc dữ liệu trước khi biến đổi.

1. **Ví dụ: Lọc và biến đổi dữ liệu:**
    
    * Lọc các số chẵn và sau đó tính bình phương của chúng:
        
        ```java
        List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
        List<Integer> result = numbers.stream()
                                      .filter(n -> n % 2 == 0) // Lọc số chẵn
                                      .map(n -> n * n)        // Tính bình phương
                                      .collect(Collectors.toList());
        System.out.println(result); // [4, 16, 36]
        ```
        
2. **Ví dụ: Lọc và chuyển đổi chuỗi:**
    
    * Lọc các chuỗi có độ dài lớn hơn 3 và chuyển đổi thành chữ in hoa:
        
        ```java
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
        List<String> result = names.stream()
                                   .filter(name -> name.length() > 3) // Lọc chuỗi
                                   .map(String::toUpperCase)         // Chuyển đổi
                                   .collect(Collectors.toList());
        System.out.println(result); // [ALICE, CHARLIE, DAVID]
        ```
        

---

### **Ví dụ tổng hợp**

1. **Sử dụng** `Predicate` và `Function` trong Stream:
    
    ```java
    List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
    List<String> result = names.stream()
                               .filter(name -> name.length() > 3) // Lọc chuỗi
                               .map(String::toUpperCase)         // Chuyển đổi
                               .collect(Collectors.toList());
    System.out.println(result); // [ALICE, CHARLIE, DAVID]
    ```
    
2. **Kết hợp nhiều** `Predicate` và `Function`:
    
    ```java
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6);
    Predicate<Integer> isEven = num -> num % 2 == 0;
    Predicate<Integer> isGreaterThan3 = num -> num > 3;
    Function<Integer, Integer> square = num -> num * num;
    
    List<Integer> result = numbers.stream()
                                  .filter(isEven.and(isGreaterThan3)) // Lọc số chẵn và > 3
                                  .map(square)                       // Tính bình phương
                                  .collect(Collectors.toList());
    System.out.println(result); // [16, 36]
    ```
    

---

### **Tổng kết**

* `Predicate<T>` được sử dụng để lọc dữ liệu trong Stream thông qua phương thức `filter()`.
    
* `Function<T, R>` được sử dụng để biến đổi dữ liệu trong Stream thông qua phương thức `map()`.
    
* Kết hợp `Predicate` và `Function` giúp thực hiện các thao tác phức tạp như lọc và biến đổi dữ liệu một cách hiệu quả.
    

---

### **Bài tập thực hành**

1. Tạo một Stream từ một danh sách các số nguyên và lọc ra các số lớn hơn 10. Sau đó, tính bình phương của các số này và in kết quả ra màn hình.
    
2. Tạo một Stream từ một danh sách các chuỗi và lọc ra các chuỗi có độ dài lớn hơn 5. Sau đó, chuyển đổi các chuỗi này thành chữ thường và in kết quả ra màn hình.
    
3. Kết hợp nhiều `Predicate` để lọc các số nguyên dương và chia hết cho 3. Sau đó, tính tổng các số này.
    

---

### **Gợi ý đáp án**

1. **Lọc và tính bình phương:**
    
    ```java
    List<Integer> numbers = Arrays.asList(5, 12, 8, 15, 3);
    List<Integer> result = numbers.stream()
                                  .filter(n -> n > 10) // Lọc số > 10
                                  .map(n -> n * n)    // Tính bình phương
                                  .collect(Collectors.toList());
    System.out.println(result); // [144, 225]
    ```
    
2. **Lọc và chuyển đổi chuỗi:**
    
    ```java
    List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
    List<String> result = names.stream()
                               .filter(name -> name.length() > 5) // Lọc chuỗi > 5 ký tự
                               .map(String::toLowerCase)         // Chuyển đổi thành chữ thường
                               .collect(Collectors.toList());
    System.out.println(result); // [charlie]
    ```
    
3. **Kết hợp nhiều Predicate và tính tổng:**
    
    ```java
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5, 6, 9, 12);
    Predicate<Integer> isPositive = num -> num > 0;
    Predicate<Integer> isDivisibleBy3 = num -> num % 3 == 0;
    
    int sum = numbers.stream()
                     .filter(isPositive.and(isDivisibleBy3)) // Lọc số dương và chia hết cho 3
                     .mapToInt(Integer::intValue)           // Chuyển đổi thành IntStream
                     .sum();                                // Tính tổng
    System.out.println(sum); // 30
    ```
    

---

### **Kết luận**

Bài viết này đã hướng dẫn chi tiết cách sử dụng **Predicate** và **Function** trong Stream API của Java 8. Hiểu rõ cách kết hợp các Functional Interface này giúp bạn xử lý dữ liệu một cách hiệu quả và linh hoạt trong các bài toán thực tế.
