在 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 将转换此方法以保持一切异步。再也不用担心链接问题了!
我的同事们喜欢的 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 将转换此方法以保持一切异步。再也不用担心链接问题了!