Hola de nuevo En previsión del comienzo del curso, "Java Developer" preparó una traducción de material útil para usted.
Hay dos métodos en
CompletableFuture
cuyo diseño me sorprende:
- CompletableFuture # allOf
- CompletableFuture # anyOf
En este artículo veremos qué les pasa y cómo pueden hacerse más convenientes.
CompletableFuture # allOf
Veamos la firma del método:
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
Aquí hay al menos dos cuestiones polémicas:
- El método acepta varios objetos
CompletableFuture
que devuelven objetos de diferentes tipos. - El método devuelve
CompletableFuture
, que devuelve Void
Además, puede que a algunos no les guste el número variable de parámetros, así que veamos también esta parte.
CompletableFuture<Void>
menudo se usa como una señal para completar una operación; sin embargo, al hacer un pequeño cambio en la API,
este método se puede usar tanto como dispositivo de señalización como como portador de los resultados de todas las operaciones completadas . Intentemos hacerlo.
Asíncrono CompletableFuture # allOf
Primero, busquemos la firma correcta.
Es justo suponer que en la mayoría de los casos se requerirá procesar una lista de
CompletableFuture
homogénea y devolver un
CompletableFuture
contenga una lista de resultados:
public static <T> CompletableFuture<List<T>> allOf( Collection<CompletableFuture<T>> futures) {
Las entrañas del método original son probablemente más complejas de lo que espera:
static CompletableFuture<Void> andTree( CompletableFuture<?>[] cfs, int lo, int hi) { CompletableFuture<Void> d = new CompletableFuture<Void>(); if (lo > hi)
Por lo tanto, en lugar de crearlo desde cero, intentaremos reutilizar lo que ya está en el método original como si estuviera destinado a ser utilizado como un dispositivo de señalización de finalización ... y luego simplemente cambie el resultado nulo a la lista futura:
CompletableFuture<List<CompletableFuture<T>>> i = futures.stream() .collect(collectingAndThen( toList(), l -> CompletableFuture.allOf(l.toArray(new CompletableFuture[0])) .thenApply(__ -> l)));
Hasta ahora todo bien. Nos las arreglamos para conseguir
CompletableFuture<List<CompletableFuture<T> >
>
CompletableFuture<Void>
lugar de
CompletableFuture<Void>
, que ya es bueno. Pero no necesitamos una lista de futuro con resultados, necesitamos una lista de resultados.
Ahora podemos simplemente procesar la lista y eliminar el futuro no deseado de ella. Es perfectamente normal llamar a los métodos de unión CompletableFuture #, porque sabemos que nunca se bloquearán (en este punto, todos los futuros ya están completos):
CompletableFuture<List<T>> result = intermediate .thenApply(list -> list.stream() .map(CompletableFuture::join) .collect(toList()));
Ahora combinemos todo esto en una solución final:
public static <T> CompletableFuture<List<T>> allOf( Collection<CompletableFuture<T>> futures) { return futures.stream() .collect(collectingAndThen( toList(), l -> CompletableFuture.allOf(l.toArray(new CompletableFuture[0])) .thenApply(__ -> l.stream() .map(CompletableFuture::join) .collect(Collectors.toList())))); }
Asíncrono y caída CompletableFuture # allOf
Si hay excepciones, el CompletableFuture # allOf original espera a que se completen todas las operaciones restantes.
Y si deseamos informar la finalización de la operación cuando se produce una excepción, tendremos que cambiar la implementación.
Para hacer esto, cree una nueva instancia de
CompletableFuture
y finalícela manualmente después de que una de las operaciones genere una excepción:
CompletableFuture<List<T>> result = new CompletableFuture<>(); futures.forEach(f -> f .handle((__, ex) -> ex == null || result.completeExceptionally(ex)));
... pero luego tenemos que lidiar con el escenario cuando todo el futuro se completará con éxito. Esto se puede hacer fácilmente usando el método mejorado allOf (), y luego simplemente terminar el futuro manualmente:
allOf(futures).thenAccept(result::complete);
Ahora podemos combinar todo para formar la solución final:
public static <T> CompletableFuture<List<T>> allOfShortcircuiting(Collection<CompletableFuture<T>> futures) { CompletableFuture<List<T>> result = new CompletableFuture<>(); for (CompletableFuture<?> f : futures) { f.handle((__, ex) -> ex == null || result.completeExceptionally(ex)); } allOf(futures).thenAccept(result::complete); return result; }
CompletableFuture # anyOf
Comencemos con la firma del método:
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
Podemos detectar inmediatamente los mismos problemas que con el método discutido anteriormente:
- El método acepta varios objetos
CompletableFuture
que contienen objetos de diferentes tipos. - El método devuelve un
CompletableFuture
contiene un objeto de tipo Object
.
Según tengo entendido, el
CompletableFuture#allOf
fue diseñado para ser utilizado como un dispositivo de señalización. Pero
CompletableFuture#anyOf
no sigue esta filosofía, devolviendo un
CompletableFuture<Object>
, que es aún más confuso.
Eche un vistazo al siguiente ejemplo donde estoy tratando de procesar CompletableFuture que contiene datos de diferentes tipos:
CompletableFuture<Integer> f1 = CompletableFuture.completedFuture(1); CompletableFuture<String> f2 = CompletableFuture.completedFuture("2"); Integer result = CompletableFuture.anyOf(f1, f2) .thenApply(r -> { if (r instanceof Integer) { return (Integer) r; } else if (r instanceof String) { return Integer.valueOf((String) r); } throw new IllegalStateException("unexpected object type!"); }).join();
Bastante incómodo, ¿no?
Afortunadamente, esto es bastante fácil de adaptar para un escenario más común (esperando uno de los muchos valores futuros que contengan el mismo tipo) cambiando la firma e introduciendo la conversión directa de tipos.
Por lo tanto, con nuestras mejoras, podemos reutilizar los métodos existentes y obtener el resultado de forma segura:
public static <T> CompletableFuture<T> anyOf(List<CompletableFuture<T>> cfs) { return CompletableFuture.anyOf(cfs.toArray(new CompletableFuture[0])) .thenApply(o -> (T) o); } public static <T> CompletableFuture<T> anyOf(CompletableFuture<T>... cfs) { return CompletableFuture.anyOf(cfs).thenApply(o -> (T) o); }
Código fuente
El código fuente se puede encontrar en
Github .
Eso es todo. Nos vemos en el
curso .