了解如何在 Java 中使用同步和异步回调,包括使用 lambda 表达式、CompletableFuture 等的回调。

如何在Java中使用回调_System

Java 中的回调操作是一个函数,它被传递给另一个函数,并在某个操作完成后执行。回调可以同步或异步执行。在同步回调的情况下,一个函数会紧接着另一个函数执行。在异步回调的情况下,函数在不确定的时间段后执行,并且与其他函数没有特定的顺序发生。

本文从Observable 设计模式中作为侦听器的回调的经典示例开始,向您介绍 Java 中的回调。您将看到各种同步和异步回调实现的示例,包括使用CompletableFuture.

Java 中的同步回调

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


正如我提到的,回调函数的示例可以在Observable 设计模式中找到。在需要单击按钮才能启动某些操作的 UI 中,我们可以将回调函数作为该按钮单击的侦听器传递。监听器函数等待按钮被单击,然后执行监听器回调。

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

匿名内部类回调

每当我们将带有方法实现的接口传递给 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("Action is being performed...");
    consumer.accept("Callback is executed");
  }

}

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

Action is being performed... 

Callback is executed...

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

您可能还注意到使用匿名内部类非常冗长。使用 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();
  }

}

输出再次表明正在执行操作并执行回调。

在此示例中,您可能会注意到我们RunnableperformAction方法中传递了函数接口。因此,我们能够在方法run()的操作performAction完成后覆盖并执行该方法。

异步回调

通常,我们希望使用异步回调方法,这意味着将在操作之后调用但与其他进程异步调用的方法。当不需要在其他进程之后立即调用回调方法时,这可能有助于提高性能。

简单的线程回调

让我们从进行异步回调调用操作的最简单方法开始。在下面的代码中,首先我们将从函数式接口实现该run()方法Runnable。然后,我们将创建一个Thread并使用run()我们刚刚在Thread. 最后,我们将开始Thread异步执行:

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()我们传递接口并使用 lambdarunnable实现另一个Runnable接口。然后我们打印“正在处理异步任务…”最后,我们调用通过参数传递的Thread回调函数,打印“回调已执行…”run

异步并行回调

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

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

// 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 回调

使用异步回调函数的另一种方法是使用CompletableFutureAPI。Java 8 中引入的这个强大的 API 有助于执行和组合异步方法调用。它完成了我们在前面的示例中所做的一切,例如创建一个新的Thread然后启动并管理它。

在下面的代码示例中,我们将创建一个新的CompletableFuture,然后我们将调用supplyAsync传递String.

接下来,我们将创建另一个回调函数,CompletableFuturethenApply我们配置的第一个函数一起执行:

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 设计模式使用回调在发生操作时通知感兴趣的实体。