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 recordaba era que cada llamada a Display
debe ejecutarse en el mismo hilo en el que Display.create()
se invocó el método. 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 a Future<Boolean>
. Esto envía una función a mi Scheduler
interfaz (que no es más que un contenedor alrededor de un ScheduledExecutorService
. Al escribir el schedule
método en el Scheduler
noté que podía usar un Supplier<T>
argumento o un Callable<T>
argumento para representar la función que se pasa. ScheduledExecutorService
No contenía un anule Supplier<T>
pero, noté que la expresión lambda () -> Display.isCloseRequested()
es de tipo compatible con ambos Callable<bool>
y Supplier<bool>
.
Mi pregunta es, ¿hay alguna diferencia entre esos dos, semánticamente o de otro modo? Y, de ser así, ¿cuál es, para que pueda adherirme?
Respuestas:
La respuesta corta es que ambos están utilizando interfaces funcionales, pero también vale la pena señalar que no todas las interfaces funcionales deben tener la
@FunctionalInterface
anotación. La parte crítica de JavaDoc dice:Y la definición más simple de una interfaz funcional es (simplemente, sin otras exclusiones) solo:
Por lo tanto, en la respuesta de @Maciej Chalapuk , también es posible eliminar la anotación y especificar la lambda deseada:
Ahora, lo que hace que ambos
Callable
ySupplier
funcionales de interfaces se debe a que contienen exactamente un método abstracto:Callable.call()
Supplier.get()
Como ambos métodos no toman en cuenta un argumento (a diferencia del
MyInterface.myCall(int)
ejemplo), los parámetros formales están vacíos (()
).Como ya debería poder inferir, eso se debe a que ambos métodos abstractos devolverán el tipo de expresión que usa. Definitivamente deberías estar usando un
Callable
dado tu uso de aScheduledExecutorService
.Exploración adicional (más allá del alcance de la pregunta)
Ambas interfaces provienen de paquetes completamente diferentes , por lo tanto, también se usan de manera diferente. En su caso, no veo cómo se utilizará una implementación de , a menos que proporcione un :
Supplier<T>
Callable
El primero
() ->
se puede interpretar libremente como "unSupplier
da ..." y el segundo como "unCallable
da ...".return value;
es el cuerpo de laCallable
lambda, que es el cuerpo de laSupplier
lambda.Sin embargo, el uso en este ejemplo artificial se vuelve un poco complicado, ya que ahora debe hacerlo
get()
desde elSupplier
primero antes deget()
obtener su resultado desde elFuture
, que acall()
su vez seráCallable
asincrónico.fuente
Una diferencia básica entre las 2 interfaces es que Callable permite que se lancen excepciones controladas desde su implementación, mientras que el Proveedor no.
Aquí están los fragmentos de código del JDK que destacan esto:
fuente
Supplier
, como la API de transmisión. Absolutamente puede pasar lambdas y referencias de métodos a métodos que toman unCallable
.Como observa, en la práctica hacen lo mismo (proporcionan algún tipo de valor), sin embargo, en principio, tienen la intención de hacer cosas diferentes:
A
Callable
es " Una tarea que devuelve un resultado , mientras que aSupplier
es" un proveedor de resultados ". En otras palabras, aCallable
es una forma de hacer referencia a una unidad de trabajo aún no ejecutada, mientras que aSupplier
es una forma de hacer referencia a un valor aún desconocido.Es posible que a
Callable
pueda hacer muy poco trabajo y simplemente devolver un valor. También es posible queSupplier
pueda hacer mucho trabajo (por ejemplo, construir una estructura de datos grande). Pero, en general, lo que te importa es su propósito principal. Por ejemplo, unExecutorService
funciona conCallable
s, porque su propósito principal es ejecutar unidades de trabajo. Un almacén de datos con carga lenta usaría aSupplier
, porque le importa que se le proporcione un valor, sin preocuparse por la cantidad de trabajo que podría tomar.Otra forma de expresar la distinción es que a
Callable
puede tener efectos secundarios (por ejemplo, escribir en un archivo), mientrasSupplier
que generalmente no debería 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 idempotente, use aSupplier
, si no, use aCallable
.fuente
Ambas son interfaces Java normales sin semántica especial. Callable es parte de la API concurrente. El proveedor 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 hace que el compilador compruebe que la interfaz es funcional y genera un error si no lo es, pero una interfaz no necesita esa anotación para ser una interfaz funcional e implementada por lambdas. Así es como un método puede ser una anulación sin estar marcado @Override pero no al revés.
Puede definir sus propias interfaces compatibles con lambdas y documentarlas con
@FunctionalInterface
anotaciones. Sin embargo, la documentación es opcional.fuente
IntPredicate
en Java.