Introdução
No âmbito da programação assíncrona e concorrente em Java, Future e CompletableFuture servem como ferramentas essenciais para gerenciar e executar tarefas assíncronas. Ambas as estruturas oferecem maneiras de representar o resultado de uma computação assíncrona, mas diferem significativamente em termos de funcionalidade, flexibilidade e facilidade de uso. Compreender as distinções entre Future e CompletableFuture é crucial para os desenvolvedores Java que desejam projetar sistemas assíncronos robustos e eficientes.
No seu cerne, um Future representa o resultado de uma computação assíncrona que pode ou não estar completa. Ele permite que os desenvolvedores submetam tarefas para execução assíncrona e obtenham um identificador para recuperar o resultado em um momento posterior. Embora o Future forneça um mecanismo básico para programação assíncrona, suas capacidades são um tanto limitadas em termos de composição, tratamento de exceções e gerenciamento de fluxo assíncrono.
Por outro lado, CompletableFuture introduz uma abordagem mais avançada e versátil para programação assíncrona em Java. Ele estende as capacidades do Future oferecendo uma API fluente para compor, combinar e lidar com tarefas assíncronas com maior flexibilidade e controle. CompletableFuture capacita os desenvolvedores a construir fluxos de trabalho assíncronos complexos, lidar com exceções de forma elegante e coordenar a execução de várias tarefas de maneira transparente.
Neste post, vamos aprofundar as diferenças entre Future e CompletableFuture, explorando suas respectivas características, casos de uso e melhores práticas. Ao compreender as vantagens e compromissos distintos de cada estrutura, os desenvolvedores podem tomar decisões informadas ao projetar sistemas assíncronos e aproveitar a concorrência em aplicativos Java. Vamos embarcar em uma jornada para explorar as nuances de Future e CompletableFuture no ecossistema Java.
Casos de Uso usando Future
Processamento Paralelo: Utilize Future para paralelizar tarefas independentes através de múltiplas threads e reunir resultados de forma assíncrona. Por exemplo, processar múltiplos arquivos simultaneamente.
I/O Assíncrona: Ao realizar operações de leitura e escrita que são blocantes, como ler de um arquivo ou fazer requisições de rede, você pode usar Future para executar essas operações em threads separadas e continuar com outras tarefas enquanto aguarda a conclusão da I/O.
Execução e Coordenação de Tarefas: Utilize Future para executar tarefas de forma assíncrona e coordenar a conclusão delas. Por exemplo, em um servidor web, lidar com múltiplas requisições simultaneamente usando Future para cada processamento de requisição.
Tratamento de Timeout: Você pode definir Timeouts para tarefas Future para evitar esperar indefinidamente pela conclusão. Isso é útil ao lidar com recursos com tempos de resposta imprevisíveis.
Casos de Uso para CompletableFuture
Padrão Async/Await: CompletableFuture suporta uma API fluente para encadear operações assíncronas, permitindo que você expresse fluxos de trabalho assíncronos complexos de forma clara e concisa, semelhante ao padrão async/await em outras linguagens de programação.
Combinação de Resultados: Utilize CompletableFuture para combinar os resultados de múltiplas tarefas assíncronas, seja esperando que todas as tarefas sejam concluídas (allOf) ou combinando os resultados de duas tarefas (thenCombine, thenCompose).
Tratamento de Exceções: CompletableFuture oferece mecanismos robustos de tratamento de exceções, permitindo lidar com exceções lançadas durante computações assíncronas de forma elegante usando métodos como exceptionally ou handle.
Gráficos de Dependência: Você pode construir gráficos de dependência complexos de tarefas assíncronas usando CompletableFuture, onde a conclusão de uma tarefa desencadeia a execução de outra, permitindo um controle refinado sobre o fluxo de execução.
Callbacks Não-Blocantes: CompletableFuture permite que você anexe callbacks que são executados após a conclusão do futuro, permitindo o tratamento não blocante de resultados ou erros.
Completar uma Future Manualmente: Ao contrário de Future, você pode completar um CompletableFuture manualmente usando métodos como complete, completeExceptionally ou cancel. Essa funcionalidade pode ser útil em cenários em que você deseja fornecer um resultado ou lidar explicitamente com casos excepcionais.
Exemplos
Criação e finalização
Exemplo de código usando Future na criação e finalização de um fluxo.
ExecutorService executor = Executors.newSingleThreadExecutor();
Future<Integer> future = executor.submit(() -> {
Thread.sleep(2000);
return 10;
});
Exemplo de código usando CompletableFuture na criação e finalização de um fluxo.
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
return 10;
});
Em CompletableFuture, métodos como supplyAsync permitem execuções assíncronas sem que seja necessário usar um ExecutorService como é mostrado no primeiro exemplo.
Encadeando Ações
Exemplo abaixo usando Future em um encadeamento de ações.
Future<Integer> future = executor.submit(() -> 10);
Future<String> result = future.thenApply(i -> "Result: " + i);
Agora, um exemplo usando CompletableFuture para encadear ações.
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<String> result = completableFuture.thenApply(i -> "Result: " + i);
CompletableFuture oferece uma API fluente (thenApply, thenCompose, etc.) para encadear ações, facilitando o uso de expressões de fluxos assíncronos.
Lidando com Exceções
Lidando com exceção usando Future.
Future<Integer> future = executor.submit(() -> {
throw new RuntimeException("Exception occurred");
});
Lidando com exceção usando CompletableFuture.
CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
throw new RuntimeException("Exception occurred");
});
Aguardando uma conclusão de uma tarefa
// Future
Integer result = future.get();
// CompletableFuture
Integer result = completableFuture.get();
Ambos Future e CompletableFuture fornecem o método get() responsável por aguardar a conclusão de um processamento e trazer os resultados esperados.
Combinado Múltiplos CompletableFutures
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20);
CompletableFuture<Integer> combinedFuture = future1.thenCombine(future2, (x, y) -> x + y);
CompletableFuture fornecem métodos como thenCombine, thenCompose, e allOf que desempenham combinações ou compõem múltiplas tarefas (tasks) assíncronas.
Conclusão
No dinâmico cenário da programação assíncrona e concorrente em Java, tanto Future quanto CompletableFuture se destacam como ferramentas indispensáveis, oferecendo vantagens e casos de uso distintos.
Podemos ver as diferenças entre Future e CompletableFuture, enquanto o Future fornece um mecanismo básico para representar o resultado de computações assíncronas, suas capacidades são um tanto limitadas quando se trata de composição, tratamento de exceções e gerenciamento de fluxo assíncrono.
Por outro lado, o CompletableFuture surge como uma alternativa poderosa e flexível, estendendo as funcionalidades do Future com uma API fluente para composição, combinação e manipulação de tarefas assíncronas com maior controle e elegância.
A escolha entre Future e CompletableFuture depende dos requisitos específicos e complexidades da tarefa em questão. Para operações assíncronas simples ou ao trabalhar dentro dos limites de bases de código existentes, o Future pode ser suficiente. No entanto, em cenários que exigem fluxos de trabalho assíncronos mais sofisticados, tratamento de exceções ou coordenação de tarefas, o CompletableFuture oferece uma solução convincente com seu conjunto de recursos rico e API intuitiva.
Comments