Java 8 - Tìm hiểu cách dùng Predicate
Trong Java 8, Predicate<T> là một functional interface và do đó nó có thể được sử dụng với lambda expression hoặc method reference cho một mục đích cụ thể nào đó. Predicate<T> sẽ trả về giá trị true/false của một argument kiểu T mà bạn đưa vào có thỏa với điều kiện của Predicate đó hay không, cụ thể là điều kiên được viết trong phương thức test().

Ví dụ đơn giản nhất
public class PredicateExample {
  public static void main(String[] args) {
    Predicate<Integer> p = (number) -> number % 2 == 1;
      System.out.println(p.test(1)); // in ra màn hình kết quả: true
      System.out.println(p.test(2)); // in ra màn hình kết quả: false
  }
}
Từ đoạn code trên các bạn có thể thấy rằng, tôi đang sử dụng biểu thức lambda để viết lại phần body code cho phương thức test() của Predicate. Đối số mà tôi truyền vào là một number có kiểu Interger (tương ứng với T).

Vậy chúng ta có thể sử dụng Predicate<T> ở đâu trong công việc lập trình hằng ngày? Bạn có thể sử dụng nó bất kỳ chỗ nào nếu như bạn muốn xem xét một điều kiện trên group/collection của các đối tượng tương tụ nhau. Ví dụ, bạn có thể xem xét các điều kiện để trả về true/false của những vấn đề như.
  • Tìm tất cả các học sinh có tên là Thuong
  • Tìm tất cả các học sinh có điểm toán lớn hơn hoặc bằng 5.0 và bé hơn 6.5
  • Tìm tất cả các học sinh có giới tính là Nam
Khá thú vị phải không nào, chúng ta sẽ tìm hiểu sâu hơn về nó qua các ví dụ sắp tới đây.

Như tôi đã đề cập ở phần đầu bài viết, Predicate là một functional interface. Điều đó có nghĩa rằng chúng ta có thể truyền vào Lambda Expression ở bất cứ đâu mà Predicate đang được mong đợi. Ví dụ như chúng ta có thể truyền Predicate vào phương thức filter() của Stream interface.

Stream<T> filter(Predicate<? super T> predicate);

Về Stream chúng ta sẽ tìm hiểu nó trong một bài viết sau nhé. Bây giờ chúng ta chỉ cần biết rằng Streams cho phép thực hiện các phép toán tuần tự và tổ hợp song song trên một chuỗi các phần tử. Có nghĩa là bất cứ lúc nào cũng có thể thực hiện các phép toán trên các phần tử có trong Stream ở 1 lần gọi. Trong ví dụ sắp tới đây, chúng ta sẽ sử dụng Stream và Predicate để lọc ra những phần tử từ một danh sách, sau đó thực hiện các phép toán cần thiết.

Sử dụng Predicate với collection

Để bắt đầu minh họa, chúng ta sẽ tạo ra một lớp Student có các thuộc tính như: firstName, lastName, age, mathPoint,...

Student.java
package com.vanthuong.tutorial.predicate;

public class Student {
  private Long id;
  private String firstName;
  private String lastName;
  private String gender;
  private Integer age;
  private Double mathPoint;

  public Student(Long id, String firstName, String lastName, String gender, Integer age, Double mathPoint) {
    this.id = id;
    this.firstName = firstName;
    this.lastName = lastName;
    this.gender = gender;
    this.age = age;
    this.mathPoint = mathPoint;
  }
  // Generate getters and setters here

  @Override
  public String toString() {
    return "\nStudent [id=" + id + ", firstName=" + firstName + ", lastName=" + lastName + ", gender=" + gender + ", age="
        + age + ", mathPoint=" + mathPoint + "]";
  }
}
Bây giờ chúng ta sẽ xây dựng các Predicate để thực thi như sau.

Tất cả học sinh có giới tính là Male/Female
public static Predicate<Student> isGenderEqualTo(String gender) {
  return p -> p.getGender().equalsIgnoreCase(gender);
}

Tất cả học sinh có điểm toán lớn hơn hoặc bằng minPoint và bé hơn maxPoint
public static Predicate<Student> isMathPointBetween(Double minPoint, Double maxPoint) {
  return p -> minPoint <= p.getMathPoint() && p.getMathPoint() < maxPoint;
}

Tất cả học sinh có tên là Thuong
public static Predicate<Student> isFirstNameEqualTo(String firstName) {
  return p -> p.getFirstName().equalsIgnoreCase(firstName);
}

Tất cả học sinh có tuổi lớn hơn 18
public static Predicate<Student> isAdult() {
  return p -> p.getAge() > 18;
}

Bạn có thể tạo thêm nhiều Predicate khác theo ý muốn để sử dụng trong ví dụ này tôi sẽ chỉ demo 4 phương thức như vậy. Bây giờ tôi sẽ gom chung chúng lại vào trong một class là StudentPredicate.

StudentPredicate.java
package com.vanthuong.tutorial.predicate;

import java.util.List;
import java.util.function.Predicate;
import java.util.stream.Collectors;

public class StudentPredicate {
  public static Predicate<Student> isGenderEqualTo(String gender) {
    return p -> p.getGender().equalsIgnoreCase(gender);
  }

  public static Predicate<Student> isMathPointBetween(Float minPoint, Float maxPoint) {
    return p -> minPoint <= p.getMathPoint() && p.getMathPoint() < maxPoint;
  }

  public static Predicate<Student> isFirstNameEqualTo(String firstName) {
    return p -> p.getFirstName().equalsIgnoreCase(firstName);
  }

  public static Predicate<Student> isAdult() {
    return p -> p.getAge() > 18;
  }

  public static List<Student> filterStudents(List<Student> students, Predicate<Student> predicate) {
    return students.stream().filter(predicate).collect(Collectors.<Student> toList());
  }
}

Trong đoạn code trên, tôi vừa bổ sung thêm phương thức filterStudents. Trong phương thức này, chúng ta sẽ truyền vào một danh sách các Student và một Predicate, kết quả trả về là một danh sách Student mới thỏa các điều kiện của Predicate.

OK, bây giờ chúng ta sẽ bắt đầu apply các Predicate này với đoạn code tương tự bên dưới đây và xem kết quả trả về như thế nào nhé.

TestStudentPredicate.java
package com.vanthuong.tutorial.predicate;

import java.util.ArrayList;
import java.util.List;
import static com.vanthuong.tutorial.predicate.StudentPredicate.*;

public class TestStudentPredicate {
  public static void main(String[] args) {
    List<Student> students = new ArrayList<>();
    students.add(new Student(1L, "Long", "Nguyen Thanh", "Male", 20, 4.0));
    students.add(new Student(2L, "Thuong", "Nguyen Van", "Male", 21, 8.5));
    students.add(new Student(3L, "Phuc", "Huynh Thi Kim", "Female", 20, 8.8));
    students.add(new Student(4L, "Thuong", "Tran Than", "Male", 18, 5.4));
    students.add(new Student(5L, "Ngoc", "Pham Bich", "Female", 19, 6.2));
    students.add(new Student(6L, "Linh", "Tran Thi", "Female", 17, 8.5));
    students.add(new Student(7L, "Le", "Vo Nhat", "Female", 17, 9.0));
    students.add(new Student(8L, "Nga", "Nguyen Thi", "Female", 17, 3.0));
    
    System.out.println("\n--- Danh sach hoc sinh nam ---");
    System.out.println(filterStudents(students, isGenderEqualTo("Male")));
    
    System.out.println("\n--- Danh sach hoc sinh co diem toan >= 8.5 va < 9.0 ---");
    System.out.println(filterStudents(students, isMathPointBetween(8.5, 9.0)));
    
    System.out.println("\n--- Danh sach hoc sinh ten la Thuong ---");
    System.out.println(filterStudents(students, isFirstNameEqualTo("Thuong")));
    
    System.out.println("\n--- Danh sach hoc sinh co tuoi > 18");
    System.out.println(filterStudents(students, isAdult()));
    
  }
}
Biên dịch và chạy chương trình chúng ta sẽ có được output như bên dưới
Ví dụ về Predicate trong Java 8

Lời kết

Predicate thật sự rất hữu ích, nó giúp bạn di chuyển các điều kiện (thường là business logic) vào một nơi để dễ kiểm soát code hơn. Bên cạnh đó nó giúp bạn viết Unit Test cho chúng dễ dàng hơn rất nhiều. Và trong ví dụ trên các bạn có thể thấy rằng chúng ta có thể sử dụng Stream để lọc các điều kiện khá đơn giản thay vì phải viết hàng loạt các vòng lặp và sử dụng if-else.

Tóm lại, bạn nên sử dụng Predicate mỗi khi có cơ hội để tăng hiệu quả khi lâp trình bên cạnh đó code của bạn sẽ đẹp hơn. Chúc các bạn học tốt nhé.