supplyasync promises example completionstage completablefuture java concurrency java-8

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 a Runnable , 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:

  1. ... 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
  2. ... 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?