CodeGym /Cursos /JAVA 25 SELF /Future, CompletionHandler: tratamento da conclusão de ope...

Future, CompletionHandler: tratamento da conclusão de operações

JAVA 25 SELF
Nível 56 , Lição 1
Disponível

1. Future: “Vou esperar até ficar pronto”

Operações assíncronas em Java são como enviar uma carta pelo correio: você não fica parado na caixa e esperando a resposta, mas vai cuidar de outras coisas. Quando a carta chegar — você simplesmente recebe uma notificação. No código, é preciso entender que a operação foi concluída e obter o resultado ou o erro. Em Java há duas abordagens: via Future e via CompletionHandler.

Com Future você recebe um “comprovante” — uma promessa de resultado no futuro. Você pode, de tempos em tempos, verificar se já está pronto ou aguardar o resultado chamando get().

Com CompletionHandler você não precisa esperar: especifica o handler antecipadamente, e ele será chamado automaticamente — em caso de sucesso ou de erro.

Como isso funciona?

Quando você chama o método assíncrono read() ou write() de AsynchronousFileChannel, pode obter um objeto do tipo Future<Integer>. É como uma senha de atendimento eletrônico: a operação começou e, a qualquer momento, você pode perguntar: “E aí, já ficou pronto?”.

Assinatura do método

Future<Integer> read(ByteBuffer dst, long position)

ou

Future<Integer> write(ByteBuffer src, long position)
  • dst — o buffer para o qual os dados serão lidos.
  • src — o buffer de onde os dados serão escritos.
  • position — a posição no arquivo para leitura/gravação.

Exemplo: leitura assíncrona com Future

Código que lê de forma assíncrona os primeiros 1024 bytes do arquivo e aguarda explicitamente o resultado:

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;
import java.io.IOException;

public class FutureReadExample {
    public static void main(String[] args) {
        Path path = Path.of("example.txt");
        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            // Iniciamos a leitura assíncrona
            Future<Integer> future = channel.read(buffer, 0);

            // ... aqui você pode fazer outras coisas enquanto o arquivo é lido

            // Aguardamos a conclusão da operação (chamada bloqueante!)
            int bytesRead = future.get(); // Pode lançar InterruptedException, ExecutionException

            System.out.println("Bytes lidos: " + bytesRead);

            // Passamos a ler do buffer
            buffer.flip();
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }
        } catch (Exception e) {
            System.err.println("Erro ao ler o arquivo: " + e);
        }
    }
}
  • O método future.get() bloqueia a thread até a conclusão da operação — é como “ficar esperando no telefone” até ligarem.
  • Você pode chamar future.isDone() para saber se a operação terminou e só então chamar get().
  • Se a operação terminar com erro, get() lançará ExecutionException (com a causa dentro).

Quando o Future é conveniente?

Future é bom quando você quer iniciar uma tarefa e, depois, quando for conveniente, aguardar o resultado: ler um arquivo, fazer uma requisição, calcular algo em background. A abordagem também é útil para iniciar várias operações em paralelo e, depois, coletar os resultados.

Mas, se você precisa de verdadeira assincronicidade sem bloqueios e com notificação automática de conclusão, vale considerar o CompletionHandler.

2. CompletionHandler: “Me chame quando terminar”

CompletionHandler é uma interface que você implementa e passa para o método read() ou write(). Quando a operação terminar (ou ocorrer um erro), o Java chamará seu handler automaticamente. É como deixar um número de telefone: “Ligue quando estiver pronto”.

Assinatura do método

void read(ByteBuffer dst,
          long position,
          A attachment,
          CompletionHandler<Integer, ? super A> handler)
  • dst — buffer para leitura.
  • position — posição no arquivo.
  • attachment — um objeto arbitrário que será passado ao handler (pode ser null ou, por exemplo, o nome do arquivo).
  • handler — o seu handler.

Interface CompletionHandler

public interface CompletionHandler<V, A> {
    void completed(V result, A attachment);
    void failed(Throwable exc, A attachment);
}
  • completed é chamado em caso de sucesso; result — número de bytes, attachment — seu objeto.
  • failed é chamado em caso de erro; exc — a exceção.

Exemplo: leitura assíncrona com CompletionHandler

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.io.IOException;

public class CompletionHandlerReadExample {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("example.txt");
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        channel.read(buffer, 0, "example.txt", new CompletionHandler<Integer, String>() {
            @Override
            public void completed(Integer result, String attachment) {
                System.out.println("Arquivo " + attachment + " lido, bytes: " + result);
                buffer.flip();
                while (buffer.hasRemaining()) {
                    System.out.print((char) buffer.get());
                }
                closeChannel();
            }

            @Override
            public void failed(Throwable exc, String attachment) {
                System.err.println("Erro ao ler o arquivo " + attachment + ": " + exc);
                closeChannel();
            }

            private void closeChannel() {
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Erro ao fechar o canal: " + e);
                }
            }
        });

        // Importante: o método main pode terminar antes da leitura se não "segurarmos" a thread!
        // Em aplicativos reais a thread geralmente não termina imediatamente (por exemplo, servidor, UI).
        try {
            Thread.sleep(500); // Damos tempo para a operação assíncrona (apenas para o exemplo!)
        } catch (InterruptedException ignored) {}
    }
}

Passamos para read() um handler anônimo que sabe o que fazer após a conclusão. Em completed recebemos o resultado e o processamos; em failed — reagimos ao erro. Não se esqueça de fechar o canal dentro do handler para não deixar recursos abertos.

Há um detalhe: o método main pode terminar antes de a operação assíncrona concluir, por isso no exemplo foi adicionado Thread.sleep(500) — apenas para ver o resultado. Em aplicativos reais esse truque normalmente não é necessário.

Quando é melhor usar CompletionHandler?

CompletionHandler é bom quando você precisa de verdadeira assincronicidade, sem esperas e sem bloqueios: você inicia a operação e descreve o que fazer ao concluir. Isso é crítico para UI (JavaFX, Swing), para que a interface não “trave”, e é útil em servidores — as threads não ficam ociosas esperando; elas só são usadas quando há trabalho.

4. Comparação: Future e CompletionHandler

Abordagem Bloqueia a thread? Quando usar? Exemplo de cenário
Future Sim (em get()) Processamento sequencial simples Cópia de arquivo, relatórios
CompletionHandler Não Truly async, UI, servidor, operações paralelas Servidor, GUI, IO em massa
  • Future — mais simples de entender, mas exige bloqueio para obter o resultado.
  • CompletionHandler — um pouco mais complexo, mas oferece verdadeira assincronicidade e não bloqueia as threads.

5. Prática: gravação assíncrona em arquivo com CompletionHandler

Exemplo que grava uma string no arquivo de forma assíncrona e informa o resultado:

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class CompletionHandlerWriteExample {
    public static void main(String[] args) throws IOException {
        String text = "Olá, mundo assíncrono!";
        ByteBuffer buffer = ByteBuffer.wrap(text.getBytes(StandardCharsets.UTF_8));
        Path path = Path.of("async_output.txt");

        AsynchronousFileChannel channel = AsynchronousFileChannel.open(
                path, StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        channel.write(buffer, 0, path, new CompletionHandler<Integer, Path>() {
            @Override
            public void completed(Integer result, Path attachment) {
                System.out.println("Gravados com sucesso " + result + " bytes no arquivo " + attachment);
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Erro ao fechar o canal: " + e);
                }
            }

            @Override
            public void failed(Throwable exc, Path attachment) {
                System.err.println("Erro ao gravar no arquivo " + attachment + ": " + exc);
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Erro ao fechar o canal: " + e);
                }
            }
        });

        // Vamos segurar o main para ver o resultado (apenas para o exemplo)
        try {
            Thread.sleep(500);
        } catch (InterruptedException ignored) {}
    }
}
Comentários
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION