Java 8 thenApply()和thenAccept()

时间:2016-04-02 19:24:07

标签: concurrency java-8

我正在创建一个链接CompleteableFuture操作的演示示例,我认为我很接近,但有些东西我不知道。除了main()中的最终“并行构建蛋糕”条款外,所有内容都会编译:

import java.util.*;
import java.util.concurrent.*;
import java.util.function.*;
import java.util.stream.*;
import java.time.*;

// Use decorator pattern to build up the cake:

interface Cake_ {
  String describe();
}

class Cake implements Cake_ {
  private int id;
  public Cake(int id) { this.id = id; }
  @Override
  public String describe() {
    return "Cake " + id;
  }
}

abstract class Decorator implements Cake_ {
  protected Cake_ cake;
  public Decorator(Cake_ cake) {
    this.cake = cake;
  }
  @Override
  public String describe() {
    return cake.describe();
  }
  @Override
  public String toString() {
    return describe();
  }
}

class Frosted extends Decorator {
  public Frosted(Cake_ cake) {
    super(cake);
  }
  @Override
  public String describe() {
    return cake.describe() + " Frosted";
  }
}

class Decorated extends Decorator {
  public Decorated(Cake_ cake) {
    super(cake);
  }
  @Override
  public String describe() {
    return cake.describe() + " Decorated";
  }
}

// For the cake-building assembly line:

class CreateCakes implements Supplier<Cake> {
  private int id;
  public CreateCakes(int id) {
    this.id = id;
  }
  @Override
  public Cake get() {
    return new Cake(id);
  }
}

class FrostCakes implements Function<Cake, Frosted> {
  @Override
  public Frosted apply(Cake cake) {
    return new Frosted(cake);
  }
}

class DecorateCakes implements Consumer<Frosted> {
  public Decorated result;
  @Override
  public void accept(Frosted fc) {
    result = new Decorated(fc);
  }
}

public class Test {
  public static int NUM_OF_CAKES = 20;
  public static void main(String[] args) {
    // Change from the default number of threads:
    System.setProperty(
      "java.util.concurrent.ForkJoinPool" +
      ".common.parallelism", "" + NUM_OF_CAKES);

    // Test/demonstrate the decorator pattern:
    List<Cake_> decorated =
      IntStream.range(0, NUM_OF_CAKES)
        .mapToObj(Cake::new)
        .map(Frosted::new)
        .map(Decorated::new)
        .collect(Collectors.toList());
    decorated.forEach(System.out::println);

    // Build cakes in parallel:
    List<CompletableFuture<?>> futures =
      IntStream.range(0, NUM_OF_CAKES)
        .mapToObj(id -> new CreateCakes(id))
        .map(CompletableFuture::supplyAsync)
        .thenApply(new FrostCakes())
        .thenAccept(new DecorateCakes())
        .collect(Collectors.toList());
    futures.forEach(CompletableFuture::join);
  }
}

我意识到我在futures列表的定义中缺少一些基本的理解,但我已经将它包含在内,以显示我尝试在这里完成的任务:一个蛋糕工厂与蛋糕创建过程的一部分并行运行。

1 个答案:

答案 0 :(得分:3)

对您的问题的快速回答是thenApply行无法编译,因为上面一行(map(CompletableFuture::supplyAsync))的结果返回Stream<CompletableFuture<Cake>>而不是CompletableFuture<Cake> 。 您需要执行map(cakeFuture -> cakeFuture.thenApply(new FrostCakes()))之类的操作。

但我认为需要做出更重要的一点。

如果您的示例仅用于教育目的,我建议您再做一两天的准备工作,更具体地说是阅读有关流操作的基础知识和CompletableFuture

通过这种方式,当您展示材料时,您会感到更自信,但更重要的是,您不会提供不完美的代码示例,这些示例可能会损害您的同事/学生关于如何流和{ {1}} s(甚至装饰器)。

我会指出一些我认为需要在你的例子中重做的事情。

  1. 手动设置公共CompletableFuture的并行度级别并不总是一个好主意。默认情况下,它使用ForkJoinPool返回的处理器数量,这是一个非常好的默认值。您需要有一个很好的理由将其更改为更多内容,因为在大多数情况下,您只会在调度和维护冗余线程时引入不必要的开销。将其更改为您计划触发的任务数量几乎总是一个坏主意(解释省略)。
  2. 您的流示例执行几个流操作,然后使用集合终止,然后对收集的列表执行流操作。通过直接在最后一个映射返回的流上应用Runtime.availableProcessors(),可以在不收集列表的情况下重写它们,并且可以说这将更好地演示使用Java 8流的流畅编程模型。
  3. 您的示例也不会并行执行操作。您可以在forEach之后添加.parallel()来轻松解决此问题,但除非您从上面删除要列出的冗余集合,否则您将无法通过打印看到您已完成并行操作列表内容为IntStream.range()
  4. 实现forEach接口的类不是非常惯用的。我认为应该用相应的lambda表达式替换它们,即使在你的情况下这可能会导致lambda中有一个满口的lambda,除非你的第二个例子稍微被重写。
  5. java.util.function会返回CompletableFuture.thenAccept,因此在流处理的这个阶段,您将失去对已创建,磨砂和装饰蛋糕的引用。如果您不关心它们,或者如果您在装饰逻辑中记录了一些关于它们的内容,这可能没问题,但您的示例很容易误导人们最终收集的CompletableFuture<Void>列表可用于覆盖蛋糕(毕竟,谁也不想吃蛋糕而且也吃它)。
  6. 所以你的第一个例子看起来像

    CompletableFuture

    请注意由于并行执行而未对蛋糕ID进行排序。