Callable <T>和Java 8的Supplier <T>有什么区别?


13

在CodeReview提出了一些建议之后,我一直在从C#切换到Java。因此,当我查看LWJGL时,我想起的一件事是,每次调用都Display必须在Display.create()调用该方法的同一线程上执行。记住这一点,我整理了一个看起来像这样的课程。

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());
    }
}

在编写此类时,您会注意到我创建了一个方法isClosed(),该方法返回Future<Boolean>。这分派给我的一个函数Scheduler接口(这无非是围绕一个包装ScheduledExecutorService。在写schedule的方法Scheduler发现我既可以用我Supplier<T>的说法还是一个Callable<T>参数表示传入的功能。ScheduledExecutorService不包含覆盖了Supplier<T>,但我注意到,lambda表达式() -> Display.isCloseRequested()实际上是与输入同时兼容Callable<bool> Supplier<bool>

我的问题是,两者在语义上或其他方面是否有区别-如果是,那是什么,所以我可以坚持下去?


我的印象是,无效代码= SO,有效代码但需要复查= CodeReview,可能需要或可能不需要代码的一般问题=程序员。我的代码确实有效,仅作为示例。我也不是要评论,而是要问语义。
Dan Pantry 2014年

..询问事物的语义不是概念性的问题吗?
Dan Pantry 2014年

我认为这是一个概念性问题,不像该站点上的其他好问题那样具有概念性,但它与实现无关。代码有效,问题不在于代码。问题是:“这两个接口有什么区别?”

为什么要从C#切换到Java!
Didier A.

2
有一个区别,即Callable.call()引发异常而Supplier.get()不引发异常。这使得后者在lambda表达式中更具吸引力。
托尔比约恩Ravn的安徒生

Answers:


6

简短的答案是,两者都使用功能接口,但是也值得注意的是,并非所有功能接口都必须具有@FunctionalInterface注释。JavaDoc的关键部分内容如下:

但是,无论接口声明中是否存在FunctionalInterface批注,编译器都会将满足功能接口定义的任何接口视为功能接口。

功能接口的最简单定义是(简单地,没有其他排除):

从概念上讲,功能接口仅具有一种抽象方法。

因此,在@Maciej Chalapuk的答案中,也可以删除批注并指定所需的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

现在,使功能接口CallableSupplier功能接口同时出现的原因是它们确实包含一种抽象方法:

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

由于这两种方法均不接受参数(与MyInterface.myCall(int)示例相反),因此形式参数为空(())。

我注意到,lambda表达式() -> Display.isCloseRequested()实际上是与输入同时兼容Callable<Boolean> Supplier<Boolean>

正如您现在应该能够推断的那样,这仅仅是因为两个抽象方法都将返回您使用的表达式的类型。您绝对应该使用Callable给定的用法ScheduledExecutorService

进一步探索(超出问题范围)

这两个接口来自完全不同的 ,因此它们的用法也不同。在您的情况下,Supplier<T>除非提供了Callable:,否则我看不到将如何使用实现:

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

第一个() ->可以宽松地解释为“ a SupplierGives ...”,第二个可以理解为“ a CallableGives ...”。return value;Callablelambda 的主体,它本身就是Supplierlambda 的主体。

然而,在使用这种人为的例子被略微复杂,因为你现在需要get()Supplier第一前get()从-ting你的结果Future,这将反过来call()Callable是异步的。

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

1
我切换接受的回答这个答案,因为这仅仅是简单地全面得多
丹茶水

更长的时间并不等同于更有用,请参阅@srrm_lwn的答案。
SensorSmith

@SensorSmith srrms答案是我标记为已接受答案的原始答案。我仍然认为这是更有用的。
丹·潘特里

21

这两个接口之间的一个基本区别是,Callable允许从其实现中引发检查的异常,而Supplier则不允许。

以下是JDK的代码片段,重点介绍了这一点-

@FunctionalInterface
public interface Callable<V> {
/**
 * Computes a result, or throws an exception if unable to do so.
 *
 * @return computed result
 * @throws Exception if unable to compute a result
 */
V call() throws Exception;
}

@FunctionalInterface
public interface Supplier<T> {

/**
 * Gets a result.
 *
 * @return a result
 */
T get();
}

这使得Callable在功能接口中不能用作参数。
Basilevs

3
@Basilevs不,它不是-只是在期望使用的地方Supplier(例如流API)不可用。您绝对可以将lambda和方法引用传递给采用的方法Callable
dimo414 '18

12

正如您所注意到的,实际上,它们执行相同的操作(提供某种价值),但是原则上它们旨在执行不同的操作:

A Callable是“ 返回结果的任务,而a Supplier是” 结果的提供者 ”。换句话说,a Callable是引用尚未运行的工作单元的方式,而a Supplier是引用尚未得知的值的方式。

a Callable可能只需要做很少的工作就可以返回一个值。也有Supplier可能做很多工作(例如构造一个大数据结构)。但是总的来说,您所关心的是它们的主要目的。例如,ExecutorService使用Callables 的作品,因为它的主要目的是执行工作单元。延迟加载的数据存储区将使用Supplier,因为它关心是否要提供一个值,而不用担心可能要进行多少工作。

区别的另一种说法是a Callable可能有副作用(例如,写入文件),而a Supplier通常应该没有副作用。文档没有明确提及这一点(因为这不是必需的),但是我建议您使用这些术语进行思考。如果工作是幂等Supplier,则使用,如果不使用Callable


2

两者都是没有特殊语义的普通Java接口。可调用是并发API的一部分。供应商是新功能编程API的一部分。由于Java8的变化,可以从lambda表达式创建它们。@FunctionalInterface使编译器检查接口是否正常工作,如果不是,则引发错误,但是接口不需要将该批注作为功能接口并由lambdas实现。就像方法可以被覆盖而不被标记为@Override一样,反之亦然。

您可以定义自己的与lambda兼容的接口,并使用@FunctionalInterface注释对其进行记录。记录是可选的。

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

...

MyInterface var = (int a) -> a != 0;

尽管值得注意的是,该特定接口IntPredicate在Java中被称为。
Konrad Borowski
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.