MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Java CompletableFuture thenApply链式转换操作解析

2021-01-122.8k 阅读

Java CompletableFuture 的 thenApply 链式转换操作解析

在Java的异步编程领域,CompletableFuture 是一个强大的工具,它提供了一种异步执行任务、处理异步结果以及将多个异步操作链接在一起的便捷方式。thenApply 方法作为 CompletableFuture 众多方法中的一员,在链式操作中扮演着非常关键的角色,它允许我们对 CompletableFuture 的结果进行转换,并返回一个新的 CompletableFuture。接下来,我们将深入剖析 thenApply 方法的工作原理、应用场景以及如何通过链式操作构建复杂的异步任务流程。

1. thenApply 基础概念

thenApply 方法定义在 CompletableFuture 类中,其方法签名如下:

<U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)

这里,T 是当前 CompletableFuture 的结果类型,U 是经过 fn 函数转换后新的 CompletableFuture 的结果类型。fn 是一个 Function 接口的实现,它接收当前 CompletableFuture 的结果作为参数,并返回一个新的结果,从而创建一个新的 CompletableFuture,其结果就是 fn 函数的返回值。

简单来说,thenApply 方法允许我们在一个 CompletableFuture 完成后,对其结果进行处理,并将处理结果包装在一个新的 CompletableFuture 中返回。

2. 简单示例

我们通过一个简单的例子来理解 thenApply 的基本用法。假设我们有一个异步任务,该任务计算两个整数的和,然后将这个和乘以 2。

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class ThenApplyExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 2 + 3)
              .thenApply(result -> result * 2);

        Integer finalResult = future.get();
        System.out.println("Final result: " + finalResult);
    }
}

在上述代码中:

  • CompletableFuture.supplyAsync(() -> 2 + 3) 创建了一个异步任务,该任务在一个线程池中异步执行,计算 2 + 3 的结果。
  • thenApply(result -> result * 2) 对前一个异步任务的结果进行处理,将结果乘以 2。

最终,通过 future.get() 获取整个异步操作链的最终结果,并打印输出。

3. thenApply 链式操作原理

CompletableFuture 的链式操作是基于事件驱动和回调机制实现的。当一个 CompletableFuture 完成时(无论是正常完成还是异常完成),它会触发注册在其上的回调函数。thenApply 方法注册的回调函数就是传入的 Function

具体来说,当 CompletableFuture.supplyAsync(() -> 2 + 3) 这个异步任务完成后,它会调用 thenApply 注册的 Function(即 result -> result * 2)。这个 Function 接收前一个任务的结果作为输入,并返回新的结果。thenApply 方法会将这个新的结果包装在一个新的 CompletableFuture 中返回。

这种链式操作的优势在于,我们可以将多个异步操作串联起来,每个操作依赖前一个操作的结果,而不需要手动管理线程的同步和阻塞。

4. 复杂链式操作示例

接下来,我们通过一个更复杂的示例来展示 thenApply 在实际应用中的链式操作。假设我们有一个电商系统,需要完成以下操作:

  1. 根据用户ID查询用户信息。
  2. 根据用户信息中的地址查询该地址的运费。
  3. 根据运费和商品价格计算总价格。
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

class User {
    private String address;

    public User(String address) {
        this.address = address;
    }

    public String getAddress() {
        return address;
    }
}

class ShippingCostCalculator {
    public static double calculateShippingCost(String address) {
        // 这里假设根据地址计算运费的逻辑
        if ("USA".equals(address)) {
            return 10.0;
        } else {
            return 20.0;
        }
    }
}

public class ComplexThenApplyExample {
    public static CompletableFuture<User> getUserInfoAsync(int userId) {
        return CompletableFuture.supplyAsync(() -> {
            // 模拟根据用户ID查询用户信息的逻辑
            if (userId == 1) {
                return new User("USA");
            } else {
                return new User("Other");
            }
        });
    }

    public static CompletableFuture<Double> calculateShippingCostAsync(User user) {
        return CompletableFuture.supplyAsync(() -> ShippingCostCalculator.calculateShippingCost(user.getAddress()));
    }

    public static double calculateTotalPrice(double shippingCost, double productPrice) {
        return shippingCost + productPrice;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        double productPrice = 50.0;
        CompletableFuture<Double> totalPriceFuture = getUserInfoAsync(1)
              .thenApplyAsync(User::getAddress)
              .thenApplyAsync(ShippingCostCalculator::calculateShippingCost)
              .thenApplyAsync(shippingCost -> calculateTotalPrice(shippingCost, productPrice));

        Double totalPrice = totalPriceFuture.get();
        System.out.println("Total price: " + totalPrice);
    }
}

在上述代码中:

  • getUserInfoAsync(int userId) 方法模拟异步查询用户信息,返回一个 CompletableFuture<User>
  • calculateShippingCostAsync(User user) 方法模拟异步计算运费,返回一个 CompletableFuture<Double>
  • calculateTotalPrice(double shippingCost, double productPrice) 方法计算总价格。

通过 thenApplyAsync(与 thenApply 类似,只是异步执行 Function)将这些操作链接在一起,形成一个完整的异步任务链。

5. thenApply 与其他相关方法的比较

CompletableFuture 提供了一系列与 thenApply 类似的方法,它们在功能上有一些差异,需要根据具体需求选择使用。

thenApply 与 thenAcceptthenAccept 方法用于在 CompletableFuture 完成时执行一个副作用操作,它不返回新的结果。其方法签名如下:

CompletableFuture<Void> thenAccept(Consumer<? super T> action)

这里,action 是一个 Consumer 接口的实现,它接收 CompletableFuture 的结果作为参数,但不返回值。例如:

CompletableFuture.supplyAsync(() -> "Hello")
      .thenAccept(result -> System.out.println("Result: " + result));

thenApply 与 thenRunthenRun 方法在 CompletableFuture 完成时执行一个无参数的 Runnable。它也不返回新的结果,主要用于执行一些与前一个任务结果无关的操作。其方法签名如下:

CompletableFuture<Void> thenRun(Runnable action)

例如:

CompletableFuture.supplyAsync(() -> "Hello")
      .thenRun(() -> System.out.println("Task completed"));

thenApply 与 handlehandle 方法与 thenApply 类似,但它可以处理 CompletableFuture 的正常结果和异常情况。其方法签名如下:

<U> CompletableFuture<U> handle(BiFunction<? super T, Throwable,? extends U> fn)

这里,fn 是一个 BiFunction,它接收 CompletableFuture 的结果(如果有)和异常(如果有)作为参数,并返回一个新的结果。例如:

CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) {
        throw new RuntimeException("Simulated exception");
    }
    return "Success";
})
.handle((result, ex) -> {
    if (ex != null) {
        System.out.println("Exception: " + ex.getMessage());
        return "Default value";
    }
    return result;
})
.thenAccept(System.out::println);

6. 异常处理

在使用 thenApply 进行链式操作时,异常处理是非常重要的。如果在 thenApply 链中的某个环节抛出异常,整个链式操作会中断。CompletableFuture 提供了几种处理异常的方法。

使用 exceptionally 方法exceptionally 方法用于在 CompletableFuture 出现异常时提供一个默认值或执行恢复操作。其方法签名如下:

CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn)

例如:

CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) {
        throw new RuntimeException("Simulated exception");
    }
    return "Success";
})
.thenApply(result -> result.toUpperCase())
.exceptionally(ex -> {
    System.out.println("Exception: " + ex.getMessage());
    return "Default value";
})
.thenAccept(System.out::println);

使用 whenComplete 方法whenComplete 方法可以在 CompletableFuture 完成时(无论是正常完成还是异常完成)执行一个回调函数。其方法签名如下:

CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action)

例如:

CompletableFuture.supplyAsync(() -> {
    if (Math.random() > 0.5) {
        throw new RuntimeException("Simulated exception");
    }
    return "Success";
})
.thenApply(result -> result.toUpperCase())
.whenComplete((result, ex) -> {
    if (ex != null) {
        System.out.println("Exception: " + ex.getMessage());
    } else {
        System.out.println("Result: " + result);
    }
});

7. 性能考虑

在使用 thenApply 进行链式操作时,性能是一个需要考虑的因素。虽然 CompletableFuture 提供了强大的异步编程能力,但如果链式操作过于复杂或不合理,可能会导致性能问题。

  • 线程池的使用CompletableFuture 默认使用 ForkJoinPool.commonPool() 来执行异步任务。如果任务数量过多或任务执行时间过长,可能会导致线程池饱和,影响性能。可以通过创建自定义的 Executor 来控制线程池的大小和行为。
  • 减少不必要的转换:在链式操作中,尽量减少不必要的 thenApply 调用,避免过度转换数据。每次 thenApply 调用都会带来一定的开销,包括函数调用和新 CompletableFuture 的创建。

8. 应用场景

thenApply 链式转换操作在实际开发中有广泛的应用场景,例如:

  • 数据处理流水线:在大数据处理场景中,常常需要对数据进行一系列的转换和处理,thenApply 可以方便地构建数据处理流水线。
  • 微服务调用链:在微服务架构中,一个业务操作可能需要调用多个微服务,thenApply 可以将这些微服务调用链接在一起,实现异步的服务调用链。
  • 异步计算和转换:当需要对异步计算的结果进行进一步的转换和处理时,thenApply 是一个非常合适的工具。

9. 总结

CompletableFuturethenApply 方法是 Java 异步编程中的一个强大工具,它允许我们对异步任务的结果进行转换,并通过链式操作构建复杂的异步任务流程。通过深入理解 thenApply 的工作原理、与其他相关方法的比较、异常处理以及性能考虑,我们可以在实际开发中更加灵活和高效地使用它,提升系统的性能和响应能力。在不同的应用场景中,合理运用 thenApply 链式操作,可以使我们的代码更加简洁、可读,并且能够充分利用多核处理器的优势,实现高效的异步编程。

在使用 thenApply 时,需要根据具体需求选择合适的方法,并注意异常处理和性能优化,以确保系统的稳定性和高效性。通过不断实践和总结经验,我们可以更好地掌握 thenApply 以及 CompletableFuture 其他方法的使用技巧,为构建高性能的Java应用程序打下坚实的基础。

以上,我们对 Java CompletableFuture thenApply 链式转换操作进行了全面深入的解析,希望能帮助读者在实际项目中灵活运用这一强大的异步编程工具。