Java – Difference Between Callable and Supplier

java

I've been switching over to Java from C# after some recommendations from some over at CodeReview. So, when I was looking into LWJGL, one thing I remembered was that every call to Display must be executed on the same thread that the Display.create() method was invoked on. Remembering this, I whipped up a class that looks a bit like this.

public class LwjglDisplayWindow implements DisplayWindow {
    private final static int TargetFramesPerSecond = 60;
    private final Scheduler _scheduler;

    public LwjglDisplayWindow(Scheduler displayScheduler, DisplayMode displayMode) throws LWJGLException {
        _scheduler = displayScheduler;
        Display.setDisplayMode(displayMode);
        Display.create();
    }

    public void dispose() {
        Display.destroy();
    }

    @Override
    public int getTargetFramesPerSecond() { return TargetFramesPerSecond; }

    @Override
    public Future<Boolean> isClosed() {
        return _scheduler.schedule(() -> Display.isCloseRequested());
    }
}

While writing this class you'll notice that I created a method called isClosed() that returns a Future<Boolean>. This dispatches a function to my Scheduler interface (which is nothing more than a wrapper around an ScheduledExecutorService. While writing the schedule method on the Scheduler I noticed that I could either use a Supplier<T> argument or a Callable<T> argument to represent the function that is passed in. ScheduledExecutorService didn't contain an override for Supplier<T> but I noticed that the lambda expression () -> Display.isCloseRequested() is actually type compatible with both Callable<bool> and Supplier<bool>.

My question is, is there a difference between those two, semantically or otherwise – and if so, what is it, so I can adhere to it?

Best Answer

The short answer is that both are using functional interfaces, but it's also worthy to note that not all functional interfaces must have the @FunctionalInterface annotation. The critical part of the JavaDoc reads:

However, the compiler will treat any interface meeting the definition of a functional interface as a functional interface regardless of whether or not a FunctionalInterface annotation is present on the interface declaration.

And the simplest definition of a functional interface is (simply, without other exclusions) just:

Conceptually, a functional interface has exactly one abstract method.

Therefore, in @Maciej Chalapuk's answer, it is also possible to drop the annotation and specify the desired lambda:

// interface
public interface MyInterface {
    boolean myCall(int arg);
}

// method call
public boolean invokeMyCall(MyInterface arg) {
    return arg.myCall(0);
}

// usage
instance.invokeMyCall(a -> a != 0); // returns true if the argument supplied is not 0

Now, what makes both Callable and Supplier functional interfaces is because they do contain exactly one abstract method:

  • Callable.call()
  • Supplier.get()

Since both methods do not take in an argument (as opposed to the MyInterface.myCall(int) example), the formal parameters are empty (()).

I noticed that the lambda expression () -> Display.isCloseRequested() is actually type compatible with both Callable<Boolean> and Supplier<Boolean>.

As you should be able to infer by now, that is just because both abstract methods will return the type of the expression you use. You should definitely be using a Callable given your usage of a ScheduledExecutorService.

Further exploration (beyond the scope of the question)

Both interfaces comes from entirely different packages, hence they are used differently too. In your case, I don't see how an implementation of Supplier<T> will be used, unless it is supplying a Callable:

public static <T> Supplier<Callable<T>> getCallable(T value) {
    return () -> () -> {
        return value;
    };
}

The first () -> can be loosely interpreted as "a Supplier gives..." and the second as "a Callable gives...". return value; is the body of the Callable lambda, which itself is the body of the Supplier lambda.

However, usage in this contrived example gets slightly complicated, as you now need to get() from the Supplier first before get()-ting your result from the Future, which will in turn call() your Callable asynchronously.

public static <T> T doWork(Supplier<Callable<T>> callableSupplier) {
    // service being an instance of ExecutorService
    return service.submit(callableSupplier.get()).get();
}