目录
- 背景
- Java中的同步回调
- 匿名内部类中的回调
- lambda的回调
- 异步回调函数
- 简单线程回调
- 平行执行的异步回调
- CompletableFuture中的回调
- 结论
背景
在Java中一个回调的操作是一个在一些操作完成之后被传递到另一个函数中并且被执行的函数。一个回调函数既可以被同步或者异步执行。在一个同步回调函数的案例中,一个函数紧着着另一个函数完成后被执行。在一个异步回调函数的案例中,一个函数不需要在其他函数执行的过程中按照特定顺序时间内被执行。
从在经典的监视者设计模式中使用的监听案例开始,这篇文章向你介绍了Java中的回调函数。你将会看到大量的同步和异步的回调实现,包含使用 CompletableFuture类的函数式回调。
Java中的同步回调
一个同步回调函数总是会在一些操作执行后立刻被执行。这意味着它将会在动作被执行后执行。
正如我提到的,在一个监视者设计模式中使用的一个回调函数的案例。在页面UI的按钮中,需要一个按钮被点击后去实例化一个操作,我们可以传递回调函数作为这个按钮动作的监听器。这个监听函数会一直等待按钮被点击,直到按钮被触发后执行。
现在让我们看代码中一个新的回调概念的案例
匿名内部类中的回调
任何时间我们在Java中,传递一个实现了接口的方法到另一个方法中,我们就是使用了回调函数的概念。在下面的代码中,我们传递了一个Consumer函数接口和一个匿名内部类去实现accept()方法。
一旦 accept() 方法被实现,我们将会在performAction方法里面执行它;然后我们会在Consumer接口里面执行accept() 方法。
import java.util.function.Consumer;public class AnonymousClassCallback {public static void main(String[] args) {performAction(new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println(s);}});}public static void performAction(Consumer<String> consumer) {System.out.println("Action is being performed...");consumer.accept("Callback is executed");}}
该代码的输出语句是:
Action is being performed…
Callback is executed…
在以上代码中,我们将Consumer接口传递到performAction() 方法中,然后performAction()方法执行后,调用accept()方法。
你可能注意到,使用一个匿名内部类是相当的啰嗦。使用lambda表达式替代匿名内部类将会更简单。让我们看看当我们在回调函数中使用lambda表达式会发生什么。
lambda的回调
在Java中,我们可以传递使用了lambda表达式实现的函数式接口到一个方法中,然后在一个操作结束后被执行。在代码中看起开是那样。
public class LambdaCallback {public static void main(String[] args) {performAction(() -> System.out.println("Callback function executed..."));}public static void performAction(Runnable runnable) {System.out.println("Action is being performed...");runnable.run();}}
输出结果表明,一旦这个操作执行后,回调即被调用。
在这个例子中,你可能注意到我们在performAction方法中传递了可运行的函数式接口。因此,我们可以重写run()方法并在performAction方法结束后执行run()方法。
异步回调函数
我们经常想去使用一个异步回调方法,这意味着一个方法将会在操作执行时被其他进程异步地调用。当一个回调方法不需要被其他进程立刻调用的时候,它可能对性能有所帮助。
简单线程回调
让我们以最简单的异步回调操作方式开始。在下面的代码中,首先我们从一个可执行的函数式接口中实现了run()方法。然后我们在一个创建线程中使用了它。最终,我们将会启动这个线程去异步地执行这个已实现的可执行函数式接口:
public class AsynchronousCallback {public static void main(String[] args) {Runnable runnable = () -> System.out.println("Callback executed...");AsynchronousCallback asynchronousCallback = new AsynchronousCallback();asynchronousCallback.performAsynchronousAction(runnable);}public void performAsynchronousAction(Runnable runnable) {new Thread(() -> {System.out.println("Processing Asynchronous Task...");runnable.run();}).start();}}
这个案例的输出结果是:
Processing Asynchronous Task…
Callback executed…
从上面的代码中,我们创建了一个实现run()方法的Runnable,然后我们调用performAsynchronousAction()方法,传递了Runnable,在新建的线程中run()方法调用了它。
在performAsynchronousAction() 方法内,我们向它传递了一个runnable参数,在方法内部用lambda实现了一个线程的run方法调用了它.。先打印 “Processing Asynchronous Task…” 最终方法内部调用了以参数传进来的runnable方法,打印 “Callback executed…”
平行执行的异步回调
比起在异步调用中回调函数,我们也可以从其他函数中平行地调用一个回调函数。这意味着我们可以开启两个线程,平行地调用这两个函数。
以下代码和之前的有点像,但是要注意到除了直接调用回调函数外,我们可以开启一个新的线程调用回调函数:
// Omitted code from above…
public void performAsynchronousAction(Runnable runnable) {new Thread(() -> {System.out.println("Processing Asynchronous Task...");new Thread(runnable).start();}).start();}
输出结果是:
Processing Asynchronous Task…
Callback executed…
当我们不需要回调函数在performAsynchronousAction() 方法调用之后被立刻执行,异步平行调用回调函数是非常有用的。
一个真实世界的案例,当我们在线上购买一个产品,我们不需要等待付款被确认后才执行检查库存等所有的这些重度操作。在这个案例中,回调在后台执行的同时我们可以做其他事情。
CompletableFuture中的回调
使用异步回调函数的另一种方法是使用CompletableFuture API。Java8中引入的这个强大的API有助于执行和组合异步方法调用。它完成了我们在上一个示例中所做的一切,例如创建一个新的线程,然后启动和管理它。
在下面的代码示例中,我们将创建一个新的CompletableFuture,然后调用传递String的supplyAsync方法。
接下来,我们将创建另一个CompletableFuture,它将应用回调函数来执行我们配置的第一个函数:
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.out.println(execution.get());}
}
它的输出结果如下:
Supply Async… Callback executed…
结论
回调在软件开发中到处使用,广泛地使用在工具、设计模式和应用程序中。有时候我们使用它们的时候却没有注意到。
在java代码中,我们通过大量常见的回调实现帮助我们解释它们广泛的用处。这里有一些回调的特征需要我们记住:
- 回调函数应该在执行另一个操作时执行,或者与该操作并行执行。
- 回调函数可以被同步执行,意味着它可以在其他操作执行后没有延迟地被立刻执行。
- 回调函数可以是异步的,这意味着它可以在后台执行,并且可能需要一些时间才能执行
- Observable设计模式使用回调来通知感兴趣的实体何时发生了操作。