Mejoras de la API de Java 9 CompletableFuture

1. Introducción

Java 9 viene con algunos cambios en la clase CompletableFuture . Estos cambios se introdujeron como parte de JEP 266 para abordar quejas y sugerencias comunes desde su introducción en JDK 8, más específicamente, soporte para retrasos y tiempos de espera, mejor soporte para subclases y algunos métodos de utilidad.

En cuanto al código, la API incluye ocho métodos nuevos y cinco métodos estáticos nuevos. Para habilitar tales adiciones, se cambiaron aproximadamente 1500 de las 2400 líneas de código (según Open JDK).

2. Adiciones de API de instancia

Como se mencionó, la API de instancia viene con ocho nuevas adiciones, que son:

  1. Executor defaultExecutor ()
  2. CompletableFuture nuevoIncompleteFuture ()
  3. CompletableFuture copy ()
  4. CompletionStage mínimaCompletionStage ()
  5. CompletableFuture completeAsync (proveedor proveedor, ejecutor ejecutor)
  6. CompletableFuture completeAsync (Proveedor proveedor)
  7. CompletableFuture oTimeout (tiempo de espera largo, unidad TimeUnit)
  8. CompletableFuture completeOnTimeout (valor T, tiempo de espera largo, unidad TimeUnit)

2.1. Método defaultExecutor ()

Firma : Ejecutor predeterminado Ejecutor ()

Devuelve el Ejecutor predeterminado utilizado para los métodos asíncronos que no especifican un Ejecutor .

new CompletableFuture().defaultExecutor()

Esto puede ser anulado por las subclases que devuelven un ejecutor que proporciona, al menos, un hilo independiente.

2.2. Método newIncompleteFuture ()

Firma : CompletableFuture newIncompleteFuture ()

El newIncompleteFuture , también conocido como el "constructor virtual", se utiliza para obtener una nueva instancia futura completable del mismo tipo.

new CompletableFuture().newIncompleteFuture()

Este método es especialmente útil cuando se subclasifican CompletableFuture , principalmente porque se usa internamente en casi todos los métodos que devuelven un CompletionStage nuevo , lo que permite que las subclases controlen qué subtipo devuelve dichos métodos.

2.3. Copiar método ()

Firma : CompletableFuture copy ()

Este método devuelve un nuevo CompletableFuture que:

  • Cuando esto se completa normalmente, el nuevo se completa normalmente también
  • Cuando esto se completa excepcionalmente con la excepción X, el nuevo también se completa excepcionalmente con una CompletionException con X como causa
new CompletableFuture().copy()

Este método puede ser útil como una forma de "copia defensiva", para evitar que los clientes completen, sin dejar de poder organizar acciones dependientes en una instancia específica de CompletableFuture .

2.4. Método minimalCompletionStage ()

Firma : CompletionStage minimalCompletionStage ()

Este método devuelve una nueva CompletionStage que se comporta exactamente de la misma manera que la descrita por el método de copia, sin embargo, dicha nueva instancia arroja UnsupportedOperationException en cada intento de recuperar o establecer el valor resuelto.

new CompletableFuture().minimalCompletionStage()

Se puede recuperar un nuevo CompletableFuture con todos los métodos disponibles mediante el método toCompletableFuture disponible en la API de CompletionStage .

2.5. Métodos completeAsync ()

El método completeAsync debe usarse para completar CompletableFuture de forma asincrónica utilizando el valor proporcionado por el Proveedor .

Firmas :

CompletableFuture completeAsync(Supplier supplier, Executor executor) CompletableFuture completeAsync(Supplier supplier)

La diferencia entre estos dos métodos sobrecargados es la existencia del segundo argumento, donde se puede especificar el Ejecutor que ejecuta la tarea. Si no se proporciona ninguno, se utilizará el ejecutor predeterminado (devuelto por el método defaultExecutor ).

2.6. Métodos oTimeout ()

Firma : CompletableFuture oTimeout (tiempo de espera largo, unidad TimeUnit)

new CompletableFuture().orTimeout(1, TimeUnit.SECONDS)

Resuelve CompletableFuture excepcionalmente con TimeoutException , a menos que se complete antes del tiempo de espera especificado.

2.7. Método completeOnTimeout ()

Firma : CompletableFuture completeOnTimeout (valor T, tiempo de espera largo, unidad TimeUnit)

new CompletableFuture().completeOnTimeout(value, 1, TimeUnit.SECONDS)

Completa CompletableFuture normalmente con el valor especificado a menos que se complete antes del tiempo de espera especificado.

3. Adiciones de API estáticas

También se agregaron algunos métodos de utilidad. Son:

  1. Ejecutor retrasado Ejecutor (gran retraso, unidad TimeUnit, Ejecutor ejecutor)
  2. Ejecutor retrasado Ejecutor (retraso largo, unidad TimeUnit)
  3. CompletionStage completedStage(U value)
  4. CompletionStage failedStage(Throwable ex)
  5. CompletableFuture failedFuture(Throwable ex)

3.1. Methods delayedExecutor

Signatures:

Executor delayedExecutor(long delay, TimeUnit unit, Executor executor) Executor delayedExecutor(long delay, TimeUnit unit)

Returns a new Executor that submits a task to the given base executor after the given delay (or no delay if non-positive). Each delay commences upon invocation of the returned executor's execute method. If no executor is specified the default executor (ForkJoinPool.commonPool()) will be used.

3.2. Methods completedStage and failedStage

Signatures:

 CompletionStage completedStage(U value)  CompletionStage failedStage(Throwable ex)

This utility methods return already resolved CompletionStage instances, either completed normally with a value (completedStage) or completed exceptionally (failedStage) with the given exception.

3.3. Method failedFuture

Signature: CompletableFuture failedFuture(Throwable ex)

The failedFuture method adds the ability to specify an already completed exceptionally CompleatebleFuture instance.

4. Example Use Cases

Within this section, one will show some examples on how to use some of the new API.

4.1. Delay

This example will show how to delay the completion of a CompletableFuture with a specific value by one second. That can be achieved by using the completeAsync method together with the delayedExecutor.

CompletableFuture future = new CompletableFuture(); future.completeAsync(() -> input, CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS));

4.2. Complete With Value on Timeout

Another way to achieve a delayed result is to use the completeOnTimeout method. This example defines a CompletableFuture that will be resolved with a given input if it stays unresolved after 1 second.

CompletableFuture future = new CompletableFuture(); future.completeOnTimeout(input, 1, TimeUnit.SECONDS);

4.3. Timeout

Another possibility is timing out which resolves the future exceptionally with TimeoutException. For example, having the CompletableFuture timing out after 1 second given it is not completed before that.

CompletableFuture future = new CompletableFuture(); future.orTimeout(1, TimeUnit.SECONDS);

5. Conclusion

En conclusión, Java 9 viene con varias adiciones a la API CompletableFuture , ahora tiene un mejor soporte para subclases, gracias al constructor virtual newIncompleteFuture , es posible tomar el control sobre las instancias de CompletionStage devueltas en la mayoría de la API de CompletionStage .

Tiene, definitivamente, mejor soporte para retrasos y tiempos de espera como se mostró anteriormente. Los métodos de utilidad añadidos siguen un patrón sensato, lo que le da a CompletableFuture una forma conveniente de especificar instancias resueltas.

Los ejemplos utilizados en este artículo se pueden encontrar en nuestro repositorio de GitHub.