Diferenças entre Future e CompletableFuture
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.