如何在 Java 中使用回调? | infoworld


Java 中的回调操作是一个函数传递给另一个函数并在某个操作完成后执行。回调可以同步或异步执行。

  • 在同步回调的情况下,一个函数紧接着另一个执行。
  • 在异步回调的情况下,一个函数在一段不确定的时间后执行,并且与其他函数没有特定的顺序发生。

匿名内部类回调

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函数接口和一个匿名内部类(没有名称的实现)来实现该accept()方法。
我们将Consumer接口传递给performAction()方法,然后accept()在操作完成后调用方法。

一旦accept()方法被实现,我们将从performAction方法中执行动作;然后我们将从消费者接口中执行accept()方法。


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 方法中传递了 Runnable 功能接口。因此,我们能够在performAction方法的动作完成后覆盖并执行run()方法。

以上是同步调用,下面是异步调用:

简单的线程回调
让我们从最简单的方式开始,我们可以进行这种异步回调的操作。在下面的代码中,首先我们将从一个Runnable功能接口中实现run()方法。然后,我们将创建一个线程,并在该线程中使用我们刚刚实现的run()方法。最后,我们将启动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...

注意在上面的代码中,首先我们从Runnable中创建了一个run()方法的实现。然后,我们调用了performAsynchronousAction()方法,将runnable功能接口与run()方法的实现一起传递。

在performAsynchronousAction()中,我们传递了runnable接口,并在Thread内部用lambda实现了其他Runnable接口。然后我们打印 "处理异步任务..."最后,我们调用我们通过参数传递的回调函数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回调
使用异步回调函数的另一种方式是使用CompletableFuture API。这个强大的API是在Java 8中引入的,便于执行和组合异步方法的调用。它可以完成我们在前面的例子中所做的一切,如创建一个新的线程,然后启动和管理它。

在下面的代码例子中,我们将创建一个新的CompletableFuture,然后我们将调用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代码中的效用和多功能性。下面是需要记住的回调的一些特点。

  • 一个回调函数应该在另一个动作执行时或与该动作并行执行。
  • 一个回调函数可以是同步的,这意味着它必须在另一个动作之后立即执行,没有任何延迟。
  • 回调函数可以是异步的,这意味着它可以在后台执行,可能需要一些时间才能执行。
  • 当一个动作发生时,可观察设计模式使用回调来通知感兴趣的实体,。