1. 程式人生 > >Java 8 習慣用語,第 7 部分 函式介面

Java 8 習慣用語,第 7 部分 函式介面

原文地址:https://www.ibm.com/developerworks/cn/java/j-java8idioms7/index.html

Java 8 習慣用語,第 7 部分

函式介面

瞭解如何建立自定義函式介面,以及為什麼應儘量使用內建介面

lambda 表示式的型別是什麼?一些語言使用函式值函式物件來表示 lambda 表示式,但 Java™ 語言沒有這麼做。Java 使用函式介面來表示 lambda 表示式型別。乍一看似乎有點奇怪,但事實上這是一種確保對 Java 語言舊版本的向後相容性的有效途徑。

您應該非常熟悉下面這段程式碼:

Thread thread = new Thread(new Runnable() {
public void run() { System.out.println("In another thread"); } }); thread.start(); System.out.println("In main");

Thread 類和它的建構函式是在 Java 1.0 中引入的,距今已有超過 20 年的時間。從那時起,建構函式從未改變過。將 Runnable 的匿名例項傳遞給建構函式已成為一種傳統。但是從 Java 8 開始,可以選擇傳遞 lambda 表示式:

Thread thread = new Thread(() -> System.out.println("In another thread"));
關於本系列

Java 8 是自 Java 語言誕生以來進行的一次最重大更新—包含了非常豐富的新功能,您可能想知道從何處開始著手瞭解它。在本系列中,作家兼教師 Venkat Subramaniam 提供了一種慣用的 Java 8 程式設計方法:這些簡短的探索會激發您反思您認為理所當然的 Java 約定,同時逐步將新技術和語法整合到您的程式中。

Thread 類的建構函式想要一個實現 Runnable 的例項。在本例中,我們傳遞了一個 lambda 表示式,而不是傳遞一個物件。我們可以選擇向各種各樣的方法和建構函式傳遞 lambda 表示式,包括在 Java 8 之前建立的一些方法和建構函式。這很有效,因為 lambda 表示式在 Java 中表示為函式介面。

函式介面有 3 條重要法則:

  1. 一個函式介面只有一個抽象方法。
  2. 在 Object 類中屬於公共方法的抽象方法不會被視為單一抽象方法。
  3. 函式介面可以有預設方法和靜態方法。

任何滿足單一抽象方法法則的介面,都會被自動視為函式介面。這包括 Runnable 和 Callable 等傳統介面,以及您自己構建的自定義介面。

內建函式介面

除了已經提到的單一抽象方法之外,JDK 8 還包含多個新函式介面。最常用的介面包括 Function<T, R>Predicate<T> 和Consumer<T>,它們是在 java.util.function 包中定義的。Stream 的 map 方法接受 Function<T, R> 作為引數。類似地,filter 使用 Predicate<T>forEach 使用 Consumer<T>。該包還有其他函式介面,比如 Supplier<T>BiConsumer<T, U>和 BiFunction<T, U, R>

可以將內建函式介面用作我們自己的方法的引數。例如,假設我們有一個 Device 類,它包含方法 checkout 和 checkin 來指示是否正在使用某個裝置。當用戶請求一個新裝置時,方法 getFromAvailable 從可用裝置池中返回一個裝置,或在必要時建立一個新裝置。

我們可以實現一個函式來借用裝置,就象這樣:

public void borrowDevice(Consumer<Device> use) { Device device = getFromAvailable(); device.checkout(); try { use.accept(device);      } finally { device.checkin(); } }

borrowDevice 方法:

  • 接受 Consumer<Device> 作為引數。
  • 從池中獲取一個裝置(我們在這個示例中不關心執行緒安全問題)。
  • 呼叫 checkout 方法將裝置狀態設定為 checked out
  • 將裝置交付給使用者。

在完成裝置呼叫後返回到 Consumer 的 accept 方法時,通過呼叫 checkin 方法將裝置狀態更改為 checked in

下面給出了一種使用 borrowDevice 方法的方式:

new Sample().borrowDevice(device -> System.out.println("using " + device));

因為該方法接收一個函式介面作為引數,所以傳入一個 lambda 表示式作為引數是可以接受的。

自定義函式介面

儘管最好儘量使用內建函式介面,但有時需要自定義函式介面。

要建立自己的函式介面,需要做兩件事:

  1. 使用 @FunctionalInterface 註釋該介面,這是 Java 8 對自定義函式介面的約定。
  2. 確保該介面只有一個抽象方法。

該約定清楚地表明該介面應接收 lambda 表示式。當編譯器看到該註釋時,它會驗證該介面是否只有一個抽象方法。

使用 @FunctionalInterface 註釋可以確保,如果在未來更改該介面時意外違反抽象方法數量規則,您會獲得錯誤訊息。這很有用,因為您會立即發現問題,而不是留給另一位開發人員在以後處理它。沒有人希望在將 lambda 表示式傳遞給其他人的自定義介面時獲得錯誤訊息。

建立自定義函式介面

作為一個示例,我們將建立一個 Order 類,它有一系列 OrderItem 以及一個轉換並輸出它們的方法。我們首先建立一個介面。

下面的程式碼將建立一個 Transformer 函式介面。

@FunctionalInterface public interface Transformer<T> { T transform(T input); }

該介面用 @FunctionalInterface 註釋做了標記,表明它是一個函式介面。因為該註釋包含在 java.lang 包中,所以沒有必要匯入。該介面有一個名為 transform 的方法,後者接受一個引數化為 T 型別的物件,並返回一個相同型別的轉換後物件。轉換的語義將由該介面的實現來決定。

這是 OrderItem 類: