I've just started exploring some concurrency features of Java 8. One thing confused me a bit is these two static methods:
CompletableFuture<Void> runAsync(Runnable runnable) CompletableFuture<U> supplyAsync(Supplier<U> supplier)
Do anyone know why they choose to use interface Supplier? Isn't it more natural to use Callable, which is the analogy of Runnable that returns a value? Is that because Supplier doesn't throw an Exception that couldn't be handled?
Short answer
No, it's not more natural to use Callable
instead of Supplier
in CompletableFuture.supplyAsync
. The argument is almost entirely about semantics, so it's OK if you still feel unconvinced afterwards.
Long answer
The Callable
and Supplier
functional interfaces/SAM types are practically equivalent in function (pardon the pun), but their origin and intended use differ.
Callable
was created as part of the java.util.concurrent
package. That package came before the vast changes around lambda expressions in Java 8 and originally concentrated on a range of tools that helped you write concurrent code, without deviating much from the classic model of hands-on multithreading.
The main purpose of Callable
was to abstract an action that can be executed in a different thread and that returns a result. From Callable
's Javadoc:
The
Callable
interface is similar toRunnable
, in that both are designed for classes whose instances are potentially executed by another thread.
Supplier
was created as part of the java.util.function
package. That package came as an integral part of the aforementioned changes in Java 8. It provides common functional types that can be targeted by lambda expressions and method references.
One such type is a function without parameters that returns a result (i.e. a function that supplies some type or a Supplier
function).
So why Supplier
and not Callable
?
CompletableFuture
is part of additions to the java.util.concurrent
package that were inspired by the aforementioned changes in Java 8 and that allow the developer to construct his code in a functional, implicitly parallelizable manner, instead of explicitly handling concurrency within it.
Its supplyAsync
method needs a way to provide a result of a specific type and its more interested in this result, and not in the action taken to reach this result. It also doesn't necessarily care about exceptional completion (also see the What about the... paragraph below).
Still, if Runnable
is used for no-parameters, no-result functional interface, shouldn't Callable
be used for no-parameters, single-result functional interface?
Not necessarily.
An abstraction for a function that does not have a parameter and does not return a result (and therefore operates entirely through side effects on outside context) was not included in java.util.function
. This means that (somewhat annoyingly) Runnable
is used wherever such a functional interface is needed.
What about the checked Exception
that can be thrown byCallable.call()
?
It's a small sign of the intended semantic difference between Callable
and Supplier
.
A Callable
is an action that can be executed in another thread, and that allows you to inspect its side effects as a result of its execution. If all goes well, you get a result of a specific type, but because exceptional situations can arise when executing some actions (especially in multithreaded context), you may also want to define and handle such exceptional situations.
A Supplier
on the other hand is a function on which you rely for supplying objects of some type. Exceptional situations should not necessarily be made your responsibility as the direct consumer of the Supplier
. This is so because:
Exception
s can be a separate stage, in case you careException
s significantly reduces the expressive powers of functional interfaces, lambda expressions and method referencesIf you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With