在 Java 8 中使用先前链接的 thenCompose lambda 的值

Using values from previously chained thenCompose lambdas in Java 8

我的同事们喜欢的 Java 8 编码风格是一直链接异步调用,例如

CompletionStage<E> someMethod() {
    return doSomething().thenCompose(a -> {
      // ...
      return b;
    }).thenCompose(b -> {
      // ...
      return c;
    }).thenCompose(c -> {
      // ...
      return d;
    }).thenApply(d -> {
      // ...
      return e;
    });
}

我遇到了类似上述的问题,但还有一个额外的挑战:我需要在后面的 lambda 中回忆在某些 lambda 中检索到的值。例如,

CompletionStage<E> someMethod() {
    return doSomething().thenCompose(a -> {
      // ...
      Foo foo = fooDAO.getFoos(a);
      // ...
      return b;
    }).thenCompose(b -> {
      // ...
      return c;
    }).thenCompose(c -> {
      // ...
      Bar bar = barDAO.getBars(foo);
      // ...
      return d;
    }).thenApply(d -> {
      // ...
      return someResult(d, bar);
    });
}

当我在外部范围内声明 Foo foo;Bar bar; 时,我收到关于它们不是最终的或实际上不是最终的错误。我读到过有关使用包装器使它们有效地最终化的信息,但是这样做对我来说似乎很老套(我不明白为什么允许这样做...)

我读到 Java 8 没有添加对元组的支持(虽然它考虑了 BiVal,也许我可以用来传递给 BiFunction lambda)。所以我尝试使用对,例如

    return doSomething().thenCompose(a -> {
      // ...
      Foo foo = fooDAO.getFoos(a);
      // ...
      return new Pair<>(foo, b);
    }).thenCompose(fooAndB -> {

然后在我需要回忆的地方 foo,

      Foo foo = fooAndB.getKey();

但这感觉在语义上是错误的。而且,它不起作用!我不知道为什么,因为我认为 lambda 参数的范围与其外部范围相同,因此所有 lambda 参数都可以从稍后链接的 lambda 中访问。

lambda 参数的真正范围是什么,是否有一种惯用的或至少在语义上无害的方式来做我想做的事情,同时保持链接?

基于打破链条的答案很好,因为它们可能对未来的观众有用,但就我而言,偏离此回购协议中的主导风格可能会导致 PR 对话和批准延迟,所以我会喜欢保留链接的解决方案。或者,解释或演示试图保持链接是多么疯狂。谢谢!

您可以尝试这种方法(我不一定提倡这样做,但它确实维护了链条)。您创建一个包含链所需的所有参数的 POJO,并将相同的 POJO 传递到链中。这里的缺点是向链中添加其他方法有点麻烦,因为您现在还必须向参数 class.

添加 属性
public class SomeMethodContext {

    private Object a;
    private Object b;
    private Object c;
    private Object d;
    private Object foo;
    private Object bar;

    // Getters and setters

}

// ...

CompletionStage<E> someMethod() {
    return doSomething().thenCompose(a -> {
      SomeMethodContext context = new SomeMethodContext();  
      context.setA(a);
      // ...
      context.setFoo(fooDAO.getFoos(context.getA()));
      // ...
      context.setB(b);
      return context;
    }).thenCompose(ctxt -> {
      // ...
      ctxt.setC(c);
      return ctxt;
    }).thenCompose(ctxt -> {
      // ...
      ctxt.setBar(barDAO.getBars(ctxt.getFoo()))
      // ...
      ctxt.setD(d)
      return ctxt;
    }).thenApply(ctxt -> {
      // ...
      return someResult(ctxt.getD(), ctxt.getBar());
    });
}

既然你提到了你同事喜欢的编码风格,你可能已经知道使用嵌套调用的替代方法:

CompletionStage<E> someMethod() {
    return doSomething().thenCompose(a -> {
        // ...
        Foo foo = fooDAO.getFoos(a);
        // ...
        CompletableFuture<B> resultB = ...;
        return resultB.thenCompose(b -> {
            // ...
            CompletableFuture<C> resultC = ...;
            return resultC;
        }).thenCompose(c -> {
            // ...
            Bar bar = barDAO.getBars(foo);
            // ...
            CompletableFuture<D> resultD = ...;
            return resultD.thenApply(d -> {
                // ...
                return someResult(d, bar);
            });
        });
    });
}

这会立即解决您的问题,但代价是代码的可读性稍差一些。但是这个问题可以很容易地通过从你的代码中提取一些方法来解决:

CompletionStage<E> someMethod() {
    return doSomething()
            .thenCompose(this::processA);
}

private CompletionStage<E> processA(final A a) {
    // ...
    Foo foo = fooDAO.getFoos(a);
    // ...
    final CompletableFuture<B> result = ...;
    return result
            .thenCompose(this::processB)
            .thenCompose(c -> processCAndFoo(c, foo));
}

private CompletionStage<C> processB(B b) {
    // ...
    return ...;
}

private CompletionStage<E> processCAndFoo(final C c, final Foo foo) {
    // ...
    Bar bar = barDAO.getBars(foo);
    // ...
    final CompletableFuture<D> result = ...;
    return result
            .thenApply(d -> someResult(d, bar));
}

通过这样做,您可以避免嵌套的 lambda(并尊重您同事的首选代码风格),但您也可以获得可读性和可测试性,因为您现在有几个更容易理解和单元测试的小方法。

作为另一种选择,您还可以考虑为 Java 引入 EA Async on your project. This API provides async/await,并且对于此类问题特别巧妙。在这里,我用 EA Async 重写了您的代码:

CompletionStage<E> someMethodWithEAAsync() {
    final A a = await(doSomething());
    Foo foo = fooDAO.getFoos(a);
    // ...
    CompletableFuture<B> futureB = ...;
    final B b = await(futureB);
    // ...
    CompletableFuture<C> futureC = ...;
    final C c = await(futureC);
    // ...
    Bar bar = barDAO.getBars(foo);
    // ...
    CompletableFuture<D> futureD = ...;
    D d = await(futureD);
    return completedFuture(someResult(d, bar));
    // or alternatively
    return futureD.thenApply(d -> someResult(d, bar));
}

它看起来非常类似于同步代码,但在幕后 EA Async 将转换此方法以保持一切异步。再也不用担心链接问题了!