CompletableFuture
的 thenCompose
和 thenApply
方法都是用来处理异步计算结果的,但它们在处理方式上有很重要的区别。
1.区别
-
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)。
2.案例
案例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)); // 使用 thenApply 对结果进行映射
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)); // 使用 thenCompose 链接另一个异步操作
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"))
);
// 使用flatMap扁平化嵌套的作者列表,并使用distinct去重
List<String> uniqueAuthors = books.stream()
.flatMap(book -> book.getAuthors().stream())
.distinct()
.collect(Collectors.toList());
// 打印出所有不重复的作者
System.out.println(uniqueAuthors);
// --------------------------------------------------------------------
// 如果不使用flatMap
// 使用嵌套循环和HashSet去重
Set<String> uniqueAuthorsSet = new HashSet<>();
for (Book book : books) {
List<String> authors = book.getAuthors();
// 如果是复杂操作的话,这边其实还要有个循环 可见复杂性
uniqueAuthorsSet.addAll(authors);
}
// 将Set转换为List
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='" + name + ''' +
'}';
}
}
public class Test4 {
public static CompletableFuture<User> getUserDetail(String userName) {
// 模拟异步API调用
// try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}
return CompletableFuture.supplyAsync(() -> new User(userName));
}
public static CompletableFuture<String> getDefaultScore(User user) {
// 模拟异步API调用
// try {TimeUnit.SECONDS.sleep(5);} catch (InterruptedException e) {throw new RuntimeException(e);}
return CompletableFuture.supplyAsync(() -> user.getName()+" default score = "+700);
}
public static void main(String[] args) {
// 先异步获取一个学生,然后异步获取这个学生默认的分数
CompletableFuture<CompletableFuture<String>> nestedResult =getUserDetail("Bob")
.thenApply(user -> getDefaultScore(user));
//----------------------不使用compuse--------------------------------
// 如果是不用compose,因为存在嵌套,需要join两次才能得到最终结果
String score = nestedResult.join().join();
// 或者我们手动解开这个两层嵌套,处理结果
// nestedResult.thenAccept(user->{
// user.thenAccept(score->{
// System.out.println(score);
// });
// });
//----------------------使用compuse--------------------------------
// 但是 如果是使用compose 就能够直接解开这个嵌套获取到对应结果
CompletableFuture<String> future = getUserDetail("Alice")
.thenCompose(user -> getDefaultScore(user));
String score = future.join();
System.out.println(score);
// 或者直接处理结果
// future.thenAccept(res->{
// System.out.println(res);
// });
}
}
通过这个例子,我们可以更加直观的理解compose的作用,也解释了为什么说thenCompose
类似于 flatMap
操作,用于链接多个异步操作,避免嵌套的 CompletableFuture
结构。
3.总结
1.功能上:thenApply
类似于 map
操作,用于对结果进行映射;而 thenCompose
类似于 flatMap
操作,用于链接多个异步操作,避免嵌套的 CompletableFuture
结构。
2.使用上:thenApply
适用于同步结果转换,而thenCompose
适用于链接多个异步操作。这两个方法对于处理异步操作链中的不同阶段非常有用,选择哪个方法取决于你的具体需求: 是否需要进行异步操作链接(compose),或者仅仅需要对结果进行转换(apply)。