私は ListenableFuture
パターンに慣れており、onSuccess()
およびonFailure()
コールバックを使用します。
_ListeningExecutorService service = MoreExecutors.listeningDecorator(Executors.newCachedThreadPool());
ListenableFuture<String> future = service.submit(...)
Futures.addCallback(future, new FutureCallback<String>() {
public void onSuccess(String result) {
handleResult(result);
}
public void onFailure(Throwable t) {
log.error("Unexpected error", t);
}
})
_
Java 8's CompletableFuture
は多かれ少なかれ同じユースケースを処理するためのものです。単純に、上の例を次のように翻訳し始めることができます:
_CompletableFuture<String> future = CompletableFuture<String>.supplyAsync(...)
.thenAccept(this::handleResult)
.exceptionally((t) -> log.error("Unexpected error", t));
_
これは確かにListenableFuture
バージョンより冗長ではなく、非常に有望に見えます。
ただし、exceptionally()
は_Consumer<Throwable>
_を使用しないため、コンパイルされません。_Function<Throwable, ? extends T>
_(この場合は_Function<Throwable, ? extends String>
_)を使用します。
これは、エラーをログに記録するだけではなく、エラーの場合に返すにはString
値を考え出す必要があり、エラーで返す意味のあるString
値がないことを意味します場合。コードをコンパイルするためだけにnull
を返すことができます。
_ .exceptionally((t) -> {
log.error("Unexpected error", t);
return null; // hope this is ignored
});
_
しかし、これは再び冗長になり始めており、冗長性を超えて、そのnull
が浮かんでいるのは好きではありません-誰かがその値を取得またはキャプチャしようとする可能性があり、かなり後で予期しないNullPointerException
が発生する可能性があります。
exceptionally()
が_Function<Throwable, Supplier<T>>
_を取った場合、少なくとも次のようなことができます-
_ .exceptionally((t) -> {
log.error("Unexpected error", t);
return () -> {
throw new IllegalStateException("why are you invoking this?");
}
});
_
-しかし、そうではありません。
exceptionally()
が有効な値を生成してはならない場合に正しいことは何ですか?私がCompletableFuture
でできる他のことはありますか、またはこのJava 8ライブラリでは、この使用例をより適切にサポートする他の何かですか?
CompletableFuture
を使用した対応する正しい変換は次のとおりです。
CompletableFuture<String> future = CompletableFuture.supplyAsync(...);
future.thenAccept(this::handleResult);
future.exceptionally(t -> {
log.error("Unexpected error", t);
return null;
});
別の方法:
CompletableFuture<String> future = CompletableFuture.supplyAsync(...);
future
.whenComplete((r, t) -> {
if (t != null) {
log.error("Unexpected error", t);
}
else {
this.handleResult(r);
}
});
ここで興味深いのは、例の中で先物を連鎖させていたことです。一見流暢に見える構文は実際には先物を連鎖させていますが、ここではそれを望まないようです。
whenComplete
によって返されるフューチャーは、内部フューチャーの結果で何かを処理するフューチャーを返したい場合に興味深いかもしれません。現在の未来の例外があればそれを保存します。ただし、futureが正常に完了し、継続がスローされた場合、スローされた例外で例外が完了します。
違いは、future
の完了後に発生することはすべて、次の継続の前に発生することです。 exceptionally
とthenAccept
の使用は、future
のエンドユーザーの場合は同等ですが、呼び出し元にフューチャーを提供している場合は、どちらも完了通知(バックグラウンドにあるかのように)、おそらくexceptionally
の継続。これは、例外をさらに継続するためにカスケードする必要があるためです。
exceptionally(Function<Throwable,? extends T> fn)
もCompletableFuture<T>
を返すことに注意してください。したがって、さらに連鎖させることができます。
Function<Throwable,? extends T>
の戻り値は、次のチェーンメソッドのフォールバック結果を生成するためのものです。したがって、たとえば、DBから使用できない場合は、キャッシュから値を取得できます。
CompletableFuture<String> future = CompletableFuture<String>.supplyAsync(/*get from DB*/)
.exceptionally((t) -> {
log.error("Unexpected error", t);
return "Fallback value from cache";
})
.thenAccept(this::handleResult);
exceptionally
が関数の代わりにSupplier<T>
を受け入れる場合、さらにチェーンするためにCompletableFuture<String>
をどのように返すことができますか?
exceptionally
を返すvoid
のバリアントが必要だと思います。しかし、残念ながら、そのような亜種はありません。
したがって、あなたのケースでは、このfuture
オブジェクトを返さず、コードでそれ以上使用しない場合は、このフォールバック関数から任意の値を安全に返すことができます(そのため、さらにチェーンすることはできません)。それを変数に割り当てない方が良いでしょう。
CompletableFuture<String>.supplyAsync(/*get from DB*/)
.thenAccept(this::handleResult)
.exceptionally((t) -> {
log.error("Unexpected error", t);
return null;
});