Atualizado: 02/11/2025

Este conteúdo é original e não foi gerado por inteligência artificial.

CompletableFuture e Promises: Processamento de Resultados Assíncronos em Java

O objeto Future representa uma tarefa assíncrona e permite obter seu resultado com o método get(). Contudo, get() bloqueia a thread atual até que o resultado esteja disponível. O CompletableFuture, que implementa Future, introduz uma abordagem diferente: ele permite reagir ao resultado da tarefa assim que ele é produzido, por meio de callbacks (funções de retorno).

Além disso, CompletableFuture implementa a interface CompletionStage, que representa uma etapa de computação assíncrona. Essa combinação possibilita criar fluxos de processamento semelhantes aos promises de linguagens como JavaScript — ou seja, operações que podem ser encadeadas e executadas de forma não bloqueante.


Criação de um CompletableFuture

Um CompletableFuture pode ser criado diretamente com o construtor:

CompletableFuture<Integer> future = new CompletableFuture<>();

No entanto, é mais comum criá-lo já associado a uma tarefa. Para isso, o método estático supplyAsync() é o mais usado:

static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier)
static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)

O parâmetro supplier é uma função que fornece o valor a ser processado. A primeira versão executa a tarefa no pool comum (ForkJoinPool.commonPool()), enquanto a segunda permite especificar um executor personalizado.


Exemplo: cálculo de fatorial

int number = 5;

CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {

    if (number < 1) throw new RuntimeException("Number must be greater than 0");

    int n = 1;
    int result = 1;
    while (n <= number) result *= n++;
    return result;
});

Aqui, a expressão lambda enviada para supplyAsync() calcula o fatorial do número. Essa função é executada de forma assíncrona e retorna um valor que será processado posteriormente.

O tipo Supplier não pode lançar exceções verificadas, portanto, utiliza-se RuntimeException em caso de erro.


Adicionando um callback

Depois de criado o CompletableFuture, é possível registrar uma função que será chamada assim que o resultado estiver disponível. Entre os métodos disponíveis, destacam-se:

  • thenAccept(Consumer<? super T> action) — consome o resultado e não retorna nada.
  • thenApply(Function<? super T, ? extends U> fn) — aplica uma função e retorna um novo CompletableFuture com o valor transformado.
  • thenRun(Runnable action) — executa uma ação independente do resultado.
  • thenCompose(Function<? super T, ? extends CompletionStage<U>> fn) — encadeia outra computação assíncrona.

O método thenAccept() é uma forma simples de lidar com o resultado:

import java.util.concurrent.*;
import java.util.function.Supplier;

class Program {

    public static void main(String[] args) throws Exception {

        System.out.println("Main thread started...");

        int number = 5;

        Supplier<Integer> task = () -> {
            if (number < 1) throw new RuntimeException("Number must be greater than 0");
            int n = 1;
            int result = 1;
            while (n <= number) result *= n++;
            return result;
        };

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(task);

        future.thenAccept(result ->
                System.out.printf("Factorial of %d is %d\n", number, result));

        System.out.println("Main thread works...");
        Thread.sleep(2000);
        System.out.println("Main thread finished...");
    }
}

Saída esperada:

Main thread started...
Main thread works...
Factorial of 5 is 120
Main thread finished...

Nesse exemplo:

  • A tarefa de cálculo do fatorial é executada de forma assíncrona.
  • O callback em thenAccept() é chamado automaticamente quando o resultado está disponível.
  • O main continua executando enquanto a tarefa roda em paralelo.

Observação sobre threads daemon

As threads usadas por padrão em CompletableFuture vêm do ForkJoinPool.commonPool(), que cria threads daemon — ou seja, threads em segundo plano. A JVM encerra a execução assim que todas as threads não daemon terminam. Por isso, se o main finalizar rapidamente, o callback pode não ser executado.

No exemplo acima, a chamada Thread.sleep(2000) mantém a thread principal ativa por tempo suficiente para que o callback termine sua execução.


Executor personalizado

Em vez de depender do pool padrão, é possível criar um executor dedicado para gerenciar as threads:

import java.util.concurrent.*;
import java.util.function.Supplier;

class Program {

    public static void main(String[] args) throws Exception {

        System.out.println("Main thread started...");

        int number = 5;

        Supplier<Integer> task = () -> {
            if (number < 1) throw new RuntimeException("Number must be greater than 0");
            int n = 1;
            int result = 1;
            while (n <= number) result *= n++;
            try { Thread.sleep(2000); } catch (InterruptedException _) {}
            return result;
        };

        ExecutorService executor = Executors.newCachedThreadPool();

        CompletableFuture<Integer> future = CompletableFuture.supplyAsync(task, executor);

        future.thenAccept(result ->
                System.out.printf("Factorial of %d is %d\n", number, result));

        System.out.println("Main thread works...");
        System.out.println("Main thread finished...");

        executor.close();
    }
}

Saída provável:

Main thread started...
Main thread works...
Main thread finished...
Factorial of 5 is 120

Aqui, o executor é responsável tanto pela execução da tarefa quanto pelo callback. Como a tarefa inclui uma pausa de 2 segundos, o resultado aparece após o encerramento da thread principal.


Encadeamento de callbacks

Os métodos do CompletableFuture permitem formar cadeias de processamento. Por exemplo, com thenApply() é possível transformar o resultado antes de exibi-lo:

import java.util.concurrent.*;
import java.util.function.Supplier;
import java.util.function.Function;
import java.util.function.Consumer;

class Program {

    public static void main(String[] args) throws Exception {

        System.out.println("Main thread started...");

        int number = 5;

        Supplier<Integer> factorialTask = () -> {
            if (number < 1) throw new RuntimeException("Number must be greater than 0");
            int n = 1;
            int result = 1;
            while (n <= number) result *= n++;
            return result;
        };

        Function<Integer, Integer> doubleTask = result -> result * 2;

        Consumer<Integer> printTask = result ->
                System.out.printf("Final result: %d\n", result);

        ExecutorService executor = Executors.newCachedThreadPool();

        CompletableFuture
                .supplyAsync(factorialTask, executor)
                .thenApply(doubleTask)
                .thenAccept(printTask);

        System.out.println("Main thread works...");
        System.out.println("Main thread finished...");

        executor.close();
    }
}

Saída esperada:

Main thread started...
Main thread works...
Main thread finished...
Final result: 240

A execução ocorre da seguinte forma:

  1. supplyAsync() calcula o fatorial.
  2. thenApply() dobra o resultado.
  3. thenAccept() imprime o valor final.

Essa estrutura cria uma pipeline assíncrona, na qual cada etapa é executada após a conclusão da anterior, sem bloqueios na thread principal.


Resumo

  • CompletableFuture é uma extensão de Future com suporte a callbacks e composição de resultados.
  • Permite reagir automaticamente quando uma tarefa assíncrona termina.
  • Pode usar o pool padrão (ForkJoinPool) ou um executor personalizado.
  • Callbacks como thenAccept(), thenApply() e thenRun() processam resultados sem bloquear.
  • Cadeias de callbacks permitem transformar e combinar valores de forma assíncrona.
  • Evita o bloqueio da thread principal e facilita a programação reativa em Java.
Política de Privacidade

Copyright © www.programicio.com Todos os direitos reservados

É proibida a reprodução do conteúdo desta página sem autorização prévia do autor.

Contato: programicio@gmail.com