Olá novamente. Antecipando o início do curso, "Java Developer" preparou uma tradução de material útil para você.
Existem dois métodos no
CompletableFuture
cujo design me surpreende:
- CompletableFuture # allOf
- CompletableFuture # anyOf
Neste artigo, veremos o que há de errado com eles e como eles podem ser feitos mais convenientes.
CompletableFuture # allOf
Vamos dar uma olhada na assinatura do método:
public static CompletableFuture<Void> allOf(CompletableFuture<?>... cfs) {
Há pelo menos duas questões controversas aqui:
- O método aceita vários objetos
CompletableFuture
que retornam objetos de tipos diferentes. - O método retorna
CompletableFuture
, que retorna Void
Além disso, alguns podem não gostar do número variável de parâmetros, então vamos ver essa parte também.
CompletableFuture<Void>
frequentemente usado como um sinal para concluir uma operação; no entanto, ao fazer uma pequena alteração na API,
esse método pode ser usado como um dispositivo de sinalização e como uma transportadora dos resultados de todas as operações concluídas . Vamos tentar fazer isso.
CompletableFuture assíncrono # allOf
Primeiro, vamos criar a assinatura certa.
É justo supor que na maioria dos casos seja necessário processar uma lista de
CompletableFuture
homogêneo e retornar um
CompletableFuture
contendo uma lista de resultados:
public static <T> CompletableFuture<List<T>> allOf( Collection<CompletableFuture<T>> futures) {
As entranhas do método original são provavelmente mais complexas do que o esperado:
static CompletableFuture<Void> andTree( CompletableFuture<?>[] cfs, int lo, int hi) { CompletableFuture<Void> d = new CompletableFuture<Void>(); if (lo > hi)
Portanto, em vez de criá-lo do zero, tentaremos reutilizar o que já está no método original, como se fosse destinado a ser usado como um dispositivo de sinalização de conclusão ... e, em seguida, basta alterar o resultado nulo para a lista futura:
CompletableFuture<List<CompletableFuture<T>>> i = futures.stream() .collect(collectingAndThen( toList(), l -> CompletableFuture.allOf(l.toArray(new CompletableFuture[0])) .thenApply(__ -> l)));
Até agora tudo bem. Nós conseguimos
CompletableFuture<List<CompletableFuture<T> >
>
CompletableFuture<Void>
vez de
CompletableFuture<Void>
, que já é bom. Mas não precisamos de uma lista de resultados futuros, precisamos de uma lista de resultados.
Agora podemos simplesmente processar a lista e remover o futuro indesejado dela. É perfeitamente normal chamar os métodos de junção CompletableFuture #, porque sabemos que eles nunca serão bloqueados (neste momento, todo o futuro já está concluído):
CompletableFuture<List<T>> result = intermediate .thenApply(list -> list.stream() .map(CompletableFuture::join) .collect(toList()));
Agora vamos combinar tudo isso em uma solução 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())))); }
Conclusão assíncrona e em quedaFuture # allOf
Se houver exceções, o CompletableFuture # allOf original aguarda a conclusão de todas as operações restantes.
E se quisermos relatar a conclusão da operação quando ocorrer uma exceção nela, teremos que alterar a implementação.
Para fazer isso, crie uma nova instância do
CompletableFuture
e encerre-a manualmente após uma das operações gerar uma exceção:
CompletableFuture<List<T>> result = new CompletableFuture<>(); futures.forEach(f -> f .handle((__, ex) -> ex == null || result.completeExceptionally(ex)));
... mas precisamos lidar com o cenário em que todo o futuro será concluído com sucesso. Isso pode ser feito facilmente usando o método allOf () aprimorado e, em seguida, simplesmente finalize o futuro manualmente:
allOf(futures).thenAccept(result::complete);
Agora podemos combinar tudo para formar a solução 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
Vamos começar com a assinatura do método:
public static CompletableFuture<Object> anyOf(CompletableFuture<?>... cfs) {
Podemos detectar imediatamente os mesmos problemas do método discutido acima:
- O método aceita vários objetos
CompletableFuture
que contêm objetos de tipos diferentes. - O método retorna um
CompletableFuture
contendo um objeto do tipo Object
.
Tanto quanto eu entendo, o
CompletableFuture#allOf
foi projetado para ser usado como um dispositivo de sinalização. Mas
CompletableFuture#anyOf
não segue essa filosofia, retornando um
CompletableFuture<Object>
, que é ainda mais confuso.
Dê uma olhada no exemplo a seguir, onde estou tentando processar o CompletableFuture que contém dados 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 desconfortável, não é?
Felizmente, isso é muito fácil de se adaptar a um cenário mais comum (aguardando um dos muitos futuros que contêm valores do mesmo tipo) alterando a assinatura e introduzindo a conversão direta do tipo.
Assim, com nossas melhorias, podemos reutilizar métodos existentes e trazer com segurança o resultado:
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 fonte
O código fonte pode ser encontrado no
Github .
Só isso. Vejo você no
curso .