如何在 Java 中使用回调

163 阅读6分钟

前言

java中的回调?是不是很少听到这个说法?Java 中的回调操作是将一个函数传递给另一个函数并在某个操作完成后执行该函数。回调函数可以是同步回调也可以是异步回调。同步回调函数紧跟着另一个函数执行。异步回调与其他调用函数之间没有顺序关系。

Java 中的同步回调

同步回调函数将始终在执行某些操作后立即执行。这意味着它将与执行该操作的函数同步。

现在让我们看一下代码中回调概念的几个示例。

匿名内部类回调

每当我们将带有方法实现的接口传递给 Java 中的另一个方法时,我们都在使用回调函数的概念。在下面的代码中,我们将通过Consumer功能接口和一个匿名内部类(没有名称的实现)来实现该accept()方法。

一旦该accept()方法被实现,我们将从该方法执行动作performAction;然后我们将从界面执行该accept()方法Consumer:

import java.util.function.Consumer;

public class AnonymousClassCallback {

    public static void main(String[] args) {
        performAction(new Consumer<String>() {
            @Override
            public void accept(String s) {
                System.out.println(s);
            }
        });
    }

    public static void performAction(Consumer<String> consumer) {
        System.out.println("执行任务...");
        consumer.accept("执行回调函数...");
    }

}

此代码的输出是打印语句:

执行任务...

执行回调函数...

在这段代码中,我们将Consumer接口传递给performAction()方法,然后accept()在操作完成后调用方法。

您可能还会注意到使用匿名内部类非常冗长,这种方法在java8以前经常会用到,比如jdbcTemplate中的回调。改用 lambda 会好得多。让我们看看当我们将 lambda 用于我们的回调函数时会发生什么。

Lambda 回调

在 Java 中,我们可以用 lambda 表达式实现函数式接口并将其传递给方法,然后在操作完成后执行该函数。这是代码中的样子:

public class LambdaCallback {

    public static void main(String[] args) {
        performAction(() -> System.out.println("执行回调函数..."));
    }

    public static void performAction(Runnable runnable) {
        System.out.println("执行任务...");
        runnable.run();
    }

}

输出结果和上述 匿名内部类回调 中的列子一样。

在此示例中,您可能会注意到我们在performAction方法中传递了功能接口Runnable,runnable在方法performAction 输出 “我先被执行...” 之后被执行。lamdba使我们的回调方式看起来优雅而又易于理解。

异步回调

有时我们需要用到异步回调。异步回调有助于提高程序的性能,及时响应等。

比如:一个人烧开水,在开水沸腾之前,我们还以可做其他的事情比如看掘金“啊qcoder”的文章等等,而无需必须等待水烧开才能干其他的事情。

异步回调可用于:

1.事情可以同步进行,结果之间无因果关系。

2.在等待事件响应期间,线程依旧可以执行其他任务。比如网络请求,文件读写等操作。

简单的线程回调

让我们从进行异步回调调用操作的最简单方法开始。在下面的代码中,首先我们定一个Runnable对象。然后,我们将创建一个Thread并使用run()方法中执行我们的runnable.run()。最后,我们将开始performAsynchronousAction执行开启线程的执行。

请注入在new Thread()中我们的runnable.run()并非并行执行。

public class AsynchronousCallback {

    public static void main(String[] args) {
        Runnable runnable = () -> System.out.println("执行回调函数...");
        AsynchronousCallback asynchronousCallback = new AsynchronousCallback();
        asynchronousCallback.performAsynchronousAction(runnable);
    }

    public void performAsynchronousAction(Runnable runnable) {
        new Thread(() -> {
            System.out.println("执行异步开始...");
            runnable.run();
        }).start();
    }

}

输出结果如下:

执行异步开始...

执行回调函数...

异步并行回调

除了在异步操作中调用回调函数之外,我们还可以在调用另一个函数的同时调用回调函数。这意味着我们可以启动两个线程并并行调用这些方法。

代码将与前面的示例类似,但请注意,我们将启动一个新线程并在这个新线程中调用回调函数,而不是直接调用回调函数:

// Omitted code from above…
public void performAsynchronousAction(Runnable runnable) {
    new Thread(() -> {
    	System.out.println("执行异步任务...");
    	new Thread(runnable).start();
	}).start();
}

此操作的输出如下:

执行异步任务...

执行回调函数...

第一个方法中,我们直接在新线程中执行传入的Runnable对象,没有再次创建新线程。这种方式虽然简单,但是如果执行的异步任务比较耗时,会阻塞当前线程,影响应用程序的性能和用户体验。

第二个方法中,我们在新线程中创建了一个新的线程,用于执行传入的Runnable对象。这样可以避免阻塞当前线程,保证应用程序的性能和用户体验。

CompletableFuture 回调

使用异步回调函数的另一种方法是使用CompletableFuture API,这个强大的 API 在 Java 8 中引入。当我们创建一个CompletableFuture时,可以通过调用它的thenApply、thenAccept、thenCompose等方法来注册回调函数,其强大之处在于我们可能等待其返回的结果。

下面是一个简单的例子,演示了使用CompletableFuture异步回调的方式来处理异步任务的结果:

CompletableFuture<Integer> future = 
    CompletableFuture.supplyAsync(() -> 2 + 3);
CompletableFuture<String> future2 = 
    future.thenApplyAsync(result -> "结果是:" + result);
future2.thenAcceptAsync(System.out::println);

在上面的代码中,我们首先使用supplyAsync方法创建一个异步任务,该任务会计算2+3的结果。然后,我们使用thenApplyAsync方法注册一个回调函数,该函数会接收异步任务的结果,将其转换成一个字符串,并返回一个新的CompletableFuture对象。最后,我们使用thenAcceptAsync方法注册另一个回调函数,该函数会接收上一个CompletableFuture的结果,并将其打印出来。

结论

回调在软件开发中无处不在,广泛用于工具、设计模式和应用程序中。有时我们会在不知不觉中使用它们。

我们已经介绍了各种常见的回调实现,以帮助展示它们在 Java 代码中的实用性和多功能性。以下是需要记住的回调的一些特性:

  • 在方法中我们将会调函数进行传递,并在需要执行的地方执行该调回函数。
  • 回调函数可以是同步的,这意味着它必须在其他操作之后立即执行,没有任何延迟。
  • 回调函数可以是异步的,这意味着它可以在后台执行,并且可能需要一些时间才能执行。