Java 中的回調操作是一個函數傳遞給另一個函數并在某個操作完成後執行。回調可以同步執行,也可以異步執行。在同步回調的情況下,一個函數緊接着另一個執行。在異步回調的情況下,一個函數在一段不确定的時間後執行,并且與其它函數沒有特定的順序發生。
本文從Observable設計模式中回調作為監聽器的經典例子開始,向大家介紹Java中的回調。接下來您将看到各種同步和異步回調實作的示例,包括CompletableFuture。
同步回調
同步回調函數将始終在執行某些操作後立即執行。這意味着它将與執行該操作的函數同步進行。
同步回調-匿名内部類回調
每當我們将帶有方法實作的接口傳遞給Java中的另一個方法時,我們都在使用回調函數的概念。在下面的示例中,我們将通過Consumer功能接口和一個匿名内部類來實作該accept()方法。
一旦accept()方法被實作,将從該方法執行動作execute()方法。
package com.joyce.callback;
import java.util.function.Consumer;
public class AnonymousInnerClassCallback {
public static void main(String[] args) {
execute(new Consumer<String>() {
@Override
public void accept(String s) {
System.err.println(s);
}
});
}
public static void execute(Consumer<String> consumer) {
System.err.println("Action is being execute...");
consumer.accept("Callback is execute...");
}
}
此代碼的執行結果如下圖所示:
在這段代碼中,我們将Consumer接口傳遞給execute()方法,然後accept()在操作完成後調用方法。
您可能注意到使用匿名内部類,代碼非常冗長。那麼接下來讓我們看看,改造使用lambda表達式用于我們的回調函數時會發生什麼。
同步回調-Lambda回調
在Java中,我們可以用lambda表達式實作函數式接口并将其傳遞給方法,然後在操作完成後執行該函數。示例如下圖:
package com.joyce.callback;
public class LambdaCallback {
public static void main(String[] args) {
execute(() -> System.err.println("Callback function execute..."));
}
public static void execute(Runnable runnable) {
System.err.println("Action is being execute...");
runnable.run();
}
}
此代碼的執行結果如下圖所示:
在此示例中,您可能會注意到我們Runnable在execute()方法中傳遞了功能接口。是以,我們能夠在方法run()的操作execute()方法完成後覆寫并執行該方法。
異步回調
通常,我們希望使用異步回調方法。這意味着将在操作之後調用但與其他程序異步調用方法。當不需要在其他程序之後立即調用回調方法時,這可能有助于提高性能。
異步回調-簡單線程回調
讓我們從進行異步回調調用操作的最簡單方法開始。在下面的示例代碼中,首先我們将從功能接口實作run()方法。然後,我們将建立一個Thread并使用run()方法。最後,我們将開始Thread異步執行。
package com.joyce.callback;
public class AsyncThreadCallback {
public static void main(String[] args) {
Runnable runnable = () -> System.err.println("Callback executed...");
AsyncThreadCallback asynchronousCallback = new AsyncThreadCallback();
asynchronousCallback.executeAsync(runnable);
}
public void executeAsync(Runnable runnable) {
new Thread(() -> {
System.err.println("Processing Async Task...");
runnable.run();
}).start();
}
}
此代碼的執行結果如下圖所示:
注意,在上面的代碼中,我們首先為run()方法建立了一個Runnable實作。然後,我們調用executeAsync()方法,将runnable功能接口與run()方法實作一起傳遞。
executeAsync()傳遞接口并使用 lambdaRunnable實作另一個Runnable接口。Thread列印“Processing Async Task…” 最後,我們調用我們通過參數傳遞的回調函數run,列印“Callback executed…”
異步回調-并行回調
除了在異步操作中調用回調函數外,我們還可以在調用另一個函數的同時調用回調函數。這意味着我們可以啟動兩個線程并行調用這些方法。
代碼将與前面的示例類似,但請注意,我們将啟動一個新線程并在這個新線程中調用回調函數,而不是直接調用回調函數:
package com.joyce.callback;
public class AsyncParallelCallback {
public static void main(String[] args) {
Runnable runnable = () -> System.err.println("Callback executed...");
AsyncParallelCallback asyncParallelCallback = new AsyncParallelCallback();
asyncParallelCallback.executeAsync(runnable);
}
public void executeAsync(Runnable runnable) {
new Thread(() -> {
System.err.println("Processing Async Task...");
new Thread(runnable).start();
}).start();
}
}
此代碼的執行結果如下圖所示:
當我們不需要在executeAsync()方法的操作之後立即執行回調函數時,異步并行回調很有用。
一個真實的例子,當我們線上購買産品時,我們不需要等到确認付款、檢查庫存以及所有那些繁重的流程。在這種情況下,我們可以在背景執行回調調用的同時做其他事情。
CompletableFuture 回調
使用異步回調函數的另一種方法是使用CompletableFuture API。這個強大的API在Java 8中引入,有助于執行群組合異步方法調用。它完成了我們在前面的示例中所做的一切,例如建立一個新的Thread然後啟動和管理它。
在下面的代碼示例中,我們将建立一個新的CompletableFuture,然後我們将調用supplyAsync()方法傳遞的方法String。
接下來,我們将建立另一個,CompletableFuture将thenApply一個回調函數與我們配置的第一個函數一起執行。
package com.joyce.callback;
import java.util.concurrent.CompletableFuture;
public class CompletableFutureCallback {
public static void main(String[] args) throws Exception {
CompletableFuture<String> completableFuture
= CompletableFuture.supplyAsync(() -> "Supply Async...");
CompletableFuture<String> execution = completableFuture
.thenApply(s -> s + " Callback executed...");
System.err.println(execution.get());
}
}
此代碼的執行結果如下圖所示:
結論
回調在軟體開發中無處不在,廣泛用于工具、設計模式和應用程式中。有時我們會在不知不覺中使用它們。
我們已經介紹了各種常見的回調實作,以幫助展示它們在Java代碼中的實用性和多功能性。以下是需要記住的回調的一些特性:
- 回調函數應該在執行另一個動作時或與執行該動作并行執行。
- 回調函數可以是同步的,這意味着它必須在其他操作之後立即執行,沒有任何延遲。
- 回調函數可以是異步的,這意味着它可以在背景執行,并且可能需要一些時間才能執行。
- Observable 設計模式使用回調在動作發生時通知感興趣的實體。