¿Cuál es la diferencia entre CallableT y SupplierT de Java 8?

12

He estado cambiando a Java desde C # después de algunas recomendaciones de algunos en CodeReview. Entonces, cuando estaba buscando en LWJGL, una cosa que recordé fue que cada llamada a Display debe ejecutarse en el mismo hilo en el que se invocó el método Display.create() . Recordando esto, preparé una clase que se parece un poco a esto.

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

Mientras escribe esta clase, notará que creé un método llamado isClosed() que devuelve un Future<Boolean> . Esto envía una función a mi interfaz Scheduler (que no es más que una envoltura alrededor de ScheduledExecutorService . Mientras escribía el método schedule en el Scheduler , noté que podría usar un argumento Supplier<T> o un argumento Callable<T> para representar la función que se pasa. ScheduledExecutorService no contenía un reemplazo para Supplier<T> pero noté que la expresión lambda () -> Display.isCloseRequested() es en realidad compatible con ambos Callable<bool> y Supplier<bool> .

Mi pregunta es, ¿hay alguna diferencia entre esos dos, semánticamente o de otra manera? Si es así, ¿qué es, para que pueda adherirme a ella?

    
pregunta Dan Pantry 24.08.2014 - 18:40

4 respuestas

6

La respuesta corta es que ambas usan interfaces funcionales, pero también es digno de notar que no todas las interfaces funcionales deben tener el @FunctionalInterface anotación. La parte crítica de JavaDoc dice:

  

Sin embargo, el compilador tratará cualquier interfaz que cumpla con la definición de una interfaz funcional como una interfaz funcional independientemente de si una anotación FunctionalInterface está presente en la declaración de la interfaz o no.

Y la definición más simple de una interfaz funcional es (simplemente, sin otras exclusiones) simplemente:

  

Conceptualmente, una interfaz funcional tiene exactamente un método abstracto.

Por lo tanto, en la respuesta de @Maciej Chalapuk , también es posible eliminar la anotación y especificar la lambda deseada :

// 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

Ahora, lo que hace que ambas interfaces funcionales Callable y Supplier se debe a que contienen exactamente un método abstracto:

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

Dado que ambos métodos no toman un argumento (a diferencia del ejemplo MyInterface.myCall(int) ), los parámetros formales están vacíos ( () ).

  

Noté que la expresión lambda () -> Display.isCloseRequested() es en realidad compatible con el tipo tanto con Callable<Boolean> como con Supplier<Boolean> .

Como debería ser capaz de inferir a estas alturas, eso es solo porque ambos métodos abstractos devolverán el tipo de expresión que usa. Definitivamente, deberías usar un Callable dado tu uso de un ScheduledExecutorService .

Exploración adicional (más allá del alcance de la pregunta)

Ambas interfaces provienen completamente de diferente < a href="https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html"> packages , por lo que también se utilizan de manera diferente. En su caso, no veo cómo se usará una implementación de Supplier<T> , a menos que esté proporcionando un Callable :

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

El primer () -> puede interpretarse libremente como "un Supplier da ..." y el segundo como "un Callable da ...". return value; es el cuerpo del Callable lambda, que a su vez es el cuerpo del Supplier lambda.

Sin embargo, el uso en este ejemplo ideado se complica un poco, ya que ahora necesita get() del Supplier primero antes de get() , indicando su resultado del Future , que a su vez será call() su Callable asincrónicamente.

public static <T> T doWork(Supplier<Callable<T>> callableSupplier) {
    // service being an instance of ExecutorService
    return service.submit(callableSupplier.get()).get();
}
    
respondido por el h.j.k. 10.05.2015 - 13:52
17

Una diferencia básica entre las 2 interfaces es que Callable permite que se lancen excepciones comprobadas desde su implementación, mientras que el Proveedor no.

Aquí están los fragmentos de código del JDK destacando esto -

@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();
}
    
respondido por el srrm_lwn 17.06.2016 - 05:42
8

Como observas, en la práctica hacen lo mismo (proporcionan algún tipo de valor), sin embargo, en principio, están destinados a hacer cosas diferentes:

Un Callable es " Una tarea que devuelve un resultado , mientras que una Supplier es " un proveedor de resultados ". En otras palabras, Callable es una forma de hacer referencia a una unidad de trabajo aún sin ejecutar, mientras que Supplier es una manera de hacer referencia a valor aún desconocido.

Es posible que un Callable pueda hacer muy poco trabajo y simplemente devolver un valor. También es posible que Supplier pueda hacer bastante trabajo (por ejemplo, construir una estructura de datos grande). Pero, en general, lo que te importa es su principal propósito. Por ejemplo, un ExecutorService funciona con Callable s, porque su propósito principal es ejecutar unidades de trabajo. Un almacén de datos cargado de forma perezosa usaría un Supplier , ya que le importa que se le otorgue un valor, sin preocuparse por la cantidad de trabajo que puede llevar.

Otra forma de expresar la distinción es que un Callable puede tener efectos secundarios (por ejemplo, escribir en un archivo), mientras que un Supplier generalmente no debe tener efectos secundarios. La documentación no menciona explícitamente esto (ya que no es un requisito ), pero sugeriría pensar en esos términos. Si el trabajo es idempotent use un Supplier , si no usa un Callable .

    
respondido por el dimo414 25.01.2016 - 21:57
2

Ambas son interfaces Java normales sin semántica especial. Callable es parte de la API concurrente. Supplier es parte de la nueva API de programación funcional. Se pueden crear a partir de expresiones lambda gracias a los cambios en Java8. @FunctionalInterface es una anotación solo de documentación (en realidad no hace nada) se usa para indicar que la interfaz está diseñada para usarse de esta manera.

Puede definir sus propias interfaces compatibles con las lambdas y documentarlas con la anotación @FunctionalInterface . Aunque la documentación es opcional.

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

...

MyInterface var = (int a) -> a != 0;
    
respondido por el Maciej Chałapuk 24.08.2014 - 21:26

Lea otras preguntas en las etiquetas