Diferença entre thenApply e thenCompose no CompletableFuture

No Java, CompletableFuture oferece duas formas principais de continuar uma computação assíncrona: thenApply e thenCompose. Ambas produzem um novo CompletableFuture, mas a forma como tratam o valor intermediário é distinta.

  1. Conceitos fundamentais

thenApply aplica uma transformação síncrona sobre o resultado de um estágio anterior. A função recebe o valor e devolve um novo valor, que será automaticamente embrulhado em outro CompletableFuture.

public <U> CompletableFuture<U> thenApply(Function<? super T, ? extends U> fn)

thenCompose é o equivalente a um flatMap para CompletionStage: a função recebe o resultado e devolve um novo CompletionStage, que é "achatado" para um único nível de CompletableFuture.

public <U> CompletableFuture<U> thenCompose(Function<? super T, ? extends CompletionStage<U>> fn)
  1. Comparação

Característica thenApply thenCompose
Operação realizada Transforma o resultado Encadeia outra operação assíncrona
Retorno da função Um valor U Um CompletionStage<U>
Estrutura do futuro resultante Pode gerar CompletableFuture<CompletableFuture<U>> Sempre CompletableFuture<U>
Uso ideal Mapear, formatar ou calcular algo simples Invocar uma nova tarefa assíncrona que depende do resultado anterior
  1. Exemplo com thenApply

Considere um futuro que devolve um preço base. Aplicar um desconto simples é uma transformação direta:

CompletableFuture<Integer> precoBase = CompletableFuture.supplyAsync(() -> 150);

CompletableFuture<Integer> precoComDesconto = precoBase.thenApply(preco -> {
   int desconto = preco / 10; // 10% de desconto
   return preco - desconto;
});

System.out.println(precoComDesconto.join()); // 135
  1. Exemplo com thenCompose

Agora imagine que o frete só pode ser calculado de forma assíncrona, dependendo do preço final. thenCompose é a escolha natural:

CompletableFuture<Integer> precoBase = CompletableFuture.supplyAsync(() -> 150);

CompletableFuture<Integer> precoFinal = precoBase.thenCompose(preco ->
   CompletableFuture.supplyAsync(() -> {
       int frete = 25;
       return preco + frete;
   })
);

System.out.println(precoFinal.join()); // 175
  1. Problema do aninhamento

Se usarmos thenApply para retornar outro CompletableFuture, o tipo resultante será aninhado:

CompletableFuture<CompletableFuture<Integer>> aninhado = precoBase
   .thenApply(preco -> CompletableFuture.supplyAsync(() -> preco + 25));

System.out.println(aninhado.join().join()); // 175

Com thenCompose, esse aninhamento desaparece e o resultado permanece em um único nível, tornando o pipeline assíncrono mais simples e seguro:

CompletableFuture<Integer> achatado = precoBase
   .thenCompose(preco -> CompletableFuture.supplyAsync(() -> preco + 25));

System.out.println(achatado.join()); // 175

Por isso, prefira thenApply quando a próxima etapa apenas transforma o valor; use thenCompose quando ela precisar iniciar uma nova operação assíncrona.

Tags: java CompletableFuture thenApply thenCompose asynchronous-programming

Publicado em 8-5 21:10