Java Dica de código

Dica de Código: Programação Assíncrona com CompletableFuture no Java

Domine a programação assíncrona com CompletableFuture no Java e impulsione o desempenho de sua aplicação. Leia agora!

há 5 meses 3 semanas

Formação Desenvolvedor Java
Conheça a formação em detalhes

A programação assíncrona é um conceito crucial na construção de aplicativos modernos, onde a responsividade é fundamental. O Java 8 introduziu a classe CompletableFuture, que oferece uma maneira elegante e eficiente de realizar operações assíncronas. Neste artigo, exploraremos como utilizar o CompletableFuture para criar código assíncrono, melhorando o desempenho e a responsividade de sua aplicação. Continuar lendo para aprender mais sobre essa poderosa ferramenta.

O Básico do CompletableFuture

O CompletableFuture permite que você execute tarefas em segundo plano sem bloquear a thread principal, tornando a execução de operações demoradas mais eficiente. Vamos começar com um exemplo básico:

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class ExemploCompletableFuture {

    public static void main(String[] args) 
        throws ExecutionException, InterruptedException 
    {
        // Criando um CompletableFuture que será completado no futuro
        var futuro = CompletableFuture.supplyAsync(() -> {
            // Simulando uma operação demorada
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // Retornando um resultado
            return 42;
        });

        // Obtendo o resultado quando estiver pronto
        int resultado = futuro.get();
        System.out.println("Resultado: " + resultado); // Imprime "Resultado: 42"
    }
}

Neste exemplo, criamos um CompletableFuture que executa uma operação demorada em segundo plano e, em seguida, obtemos o resultado quando estiver pronto. Isso permite que a thread principal continue sua execução sem esperar pela conclusão da tarefa assíncrona.

Executando Ações Após a Conclusão

O CompletableFuture também permite a execução de ações após a conclusão da tarefa. No exemplo a seguir, definimos uma ação que será executada quando o CompletableFuture estiver completo:

Java - Fundamentos
Curso Java - Fundamentos
Conhecer o curso

import java.util.concurrent.CompletableFuture;

public class ExemploCompletableFuture {

    public static void main(String[] args) {
        // Criando um CompletableFuture que será completado no futuro
        var futuro = CompletableFuture.supplyAsync(() -> {
            // Simulando uma operação demorada
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // Retornando um resultado
            return 42;
        });

        /* 
         * Definindo uma ação a ser executada quando 
         * o CompletableFuture estiver completo 
         */
        var novoFuturo = futuro.thenAccept(resultado -> {
            System.out.println("Resultado: " + resultado); // Imprime "Resultado: 42"
        });

        // Aguardando a conclusão (isso é opcional, dependendo do contexto)
        novoFuturo.join();
    }
}

Nesse caso, a ação definida com thenAccept imprime o resultado quando a operação assíncrona estiver completa.

Combinando Resultados de Múltiplos CompletableFuture

O CompletableFuture também permite a combinação de resultados de várias tarefas assíncronas. No exemplo a seguir, criamos dois CompletableFutures com operações assíncronas e combinamos seus resultados:

import java.util.concurrent.CompletableFuture;

public class ExemploCompletableFuture {

    public static void main(String[] args) {
        // Criando dois CompletableFutures com operações assíncronas
        var futuro1 = CompletableFuture.supplyAsync(() -> 10);
        var futuro2 = CompletableFuture.supplyAsync(() -> 20);

        /* 
         * Combinando os resultados dos CompletableFutures 
         * quando ambos estiverem prontos
         */
        var resultado = futuro1
            .thenCombine(futuro2, (valor1, valor2) -> valor1 + valor2);

        // Definindo uma ação a ser executada quando o resultado estiver pronto
        resultado.thenAccept(soma -> {
            System.out.println("Soma: " + soma); // Imprime "Soma: 30"
        });

        // Aguardando a conclusão (isso é opcional, dependendo do contexto)
        resultado.join();
    }
}

Neste exemplo, combinamos os resultados de futuro1 e futuro2 usando thenCombine, o que nos permite realizar operações com os valores retornados por ambos.

Transformando Resultados

O CompletableFuture também permite a transformação dos resultados. No exemplo a seguir, aplicamos uma transformação ao resultado de uma tarefa assíncrona:

Java - Orientação a objetos
Curso Java - Orientação a objetos
Conhecer o curso

import java.util.concurrent.CompletableFuture;

public class ExemploCompletableFuture {

    public static void main(String[] args) {
        // Criando um CompletableFuture com uma operação assíncrona
        var futuro = CompletableFuture.supplyAsync(() -> 10);

        // Aplicando uma transformação ao resultado quando estiver pronto
        var resultado = futuro.thenApply(valor -> "Resultado: " + valor);

        /* Definindo uma ação a ser executada quando 
         * o resultado transformado estiver pronto
         */
        resultado.thenAccept(mensagem -> {
            System.out.println(mensagem); // Imprime "Resultado: 10"
        });

        // Aguardando a conclusão (isso é opcional, dependendo do contexto)
        resultado.join();
    }
}

Neste exemplo, usamos thenApply para transformar o resultado de futuro em uma mensagem formatada.

Tratamento de Erros

O CompletableFuture também permite o tratamento de erros de maneira elegante. No exemplo a seguir, lidamos com uma exceção lançada por uma tarefa assíncrona:

import java.util.concurrent.CompletableFuture;

public class ExemploCompletableFuture {
    
    public static void main(String[] args) {
        // Criando uma CompletableFuture que irá falhar no futuro
        var futuro = CompletableFuture.supplyAsync(() -> {
            throw new RuntimeException("Erro!");
        });

        // Tratando o erro
        var futuroTratado = futuro.exceptionally(ex -> {
            System.out.println("Erro: " + ex.getMessage());
            return null;
        });

        // Aguardando o término da execução
        futuroTratado.join(); // Erro: java.lang.RuntimeException: Erro!
    }
}

Nesse caso, utilizamos exceptionally para lidar com a exceção lançada pela tarefa assíncrona, permitindo que a aplicação continue a execução, mesmo em caso de erro.

Melhorando o Desempenho com CompletableFuture

Vamos agora explorar um exemplo prático de como o CompletableFuture pode ser usado para melhorar o desempenho de uma aplicação. Imagine que você precisa fazer três chamadas de API que demoram um tempo específico para responder. Se fizermos essas chamadas de forma sequencial, o tempo total será a soma dos tempos de cada chamada. No entanto, se fizermos as chamadas de forma concorrente, o tempo total será o tempo da chamada mais demorada.

Java - Collections - Parte 1
Curso Java - Collections - Parte 1
Conhecer o curso

import java.util.concurrent.CompletableFuture;

public class ExemploCompletableFuture {
    
    // Simulando uma chamada de API que demora um tempo específico
    public static String fakeApi(long tempo) {
        try {
            Thread.sleep(tempo);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        return "OK";
    }

    public static void sequencial() {
        // Chamando a API de forma sequencial
        var inicio = System.currentTimeMillis();
        fakeApi(1000);
        fakeApi(2000);
        fakeApi(1500);
        var fim = System.currentTimeMillis();
        System.out.println("Tempo total: " + (fim - inicio));
    }

    public static void concorrente() {
        // Chamando a API de forma concorrente
        var inicio = System.currentTimeMillis();
        var futuro1 = CompletableFuture.supplyAsync(() -> fakeApi(1000));
        var futuro2 = CompletableFuture.supplyAsync(() -> fakeApi(2000));
        var futuro3 = CompletableFuture.supplyAsync(() -> fakeApi(1500));

        /*
         * Podemos usar o método CompletableFuture.allOf() para aguardar o término de
         * todas as chamadas.
         */
        CompletableFuture.allOf(futuro1, futuro2, futuro3).join();
        var fim = System.currentTimeMillis();
        System.out.println("Tempo total: " + (fim - inicio));
    }
    
    public static void main(String[] args) {
        sequencial(); // Imprime "Tempo total: 4501"
        concorrente(); // Imprime "Tempo total: 2005"
    }
}

Neste exemplo, comparamos a execução sequencial das chamadas de API com a execução concorrente usando CompletableFuture. O resultado demonstra como a programação assíncrona pode melhorar significativamente o desempenho da aplicação.

Conclusão

O CompletableFuture é uma ferramenta poderosa para lidar com programação assíncrona no Java. Com ele, você pode executar tarefas em segundo plano, combinar resultados, transformar dados e lidar com erros de forma eficiente. Além disso, o uso do CompletableFuture pode melhorar o desempenho de sua aplicação, tornando-a mais responsiva e eficaz. Portanto, considere incorporar o CompletableFuture em seus projetos para obter benefícios significativos na programação assíncrona.

Java - Stream API
Curso Java - Stream API
Conhecer o curso

Esperamos que este artigo tenha fornecido uma compreensão sólida do CompletableFuture e como usá-lo em sua jornada de programação assíncrona no Java. Se você quiser saber mais, não hesite em explorar a documentação oficial do Java e experimentar com exemplos adicionais. O futuro da programação assíncrona no Java está repleto de oportunidades emocionantes!

Autor(a) do artigo

Cleyson Lima
Cleyson Lima

Professor, programador, fã de One Piece e finge saber cozinhar. Cleyson é graduando em Licenciatura em Informática pelo IFPI - Campus Teresina Zona Sul, nos anos de 2019 e 2020 esteve envolvido em vários projetos coordenados pela secretaria municipal de educação da cidade de Teresina, onde o foco era introduzir alunos da rede pública no mundo da programação e robótica. Hoje é instrutor dos cursos de Spring na TreinaWeb, mas diz que seu coração sempre pertencerá ao Python.

Todos os artigos

Artigos relacionados Ver todos