如何让CompletableFuture的exceptionally方法返回Collection类型?
项目中有以下简化后的Java代码(JDK 11),原本运行正常:
CompletionStage<Collection<String>> fn1(String prefix) { return fn2() .thenApply(list -> list.stream() .map(s -> prefix + s) .collect(Collectors.toList()) ); } CompletionStage<List<String>> fn2() { return CompletableFuture.completedFuture(List.of("results", "of", "computation")); }
但为fn1添加exceptionally错误处理分支后,代码无法编译:
CompletionStage<Collection<String>> fn1(String prefix) { return fn2() .thenApply(list -> list.stream() .map(s -> prefix + s) .collect(Collectors.toList()) ).exceptionally(t -> { // 记录错误逻辑 // ... // 返回空集合时编译报错 Collection<String> emptyCol = List.of(); return emptyCol; // 错误:Collection<String>无法转换为List<String> // 尝试其他写法仍报错 // List<String> emptyList = List.of(); // return emptyList; // 错误:CompletionStage<List<String>>无法转换为CompletionStage<Collection<String>> // return emptyList.stream().collect(Collectors.toList()); // 错误同上 }); }
要求不修改fn1的签名,如何修复?同时解释为什么.collect(Collectors.toList())在thenApply中能正常工作。
核心问题是泛型类型的协变匹配问题,只需显式约束thenApply的返回类型为Collection<String>,让编译器明确链式调用的泛型上下文即可。推荐两种修复方式:
方式一:显式指定thenApply的泛型类型
CompletionStage<Collection<String>> fn1(String prefix) { return fn2() // 强制指定thenApply的泛型参数为Collection<String> .<Collection<String>>thenApply(list -> list.stream() .map(s -> prefix + s) .collect(Collectors.toList()) ).exceptionally(t -> { // 记录错误 // ... // 返回空集合,此时编译器识别为Collection<String>类型 return List.of(); }); }
方式二:在thenApply的lambda中显式返回Collection<String>
CompletionStage<Collection<String>> fn1(String prefix) { return fn2() .thenApply(list -> { // 显式声明返回值为Collection<String> Collection<String> result = list.stream() .map(s -> prefix + s) .collect(Collectors.toList()); return result; }).exceptionally(t -> { // 记录错误 // ... return List.of(); }); }
1. thenApply中正常编译的原因
fn2()返回CompletionStage<List<String>>,调用thenApply后,lambda返回的List<String>(Collectors.toList()在JDK11中返回List)是Collection<String>的子类。Java泛型允许返回值协变:CompletionStage<List<String>>可以自动向上转型为CompletionStage<Collection<String>>,以匹配fn1的返回类型。编译器会自动完成这个隐式转换,因此原本的代码无需额外处理就能编译。
2. 添加exceptionally后编译失败的原因
exceptionally方法的泛型规则要求:它的返回值类型必须与调用它的CompletionStage的泛型类型完全一致。
在未显式约束thenApply泛型的情况下,thenApply返回的是CompletionStage<List<String>>,此时exceptionally的lambda必须返回List<String>。但我们要么返回了父类型Collection<String>(类型不兼容),要么试图让整个链式调用的结果直接转为CompletionStage<Collection<String>>(泛型协变无法在链式调用的中间步骤自动完成),因此触发编译错误。
当显式指定thenApply的泛型为Collection<String>后,thenApply返回的是CompletionStage<Collection<String>>,此时exceptionally的lambda只需返回Collection<String>类型的实例即可——List.of()返回的List<String>是Collection<String>的子类,完全符合类型要求,编译自然通过。
内容的提问来源于stack exchange,提问作者shiuu

