java - promises - CompletableFuture supplyAsync
java 8 completionstage (1)
Respuesta corta
No, no es más natural usar Callable
lugar de Supplier
en CompletableFuture.supplyAsync
. El argumento es casi enteramente sobre la semántica, así que está bien si aún no se siente convencido después.
Respuesta larga
Los Callable
interfaz funcional / Callable
y Supplier
/ SAM son prácticamente equivalentes en función (perdón por el juego de palabras), pero su origen y uso previsto difieren.
Callable
se creó como parte del paquete java.util.concurrent
. Ese paquete se produjo antes de los grandes cambios alrededor de las expresiones lambda en Java 8 y originalmente se concentró en una gama de herramientas que lo ayudaron a escribir código concurrente, sin desviarse mucho del modelo clásico de subprocesos múltiples.
El propósito principal de Callable
era abstraer una acción que se puede ejecutar en un hilo diferente y que devuelve un resultado. Del Javadoc de Callable
:
La interfaz
Callable
es similar aRunnable
, ya que ambas están diseñadas para clases cuyas instancias son potencialmente ejecutadas por otro hilo.
Supplier
se creó como parte del paquete java.util.function
. Ese paquete se incorporó como parte integral de los cambios mencionados en Java 8. Proporciona tipos funcionales comunes a los que pueden dirigirse las expresiones lambda y las referencias de métodos.
Uno de estos tipos es una función sin parámetros que devuelve un resultado (es decir, una función que suministra algún tipo o una función de Supplier
).
Entonces, ¿por qué Supplier
y no Callable
?
CompletableFuture
es parte de las adiciones al paquete java.util.concurrent
que se inspiraron en los cambios mencionados en Java 8 y que permiten al desarrollador construir su código de una manera funcional, implícitamente paralelizable, en lugar de manejar explícitamente la concurrencia dentro de él.
Su método supplyAsync
necesita una forma de proporcionar un resultado de un tipo específico y está más interesado en este resultado, y no en la acción tomada para alcanzar este resultado. Tampoco le importa necesariamente la finalización excepcional (consulte también el apartado ¿Qué pasa con ... más abajo?).
Sin embargo, si Runnable
se usa para la interfaz funcional sin parámetros, sin resultados, ¿no debería Callable
para la interfaz funcional sin parámetros y de resultados únicos?
No necesariamente.
Una abstracción para una función que no tiene un parámetro y no devuelve un resultado (y, por lo tanto, funciona completamente a través de efectos secundarios en el contexto externo) no se incluyó en java.util.function
. Esto significa que (algo molesto) Runnable
se usa donde se necesita tal interfaz funcional.
¿Qué pasa con la Exception
comprobada que puede ser lanzada por Callable.call()
?
Es un pequeño signo de la diferencia semántica prevista entre Callable
y Supplier
.
Un Callable
es una acción que se puede ejecutar en otro hilo y que le permite inspeccionar sus efectos secundarios como resultado de su ejecución. Si todo va bien, obtiene un resultado de un tipo específico, pero debido a que pueden surgir situaciones excepcionales al ejecutar algunas acciones (especialmente en contextos multiproceso), es posible que también desee definir y manejar dichas situaciones excepcionales.
Por otro lado, un Supplier
es una función en la que confía para suministrar objetos de algún tipo. Las situaciones excepcionales no deben necesariamente ser responsabilidad suya como consumidor directo del Supplier
. Esto es así porque:
- ... las interfaces funcionales se usan a menudo para definir una etapa específica en un proceso de múltiples etapas para crear o mutar datos, y el manejo de
Exception
puede ser una etapa separada, en caso de que le importe - ... el manejo explícito de
Exception
s reduce significativamente los poderes expresivos de las interfaces funcionales, las expresiones lambda y las referencias de métodos
Acabo de comenzar a explorar algunas características de concurrencia de Java 8. Una cosa me confundió un poco son estos dos métodos estáticos:
CompletableFuture<Void> runAsync(Runnable runnable)
CompletableFuture<U> supplyAsync(Supplier<U> supplier)
¿Alguien sabe por qué eligen usar Proveedor de interfaz? ¿No es más natural usar Callable, que es la analogía de Runnable que devuelve un valor? ¿Es porque el Proveedor no lanza una Excepción que no se pudo manejar?