CodeGym /課程 /JAVA 25 SELF /函式介面:@FunctionalInterface

函式介面:@FunctionalInterface

JAVA 25 SELF
等級 20 , 課堂 3
開放

1. 導言

函式介面 — 是只包含恰好一個抽象方法(也就是沒有實作的方法)的介面。這樣的介面可以用來精簡地表達方法的實作 — 在 Java 中可透過 Lambda 運算式來完成(我們稍後會學到)。

為什麼只能有一個方法?

因為函式介面描述的就是一個操作。若方法有兩個以上,就不清楚到底要實作哪一個。因此規則很簡單:一個介面 — 一個抽象方法

標準程式庫中的範例

  • Runnable — 用於執行緒中的任務(void run())
  • Callable<V> — 用於會回傳結果的任務(V call())
  • Comparator<T> — 用於比較物件(int compare(T o1, T o2))
  • Consumer<T> — 值的「消費者」(void accept(T t))
  • Supplier<T> — 值的「供應者」(T get())
  • Function<T, R> — 從 T 對應到 R 的函式(R apply(T t))
  • Predicate<T> — 條件判斷(boolean test(T t))

例如,介面 Runnable

public interface Runnable {
    void run();
}

再看介面 Comparator

public interface Comparator<T> {
    int compare(T o1, T o2);
    // ... 還有 default 與 static 方法,但抽象方法只有一個!
}

重要提示: defaultstatic 方法不算作抽象方法,因此它們可以有任意多個!

2. 註解 @FunctionalInterface

Java — 是一位嚴謹而有原則的女士。為了避免混淆,它允許你用註解 @FunctionalInterface 明確標記某個介面為函式介面。這就像貼上「只用一個按鈕就能運作!」的貼紙 — 以免有人加上多餘的東西。

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

此後若你不小心又加入第二個抽象方法,編譯器會立刻報錯:

@FunctionalInterface
public interface Oops {
    void doIt();
    void doSomethingElse(); // 錯誤!有兩個抽象方法
}

註解是必須的嗎?

不,並非必須。若介面恰好只有一個抽象方法,即使沒有它,介面仍然是函式介面。不過使用註解可以明確表達你的意圖,並保護你免於不小心犯錯。

可以加入 default 和 static 方法嗎?

可以!重點是必須只有 一個 抽象方法。其餘方法都可以是 defaultstatic,想加多少都行。

範例:

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

    default void printInfo() {
        System.out.println("我是個花式操作!");
    }

    static void description() {
        System.out.println("用於算術的函式介面。");
    }
}

3. 宣告與使用範例

假設你想描述一個針對兩個數字的操作,可以這樣做:

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

現在可以用不同方式來實作這個介面。

以一般類別實作

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

用法:

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

以匿名類別實作

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

關於 Lambda 的備註

自 Java 8 起,這類介面很適合用更精簡的 Lambda 運算式來實作。我們會在接下來幾堂課學到 Lambda;目前只要知道:函式介面的設計目的就是讓你能更方便地與之搭配使用。

4. 實作練習:寫出你自己的函式介面

任務 1:做一個屬於你的 Action!

建立介面 Action,接收一個字串且不回傳任何值。用匿名類別實作,將傳入字串以全大寫輸出。

@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("我在學 Java!"); // 我在學 JAVA!
    }
}

(之後我們會看到如何用 Lambda 運算式把它寫得更精簡。)

任務 2:數字過濾器

建立介面 NumberPredicate,其方法為 boolean test(int n)。使用匿名類別實作偶數檢查。

@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
    }
}

任務 3:使用標準介面

也可以不用自訂介面,直接使用現成的 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

表格:標準程式庫中的函式介面

介面 方法 描述 應用範例
Runnable
void run()
無引數且無回傳值的任務 執行緒、計時器
Callable<V>
V call()
有回傳值的任務 執行緒、ExecutorService
Comparator<T>
int compare(T, T)
比較兩個物件 集合排序
Consumer<T>
void accept(T)
值的「消費者」 遍歷集合
Supplier<T>
T get()
值的「供應者」 惰性初始化、資料生成
Function<T, R>
R apply(T)
從 T 到 R 的函式 資料轉換
Predicate<T>
boolean test(T)
條件檢查 集合過濾

5. 使用函式介面時的常見錯誤

錯誤 1:加入了第二個抽象方法。 若介面包含超過一個抽象方法,就不再是函式介面。編譯器(特別是在有 @FunctionalInterface 時)會立刻報錯。

錯誤 2:忘了 default 與 static 方法不算抽象方法。 可以放心把它們加到函式介面中 — 這不會違反「僅有一個抽象方法」的規則。

錯誤 3:實作的方法簽章不正確。 例如介面要求兩個參數,而你卻只在方法中寫了一個。務必核對簽章。

錯誤 4:沒有使用 @FunctionalInterface,結果不小心破壞了介面。 如果沒有用註解標記,很可能不小心加了多餘的方法 — 然後花很長時間找為什麼程式不運作。為求明確,建議一律加上註解。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION