I'm learning about CompletableFuture
s.
I am not asking about the difference between thenApply()
and thenCompose()
. Instead, I want to ask about a code "scent" that doesn't feel right, and what might actually justify it.
From the usages of CompletableFuture
s I've seen so far, it seems you'd never have this:
CompletableFuture<String> foo = getSomething().thenApply((result) -> { ... });
Nor this:
String foo = getSomething().thenCompose((result) -> { ... });
To return a future, you have to use thenCompose()
, and otherwise thenApply()
.
From experience though, it seems weird that language didn't devise a way to eliminate making this unambiguous choice every time. For example, couldn't there have been a single method thenDo()
whose return type is inferred (during compile-time) from the return
within the lambda? It could then be given thenApply
or thenCompose
-like properties at compile-time as well.
But I'm sure there's a good reason for having separate methods, so I'd like to know why.
Is it because it's dangerous or not possible to infer return types from lambdas in Java? (I'm new to Java as well.)
Is it because there is a case in which a single method would indeed be ambiguous, and the only solution is to have separate methods? (I'm imagining maybe, nested CompletableFuture
s or complicated interfaces and generics.) If so, can someone provide a clear example?
Is it for some other reason or documented recommendation?
For reference, the signatures of the two methods are:
<U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn)
<U> CompletableFuture<U> thenCompose(Function<? super T,? extends CompletionStage<U>> fn)
Function<? super T,? extends U>
and Function<? super T,? extends CompletionStage<U>>
have Function<? super T, ?>
as a common supertype (ok, technically it's just Function
).
Therefore the signature of thenDo
would be something like:
<U> CompletableFuture<U> thenDo(Function<? super T,?> fn)
which, while legal, would be a real pain to use as the compiler would have no way to check whether the return type for fn
is correct and would have to accept anything.
Also, the implementation of this thenDo
would have no other option but to apply
the function and check whether the returned object implements CompletionStage
, which (besides being slow and... repugnantly inelegant) would have real issues in non-straightforward cases: what would happen when calling thenDo
on a CompletableFuture<CompletionStage<String>>
?
If you are new to java generics, my recommendation is to concentrate on understanding two things first and foremost:
List<String>
a subtype of List<Object>
? What's the difference between List<Object>
and List<?>
?After you have those set, investigate how type variables can be resolved via reflection (eg: understand how Guava's TypeToken
works)
edit: fixed a link