24

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?

Tuan Do
  • 357
  • 1
  • 2
  • 11
  • 1
    Because they just had to be different from C#, which has generically named `Action<>` and `Func<>` interfaces which do the same job but without confusing the programmer. And so they proliferated functional interfaces for methods with the same signatures but different names. They probably thought it would help the programmer by giving him a mental model to work with, but it doesn't: It's just a minor PITA. – davidbak Apr 27 '16 at 23:36

1 Answers1

15

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 to Runnable, 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:

  1. ...functional interfaces are often used for defining a specific stage in a multi-stage process for creating or mutating data, and handling Exceptions can be a separate stage, in case you care
  2. ...explicitly handling Exceptions significantly reduces the expressive powers of functional interfaces, lambda expressions and method references
Dimitar Dimitrov
  • 16,032
  • 5
  • 53
  • 55
  • I think your arguments that the main difference lies on the semantic side, not practical side is correct. Semantically using a method named _CallAsync_ doesn't imply anything that it would return a result. – Tuan Do Apr 01 '16 at 20:03
  • IMO the whole idea of having separately named functional interfaces with _different methods for the same functionality_ (e.g., `call()` vs `get()`) was ill thought through and leads to lots of annoyances. Two in particular are: trying to remember what the darn method you need to call is and then not being easily able to send a Supplier you've got to a method needing Callable, etc. etc. The intended "semantics" you describe are all in words and have nothing to do with anything the compiler would enforce and are ill-described or not described at all in the Java docs. C# did it better. – davidbak Apr 27 '16 at 23:32
  • 1
    @davidbak when you want to send a `Supplier` to a method expecting a `Callable`, it doesn’t matter whether the method names match or not, as there is no duck typing in Java. On the other hand, you can simply use `existingSupplier::get` where a `Callable` is expected and it works, again, independent of the actual method name. – Holger Feb 11 '20 at 10:43