为什么编译器提示“无法将CompletableFuture<Object>转换为CompletableFuture<String>”?

3

我正在尝试使用CompletableFuture链接一些文件处理过程,它应该返回一个CompletableFuture<String>

CompletableFuture<String> allGen = loadFile1().thenApply(params1 -> {

    CompletableFuture<String> gen1 = loadFile2().thenApply(params2 -> {
        return generateResultFile1(params1, params2);
    });

    CompletableFuture<String> gen2 = loadFile3().thenApply(params3 -> {
        return generateResultFile2(params1, params3);
    });

    return CompletableFuture
        .allOf(gen1, gen2)
            .thenApply(r -> Stream.of(gen1, gen2).map(CompletableFuture::join).collect(joining(",")));
});

我知道CompletableFuture.allOf()返回CompletableFuture<Void>,所以我在它的thenApply()方法中生成了一个字符串...

但是为什么编译器会假定我在这里生成了一个CompletableFuture<Object>

我错过了什么,请问?

顺便问一下,有更好的链接方法吗?

1个回答

3
你的主句是:
CompletableFuture<String> allGen = loadFile1().thenApply(params1 -> {
    …
});

所以指定的函数应该返回一个`String`。但是你的代码试图返回一个`CompletableFuture<String>`,因为`Stream.of(gen1, gen2).map(CompletableFuture::join).collect(joining(","))`产生了一个`String`,而你正在在`return CompletableFuture.allOf(gen1, gen2).thenApply(r -> …);`中使用这个表达式。
在泛型代码中,编译器错误消息通常是非常无用的。
最简单的解决方法(改动最小)是使用`thenCompose`而不是`thenApply`,允许函数返回一个`CompletableFuture`。
CompletableFuture<String> allGen = loadFile1().thenCompose(params1 -> {

    CompletableFuture<String> gen1 = loadFile2().thenApply(params2 -> {
        return generateResultFile1(params1, params2);
    });

    CompletableFuture<String> gen2 = loadFile3().thenApply(params3 -> {
        return generateResultFile2(params1, params3);
    });

    return CompletableFuture.allOf(gen1, gen2)
        .thenApply(r -> Stream.of(gen1, gen2)
            .map(CompletableFuture::join).collect(joining(",")));
});

然而,有利用简化语法的机会。

CompletableFuture<String> allGen = loadFile1().thenCompose(params1 -> {
    CompletableFuture<String> gen1 = loadFile2()
        .thenApply(params2 -> generateResultFile1(params1, params2));

    CompletableFuture<String> gen2 = loadFile3()
        .thenApply(params3 -> generateResultFile2(params1, params3));

    return CompletableFuture.allOf(gen1, gen2)
        .thenApply(r -> Stream.of(gen1, gen2)
            .map(CompletableFuture::join).collect(joining(",")));
});

如果代码总是将恰好两个结果合并,那么可以使用更简单的方法:

CompletableFuture<String> allGen = loadFile1().thenCompose(params1 ->
    loadFile2().thenApply(params2 -> generateResultFile1(params1, params2))
        .thenCombine(
            loadFile3().thenApply(params3 -> generateResultFile2(params1, params3)),
            (s1, s2) -> String.join(",", s1, s2))
);

尽管有不同的嵌套形式,loadFile2().thenApply(…)loadFile3().thenApply(…)仍然是两个独立的操作,只有最终的(s1, s2) -> String.join(",", s1, s2)依赖于两者。
如果想使这更加清晰明了,请保留本地变量。
CompletableFuture<String> allGen = loadFile1().thenCompose(params1 -> {
    CompletableFuture<String> gen1
        = loadFile2().thenApply(params2 -> generateResultFile1(params1, params2));
    CompletableFuture<String> gen2
        = loadFile3().thenApply(params3 -> generateResultFile2(params1, params3));
    return gen1.thenCombine(gen2, (s1, s2) -> s1 + "," + s2);
});

如上例所示,您也可以将String.join(",", s1, s2)替换为s1 + "," + s2。后者会稍微更加高效,但由于它不太可能在整体性能方面占主导地位,因此这更多是一种口味问题。


关于您最后的改进:gen1.thenCombine(gen2会使得gen1gen2并行运行吗? - Cristiano
2
allOf一样,这对它们的完成没有任何影响。决定它们如何完成的是loadFile2()loadFile3()方法以及它们创建future的方式。allOfthenCombine(以及所有其他链接操作)两种方法都只会创建一个新的future,当传入的futures已经完成时,它才会被完成。如果您想确保generateResultFile1generateResultFile2在任何情况下都并行运行,则必须将thenApply替换为thenApplyAsync - Holger
啊,好的。我现在明白了。两种生成方法都有return CompletableFuture.supplyAsync()。我将在它们上面添加日志,以便查看使用哪些线程来执行它们。 - Cristiano

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接