当前位置: 首页 > 知识库问答 >
问题:

如何用CompletionStage的集合很好地完成allOf/AnyOf

罗毅
2023-03-14

目前,要用CompletionStage的集合做一些简单的事情,需要跨越几道丑陋的关卡:

public static CompletionStage<String> translate(String foo) {
    // just example code to reproduce
    return CompletableFuture.completedFuture("translated " + foo);
}

public static CompletionStage<List<String>> translateAllAsync(List<String> input) {
    List<CompletableFuture<String>> tFutures = input.stream()
        .map(s -> translate(s)
            .toCompletableFuture())
        .collect(Collectors.toList()); // cannot use toArray because of generics Arrays creation :-(
    return CompletableFuture.allOf(tFutures.toArray(new CompletableFuture<?>[0])) // not using size() on purpose, see comments
        .thenApply(nil -> tFutures.stream()
            .map(f -> f.join())
            .map(s -> s.toUpperCase())
            .collect(Collectors.toList()));
}

我想写的是:

public CompletionStage<List<String>> translateAllAsync(List<String> input) {
    // allOf takes a collection< futures<X>>, 
    // and returns a future<collection<x>> for thenApply()
    return XXXUtil.allOf(input.stream() 
            .map(s -> translate(s))
            .collect(Collectors.toList()))
        .thenApply(translations -> translations.stream()
            .map(s -> s.toUpperCase())
            .collect(Collectors.toList()));
}

关于完成未来并转换为数组和连接的整个仪式都是样板文件,分散了对实际代码语义的注意力。

可能有一个版本的allOf()返回< code>Future

我可以自己尝试实现XXXUtil,但我想知道是否已经有一个成熟的3rdparty库来解决这个问题和类似的问题(例如Spotify的CompletableFutures)。如果是这样,我想看看这样一个库的等效代码作为答案。

或者也许上面发布的原始代码可以以不同的方式更简洁地编写?

JUnit测试代码:

@Test
public void testTranslate() throws Exception {
    List<String> list = translateAllAsync(Arrays.asList("foo", "bar")).toCompletableFuture().get();
    Collections.sort(list);
    assertEquals(list,
        Arrays.asList("TRANSLATED BAR", "TRANSLATED FOO"));
}

共有1个答案

戴品
2023-03-14

我刚刚查看了CompletableFuture.allOf的源代码,发现它基本上创建了一个节点的二叉树,一次处理两个阶段。我们可以轻松实现类似的逻辑,而无需显式使用toCompletableFuture()并一次性处理结果列表生成:

public static <T> CompletionStage<List<T>> allOf(
                  Stream<? extends CompletionStage<? extends T>> source) {
    return allOf(source.collect(Collectors.toList()));
}
public static <T> CompletionStage<List<T>> allOf(
                  List<? extends CompletionStage<? extends T>> source) {
    int size = source.size();
    if(size == 0) return CompletableFuture.completedFuture(Collections.emptyList());
    List<T> result = new ArrayList<>(Collections.nCopies(size, null));
    return allOf(source, result, 0, size-1).thenApply(x -> result);
}
private static <T> CompletionStage<Void> allOf(
                   List<? extends CompletionStage<? extends T>> source,
                   List<T> result, int from, int to) {
    if(from < to) {
        int mid = (from+to)>>>1;
        return allOf(source, result, from, mid)
            .thenCombine(allOf(source, result, mid+1, to), (x,y)->x);
    }
    return source.get(from).thenAccept(t -> result.set(from, t));
}

就是这样。

您可以使用此解决方案将问题代码的逻辑实现为

public static CompletionStage<List<String>> translateAllAsync(List<String> input) {
    return allOf(input.stream().map(s -> translate(s)))
        .thenApply(list -> list.stream()
            .map(s -> s.toUpperCase())
            .collect(Collectors.toList()));
}

虽然用起来会更自然

public static CompletionStage<List<String>> translateAllAsync(List<String> input) {
    return allOf(input.stream().map(s -> translate(s).thenApply(String::toUpperCase)));
}

请注意,此解决方案维护顺序,因此无需在测试用例中对结果进行排序:

@Test
public void testTranslate() throws Exception {
    List<String> list = translateAllAsync(Arrays.asList("foo", "bar")).toCompletableFuture().get();
    assertEquals(list, Arrays.asList("TRANSLATED FOO", "TRANSLATED BAR"));
}
 类似资料:
  • 问题内容: 像Go这样的类型,并且不能存储null值,因此我发现可以为此使用sql.NullInt64和sql.NullString。 但是,当我在Struct中使用它们,并使用json包从Struct生成JSON时,格式与使用常规和类型时不同。 JSON具有附加级别,因为sql.Null ***也是Struct。 有没有很好的解决方法,还是应该在我的SQL数据库中不使用NULL? 问题答案: 像

  • 你能帮忙找出哪一部分错了吗,多谢。:)

  • 基本上,它想要完成的就是找到胜率最多的球队。这是通过对每个球员的制胜球进行合计,并根据他们来自哪一支球队来判断,为该队计数来发现的。 我通过遍历所有玩家并找到唯一的队名来创建作为队对象的队列表来实现这一点。 然后我查了一下球员名单,如果球员所在的球队和现在的球队一样,就会为他们的制胜球加分。 一个小任务总共有四个for循环。看起来很恶心。

  • 我正在生成Restendpoint,包括向生成的代码添加Openapi/Swagger注释。 虽然它可以很好地处理基本类型,但我在自定义类方面有一些问题。 现在我有很多自定义类的重复模式条目(使用@Schema(实现=MyClass.class)),但至少需要的信息在那里。然而,我想找到一种方法来删除重复的模式条目,同时保留附加信息。 在一个讨论$ref和缺乏兄弟属性的github问题上,我发现了

  • 我们在Tomcat上部署了一个Grails应用程序,该应用程序部署在终止SSL的负载均衡器后面(负载均衡器然后与端口8080上的tomcat实例通信)。我们已将SpringSecurity配置为需要所有资源上的安全通道,注意来自负载均衡器的标头,强制https并从负载均衡器映射端口: 大部分工作正常-Grails中的重定向按预期使用https协议,以及大多数ajax请求。 但是,有些ajax请求无

  • 我有一个映射集合,其中包含productId、数量和订单数量的列表,格式为productId=[quantity,no of orders]。地图收藏就像 对于每个productId,都有数量和数量的订单。代码是用java返回的。我的要求是,我想将订单的ProductID、数量和编号分成3个不同的列表,如 订单的数量和编号应该与它们相关的产品Id相匹配。我如何在java中实现这一点?