CodeGym /Các khóa học /JAVA 25 SELF /Giao diện hàm: @FunctionalInterface

Giao diện hàm: @FunctionalInterface

JAVA 25 SELF
Mức độ , Bài học
Có sẵn

1. Giới thiệu

Giao diện hàm (functional interface) — là một giao diện chứa đúng một phương thức trừu tượng (tức là phương thức không có phần triển khai). Chính kiểu giao diện này có thể được dùng để viết gọn phần triển khai phương thức — trong Java điều này được thực hiện bằng biểu thức lambda (chúng ta sẽ học sau).

Vì sao chỉ một phương thức?

Bởi vì một giao diện hàm mô tả đúng một thao tác. Nếu có từ hai phương thức trở lên, sẽ không rõ cần triển khai chính xác phương thức nào. Vậy nên quy tắc rất đơn giản: một giao diện — một phương thức trừu tượng.

Ví dụ từ thư viện chuẩn

  • Runnable — dành cho tác vụ trong luồng (void run())
  • Callable<V> — dành cho tác vụ trả về kết quả (V call())
  • Comparator<T> — để so sánh các đối tượng (int compare(T o1, T o2))
  • Consumer<T> — “bộ tiêu thụ” giá trị (void accept(T t))
  • Supplier<T> — “nhà cung cấp” giá trị (T get())
  • Function<T, R> — hàm từ T sang R (R apply(T t))
  • Predicate<T> — kiểm tra điều kiện (boolean test(T t))

Ví dụ, đây là giao diện Runnable:

public interface Runnable {
    void run();
}

Và đây là giao diện Comparator:

public interface Comparator<T> {
    int compare(T o1, T o2);
    // ... còn có các phương thức default và static, nhưng chỉ có một phương thức trừu tượng!
}

Lưu ý quan trọng: các phương thức defaultstatic không được tính là trừu tượng, vì vậy có thể có bao nhiêu cũng được!

2. Chú thích @FunctionalInterface

Java rất nghiêm ngặt và nguyên tắc. Để tránh nhầm lẫn, bạn có thể gắn nhãn giao diện là giao diện hàm bằng chú thích @FunctionalInterface. Nó giống như một nhãn “Chỉ hoạt động với một nút!” — để không ai thêm thắt linh tinh.

@FunctionalInterface
public interface Operation {
    int apply(int a, int b);
}

Bây giờ nếu bạn vô tình thêm phương thức trừu tượng thứ hai, trình biên dịch sẽ báo lỗi ngay:

@FunctionalInterface
public interface Oops {
    void doIt();
    void doSomethingElse(); // Lỗi! Có hai phương thức trừu tượng
}

Chú thích có bắt buộc không?

Không, không bắt buộc. Giao diện vẫn là giao diện hàm ngay cả khi không có chú thích, miễn là nó chứa đúng một phương thức trừu tượng. Nhưng với chú thích, bạn thể hiện rõ ý định của mình và tự bảo vệ khỏi những sai sót tình cờ.

Có thể thêm phương thức default và static không?

Có chứ! Miễn là chỉ có một phương thức trừu tượng. Tất cả các phương thức còn lại có thể là default hoặc static, bao nhiêu tùy thích.

Ví dụ:

@FunctionalInterface
public interface FancyOperation {
    int apply(int a, int b);

    default void printInfo() {
        System.out.println("Tôi — phép toán fancy!");
    }

    static void description() {
        System.out.println("Giao diện hàm cho số học.");
    }
}

3. Ví dụ khai báo và sử dụng

Giả sử bạn muốn mô tả một phép toán trên hai số. Làm như sau:

@FunctionalInterface
public interface Operation {
    int apply(int a, int b);
}

Bây giờ có thể triển khai giao diện này theo nhiều cách.

Triển khai bằng lớp thông thường

public class SumOperation implements Operation {
    @Override
    public int apply(int a, int b) {
        return a + b;
    }
}

Sử dụng:

Operation sum = new SumOperation();
System.out.println(sum.apply(2, 3)); // 5

Triển khai bằng lớp ẩn danh

Operation multiply = new Operation() {
    @Override
    public int apply(int a, int b) {
        return a * b;
    }
};
System.out.println(multiply.apply(2, 3)); // 6

Ghi chú về lambda

Kể từ Java 8, các giao diện như vậy rất tiện để triển khai bằng biểu thức lambda — cú pháp ngắn gọn hơn. Chúng ta sẽ học lambda sau vài bài giảng nữa, nên hiện giờ chỉ cần biết: giao diện hàm được sinh ra để làm việc với chúng một cách tối ưu.

4. Thực hành: hãy tự viết giao diện hàm của bạn

Bài tập 1. Tạo Action của riêng bạn!

Tạo giao diện Action nhận một chuỗi và không trả về gì. Triển khai nó bằng lớp ẩn danh, lớp này in chuỗi ở dạng chữ hoa.

@FunctionalInterface
interface Action {
    void act(String s);
}

public class ActionDemo {
    public static void main(String[] args) {
        Action shout = new Action() {
            @Override
            public void act(String text) {
                System.out.println(text.toUpperCase());
            }
        };

        shout.act("tôi học java!"); // TÔI HỌC JAVA!
    }
}

(Sau này chúng ta sẽ thấy có thể viết ngắn gọn bằng biểu thức lambda.)

Bài tập 2. Bộ lọc số

Tạo giao diện NumberPredicate với phương thức boolean test(int n). Triển khai kiểm tra tính chẵn bằng lớp ẩn danh.

@FunctionalInterface
interface NumberPredicate {
    boolean test(int n);
}

public class PredicateDemo {
    public static void main(String[] args) {
        NumberPredicate isEven = new NumberPredicate() {
            @Override
            public boolean test(int n) {
                return n % 2 == 0;
            }
        };

        System.out.println(isEven.test(4)); // true
        System.out.println(isEven.test(7)); // false
    }
}

Bài tập 3. Sử dụng các giao diện chuẩn

Thay vì giao diện tự viết, có thể dùng sẵn Predicate<Integer>:

import java.util.function.Predicate;

Predicate<Integer> isPositive = new Predicate<Integer>() {
    @Override
    public boolean test(Integer x) {
        return x > 0;
    }
};

System.out.println(isPositive.test(10)); // true
System.out.println(isPositive.test(-5)); // false

Bảng: các giao diện hàm trong thư viện chuẩn

Giao diện Phương thức Mô tả Ví dụ sử dụng
Runnable
void run()
Tác vụ không có tham số và không có kết quả Luồng, bộ hẹn giờ
Callable<V>
V call()
Tác vụ có kết quả Luồng, ExecutorService
Comparator<T>
int compare(T, T)
So sánh hai đối tượng Sắp xếp collection
Consumer<T>
void accept(T)
“Bộ tiêu thụ” giá trị Duyệt collection
Supplier<T>
T get()
“Nhà cung cấp” giá trị Khởi tạo lười, sinh dữ liệu
Function<T, R>
R apply(T)
Hàm từ T sang R Chuyển đổi dữ liệu
Predicate<T>
boolean test(T)
Kiểm tra điều kiện Lọc collection

5. Những lỗi thường gặp khi làm việc với các giao diện hàm

Lỗi số 1: thêm phương thức trừu tượng thứ hai. Nếu giao diện có nhiều hơn một phương thức trừu tượng, nó không còn là giao diện hàm nữa. Trình biên dịch (đặc biệt khi dùng @FunctionalInterface) sẽ báo lỗi ngay.

Lỗi số 2: quên rằng phương thức default và static không được tính là trừu tượng. Bạn có thể thoải mái thêm chúng vào giao diện hàm — điều đó không vi phạm quy tắc “một phương thức trừu tượng”.

Lỗi số 3: triển khai sai chữ ký (signature) của phương thức. Ví dụ, giao diện yêu cầu hai tham số, nhưng bạn lại viết phương thức chỉ có một. Luôn kiểm tra chữ ký phương thức.

Lỗi số 4: không dùng @FunctionalInterface và vô tình làm hỏng giao diện. Nếu không gắn chú thích, bạn có thể vô tình thêm một phương thức thừa — rồi mất thời gian tìm hiểu vì sao mã không chạy. Tốt hơn là luôn thêm chú thích để thể hiện rõ ràng.

Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION