Skip to main content

Command Palette

Search for a command to run...

Bài 2: Các Functional Interface phổ biến trong Java 8

Published
5 min readView as Markdown

Mục tiêu:

  • Giới thiệu chi tiết về các Functional Interface (FI) có sẵn trong Java 8.

  • Giúp người đọc hiểu cách sử dụng các FI phổ biến như Predicate, Function, Consumer, và Supplier.

  • Phân biệt các FI chuyên biệt và lợi ích của chúng.


Dàn ý chi tiết:

1. Nhóm FI cơ bản

Các Functional Interface cơ bản trong Java 8 được định nghĩa trong gói java.util.function. Chúng được thiết kế để hỗ trợ các tác vụ phổ biến như kiểm tra điều kiện, ánh xạ dữ liệu, thực hiện hành động, và cung cấp giá trị.

  1. Predicate<T>: Kiểm tra điều kiện

    • Định nghĩa: Predicate<T> là một FI có phương thức test(T t) trả về giá trị boolean.

    • Công dụng: Dùng để kiểm tra một điều kiện nào đó trên đối tượng kiểu T.

    • Ví dụ:

        Predicate<Integer> isEven = num -> num % 2 == 0;
        System.out.println(isEven.test(4)); // true
        System.out.println(isEven.test(5)); // false
      
    • Ứng dụng: Thường dùng trong filter() của Stream để lọc dữ liệu.

  2. Function<T, R>: Ánh xạ dữ liệu

    • Định nghĩa: Function<T, R> là một FI có phương thức apply(T t) trả về giá trị kiểu R.

    • Công dụng: Dùng để chuyển đổi dữ liệu từ kiểu T sang kiểu R.

    • Ví dụ:

        Function<String, Integer> stringLength = str -> str.length();
        System.out.println(stringLength.apply("Java")); // 4
      
    • Ứng dụng: Thường dùng trong map() của Stream để biến đổi dữ liệu.

  3. Consumer<T>: Thực hiện hành động

    • Định nghĩa: Consumer<T> là một FI có phương thức accept(T t) không trả về giá trị.

    • Công dụng: Dùng để thực hiện một hành động trên đối tượng kiểu T.

    • Ví dụ:

        Consumer<String> printMessage = message -> System.out.println(message);
        printMessage.accept("Hello, World!"); // Hello, World!
      
    • Ứng dụng: Thường dùng trong forEach() của Stream để thực hiện hành động trên từng phần tử.

  4. Supplier<T>: Cung cấp giá trị

    • Định nghĩa: Supplier<T> là một FI có phương thức get() trả về giá trị kiểu T.

    • Công dụng: Dùng để cung cấp giá trị mà không cần đầu vào.

    • Ví dụ:

        Supplier<Double> randomValue = () -> Math.random();
        System.out.println(randomValue.get()); // Giá trị ngẫu nhiên từ 0.0 đến 1.0
      
    • Ứng dụng: Thường dùng để tạo Stream từ một nguồn dữ liệu động.


2. Nhóm FI chuyên biệt

Java 8 cung cấp các FI chuyên biệt để tối ưu hóa hiệu suất khi làm việc với các kiểu dữ liệu nguyên thủy (primitive types) như int, double, long, v.v. Các FI này giúp tránh việc auto-boxing/unboxing, từ đó cải thiện hiệu suất.

  1. Primitive Specialization

    • IntPredicate, DoublePredicate, LongPredicate:

      • Tương tự Predicate<T> nhưng làm việc trực tiếp với kiểu int, double, long.

      • Ví dụ:

          IntPredicate isPositive = num -> num > 0;
          System.out.println(isPositive.test(10)); // true
        
    • IntFunction<R>, DoubleFunction<R>, LongFunction<R>:

      • Tương tự Function<T, R> nhưng làm việc trực tiếp với kiểu int, double, long.

      • Ví dụ:

          IntFunction<String> intToString = num -> "Number: " + num;
          System.out.println(intToString.apply(42)); // Number: 42
        
    • IntConsumer, DoubleConsumer, LongConsumer:

      • Tương tự Consumer<T> nhưng làm việc trực tiếp với kiểu int, double, long.

      • Ví dụ:

          IntConsumer printSquare = num -> System.out.println(num * num);
          printSquare.accept(5); // 25
        
    • IntSupplier, DoubleSupplier, LongSupplier:

      • Tương tự Supplier<T> nhưng làm việc trực tiếp với kiểu int, double, long.

      • Ví dụ:

          IntSupplier randomInt = () -> (int) (Math.random() * 100);
          System.out.println(randomInt.get()); // Giá trị ngẫu nhiên từ 0 đến 99
        
  2. Multi-arguments FI

    • BiPredicate<T, U>:

      • Tương tự Predicate<T> nhưng nhận hai đối số.

      • Ví dụ:

          BiPredicate<String, Integer> isLengthEqual = (str, len) -> str.length() == len;
          System.out.println(isLengthEqual.test("Java", 4)); // true
        
    • BiFunction<T, U, R>:

      • Tương tự Function<T, R> nhưng nhận hai đối số.

      • Ví dụ:

          BiFunction<Integer, Integer, Integer> add = (a, b) -> a + b;
          System.out.println(add.apply(5, 3)); // 8
        
    • BiConsumer<T, U>:

      • Tương tự Consumer<T> nhưng nhận hai đối số.

      • Ví dụ:

          BiConsumer<String, Integer> printDetails = (name, age) -> 
              System.out.println(name + " is " + age + " years old.");
          printDetails.accept("Alice", 25); // Alice is 25 years old.
        

3. So sánh ưu/nhược

  1. Ưu điểm của Primitive Specialization:

    • Tránh auto-boxing/unboxing, giúp cải thiện hiệu suất.

    • Phù hợp khi làm việc với dữ liệu nguyên thủy.

  2. Nhược điểm của Primitive Specialization:

    • Chỉ hỗ trợ một số kiểu dữ liệu cụ thể (int, double, long).

    • Không linh hoạt như các FI generic (Predicate<T>, Function<T, R>, v.v.).


4. Ví dụ minh họa

  • Ví dụ 1: Sử dụng PredicateFunction trong Stream

      List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
      List<Integer> evenNumbers = numbers.stream()
                                         .filter(num -> num % 2 == 0) // Predicate
                                         .map(num -> num * 2)         // Function
                                         .collect(Collectors.toList());
      System.out.println(evenNumbers); // [4, 8]
    
  • Ví dụ 2: Sử dụng ConsumerSupplier

      Supplier<String> messageSupplier = () -> "Hello, World!";
      Consumer<String> messageConsumer = message -> System.out.println(message);
      messageConsumer.accept(messageSupplier.get()); // Hello, World!
    

Tổng kết

  • Các Functional Interface cơ bản như Predicate, Function, Consumer, và Supplier là nền tảng quan trọng trong Java 8.

  • Các FI chuyên biệt như IntPredicate, BiFunction, v.v. giúp tối ưu hóa hiệu suất và hỗ trợ các tác vụ phức tạp hơn.

  • Hiểu rõ các FI này giúp viết code ngắn gọn, hiệu quả và tận dụng tối đa sức mạnh của Stream API.


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

  1. Tạo một Predicate<String> để kiểm tra xem một chuỗi có độ dài lớn hơn 5 không.

  2. Sử dụng Function<Integer, String> để chuyển đổi một số nguyên thành chuỗi có định dạng "Number: X".

  3. Viết một Consumer<Integer> để in ra bình phương của một số.

  4. Sử dụng Supplier<LocalDate> để cung cấp ngày hiện tại.


Gợi ý đáp án

  1. Predicate:

     Predicate<String> isLong = str -> str.length() > 5;
     System.out.println(isLong.test("Java")); // false
    
  2. Function:

     Function<Integer, String> formatNumber = num -> "Number: " + num;
     System.out.println(formatNumber.apply(42)); // Number: 42
    
  3. Consumer:

     Consumer<Integer> printSquare = num -> System.out.println(num * num);
     printSquare.accept(5); // 25
    
  4. Supplier:

     Supplier<LocalDate> todaySupplier = () -> LocalDate.now();
     System.out.println(todaySupplier.get()); // Ngày hiện tại
    

Kết luận

Bài viết này đã giới thiệu chi tiết về các Functional Interface phổ biến trong Java 8, từ cơ bản đến chuyên biệt. Hiểu rõ các FI này giúp bạn viết code hiệu quả hơn và tận dụng tối đa sức mạnh của Stream API trong các bài viết tiếp theo.

More from this blog

hoangkim

366 posts