本文由 简悦 SimpRead 转码, 原文地址 juejin.cn
CompletableFuture 的 thenCompose 和 thenApply 方法都是用来处理异步计算结果的,但它们在处理方式上有很重要的区别。
CompletableFuture 的 thenCompose 和 thenApply 方法都是用来处理异步计算结果的,但它们在处理方式上有很重要的区别。
-
thenApply
thenApply用于将前一个异步计算的结果进行转换。你提供一个函数,这个函数接受前一个计算的结果作为输入,并返回一个新的值。这个方法不会创建另一个CompletableFuture,它只是返回一个将计算结果应用函数后的新CompletableFuture。使用
thenApply的例子:CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello") CompletableFuture<String> greetingFuture = future.thenApply(s -> s + " World!") // greetingFuture 完成时会得到 "Hello World!" -
thenCompose
thenCompose用于链接两个异步操作,当第一个操作完成时,将其结果作为参数传递给一个返回CompletableFuture的函数。这使得你可以创建一个平坦的结果链,即只有一个级别的CompletableFuture,而不是嵌套的CompletableFuture<CompletableFuture<T>>。使用
thenCompose的例子:CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> "Hello") CompletableFuture<String> greetingFuture = future.thenCompose(s -> CompletableFuture.supplyAsync(() -> s + " World!")) // greetingFuture 完成时会得到 "Hello World!"
简单来说,thenApply 类似于 map 操作,用于对结果进行映射;而 thenCompose 类似于 flatMap 操作,用于链接多个异步操作,避免嵌套的 CompletableFuture 结构。
这两个方法对于处理异步操作链中的不同阶段非常有用,选择哪个方法取决于你的具体需求: 是否需要进行异步操作链接(compose),或者仅仅需要对结果进行转换(apply)。
案例 1
假设我们有一个在线书店,我们需要首先根据用户的 ID 异步获取用户信息,然后再根据用户信息异步获取推荐的书籍列表。
使用 thenApply(映射结果)
import java.util.concurrent.CompletableFuture;
public class BookStore {
public CompletableFuture<User> getUserInfo(String userId) {
return CompletableFuture.supplyAsync(() -> {
return new User(userId, "John Doe");
});
}
public List<Book> getRecommendedBooks(User user) {
return Arrays.asList(new Book("Book1"), new Book("Book2"));
}
public void displayRecommendedBooks(String userId) {
CompletableFuture<List<Book>> recommendedBooksFuture = getUserInfo(userId)
.thenApply(user -> getRecommendedBooks(user));
recommendedBooksFuture.thenAccept(books -> books.forEach(book -> System.out.println(book.getTitle())));
}
}
在这个例子中,thenApply 使用了从getUserInfo方法返回的User对象,并同步地调用了getRecommendedBooks方法来得到书籍列表。由于getRecommendedBooks方法是同步的,这里不会引入额外的异步操作。
使用 thenCompose(链接异步操作)
现在假设getRecommendedBooks也是一个异步操作,我们需要修改我们的方法来适应这一点。
import java.util.concurrent.CompletableFuture;
public class BookStore {
public CompletableFuture<User> getUserInfo(String userId) {
return CompletableFuture.supplyAsync(() -> {
return new User(userId, "John Doe");
});
}
public CompletableFuture<List<Book>> getRecommendedBooksAsync(User user) {
return CompletableFuture.supplyAsync(() -> {
return Arrays.asList(new Book("Book1"), new Book("Book2"));
});
}
public void displayRecommendedBooks(String userId) {
CompletableFuture<List<Book>> recommendedBooksFuture = getUserInfo(userId)
.thenCompose(user -> getRecommendedBooksAsync(user));
recommendedBooksFuture.thenAccept(books -> books.forEach(book -> System.out.println(book.getTitle())));
}
}
在这个修改后的例子中,我们使用thenCompose来链接两个异步操作:首先异步获取用户信息,然后在用户信息获取后,我们传递它到getRecommendedBooksAsync方法异步获取书籍列表。thenCompose确保我们有一个平坦的、不嵌套的CompletableFuture<List<Book>>,我们可以在其上注册一个动作来处理最终的推荐书籍列表。
通过这案例 1,你应该能够看到thenApply和thenCompose在实际应用中的区别:thenApply适用于同步结果转换,而thenCompose适用于链接多个异步操作。
案例 2
为什么说 thenCompose 和 flatMap 像呢?因为两者都是用来处理 “嵌套” 问题的。
flatMap 处理嵌套问题:
需求:假设我们有一个书籍列表,每本书籍有一个作者列表,现在我们需要将所有书籍的作者汇总到一个列表中,同时保证作者列表中没有重复项
class Book {
private String title;
private List<String> authors;
public Book(String title, List<String> authors) {
this.title = title;
this.authors = authors;
}
public List<String> getAuthors() {
return authors;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public void setAuthors(List<String> authors) {
this.authors = authors;
}
}
public class FlatMapTest {
public static void main(String[] args) {
List<Book> books = Arrays.asList(
new Book("Java 8 in Action", Arrays.asList("Raoul-Gabriel Urma", "Mario Fusco", "Alan Mycroft")),
new Book("Effective Java", Arrays.asList("Joshua Bloch")),
new Book("Clean Code", Arrays.asList("Robert C. Martin")),
new Book("Code Complete", Arrays.asList("Steve McConnell")),
new Book("The Pragmatic Programmer", Arrays.asList("Andy Hunt", "Dave Thomas")),
new Book("Clean Architecture", Arrays.asList("Robert C. Martin"))
);
List<String> uniqueAuthors = books.stream()
.flatMap(book -> book.getAuthors().stream())
.distinct()
.collect(Collectors.toList());
System.out.println(uniqueAuthors);
Set<String> uniqueAuthorsSet = new HashSet<>();
for (Book book : books) {
List<String> authors = book.getAuthors();
uniqueAuthorsSet.addAll(authors);
}
List<String> uniqueAuthorsList = new ArrayList<>(uniqueAuthorsSet);
System.out.println(uniqueAuthorsList);
}
}
thenCompose 处理嵌套问题:
需求:假设我们有两个异步操作: 1. 查询用户的个人信息(返回CompletableFuture<User>)。 2. 根据用户的个人信息获取用户的一个评分(返回CompletableFuture<String>)。
class User {
private String name;
public User(String name) {
this.name = name;
}
public String getName() {
return name;
}
@Override
public String toString() {
return "User{" +
" + name + ''' +
'}';
}
}
public class Test4 {
public static CompletableFuture<User> getUserDetail(String userName) {
return CompletableFuture.supplyAsync(() -> new User(userName));
}
public static CompletableFuture<String> getDefaultScore(User user) {
return CompletableFuture.supplyAsync(() -> user.getName()+" default score = "+700);
}
public static void main(String[] args) {
CompletableFuture<CompletableFuture<String>> nestedResult =getUserDetail("Bob")
.thenApply(user -> getDefaultScore(user));
String score = nestedResult.join().join();
CompletableFuture<String> future = getUserDetail("Alice")
.thenCompose(user -> getDefaultScore(user));
String score = future.join();
System.out.println(score);
}
}
通过这个例子,我们可以更加直观的理解 compose 的作用,也解释了为什么说thenCompose类似于flatMap操作,用于链接多个异步操作,避免嵌套的CompletableFuture结构。
-
功能上:
thenApply类似于map操作,用于对结果进行映射;而thenCompose类似于flatMap操作,用于链接多个异步操作,避免嵌套的CompletableFuture结构。 -
使用上:
thenApply适用于同步结果转换,而thenCompose适用于链接多个异步操作。这两个方法对于处理异步操作链中的不同阶段非常有用,选择哪个方法取决于你的具体需求:是否需要进行异步操作链接(compose),或者仅仅需要对结果进行转换(apply)。