Tratamento de erros em Java: do try/catch às técnicas avançadas
O tratamento de exceções é uma das pedras angulares de uma aplicação Java confiável. A maioria dos desenvolvedores conhece a construção básica try-catch-finally, mas em um nível avançado, o Java oferece ferramentas muito mais flexíveis e eficientes. Neste artigo, abordaremos não apenas a abordagem clássica, mas também recursos modernos, como multi-catch, try-with-resources, criação de exceções personalizadas e estratégias para tratá-las. O material é voltado para desenvolvedores que já escrevem em Java com confiança e desejam aprofundar seus conhecimentos.
1. Clássico: try-catch-finally e suas nuances
A construção básica continua sendo a base. No entanto, em um nível avançado, é importante entender como ela funciona internamente e quais erros até mesmo programadores experientes cometem.
Vamos considerar um exemplo com entrada e saída de arquivos:
import java.io.*;
public class ClassicExample { public static void main(String[] args) { BufferedReader reader = null; try { reader = new BufferedReader(new FileReader("file.txt")); String line = reader.readLine(); System.out.println(line); } catch (IOException e) { System.err.println("Erro de leitura: " + e.getMessage()); } finally { try { if (reader != null) { reader.close(); } } catch (IOException e) { System.err.println("Erro ao fechar: " + e.getMessage()); } } }}Pontos-chave para o nível avançado:
- Ordem dos blocos catch: exceções mais específicas devem vir primeiro. Se colocar
Exceptionantes deIOException, o compilador emitirá um erro. - O bloco finally sempre é executado, mesmo se houver um
returnno try ou catch. Isso é importante para liberar recursos. - Supressão de exceções: se uma exceção ocorrer tanto no try quanto no finally, a exceção do finally "engole" a primeira. Para evitar isso, use
Throwable.getSuppressed().
2. Multi-catch: elegância e legibilidade
A partir do Java 7, tornou-se possível combinar várias exceções em um único bloco catch. Isso reduz significativamente a duplicação de código.
Sem multi-catch, o código fica verboso:
try { // código que pode lançar IOException ou SQLException} catch (IOException e) { logger.error("Erro de entrada/saída", e); throw new RuntimeException(e);} catch (SQLException e) { logger.error("Erro de banco de dados", e); throw new RuntimeException(e);}Com multi-catch, ele se torna conciso:
try { // código} catch (IOException | SQLException e) { logger.error("Erro ao trabalhar com recursos", e); throw new RuntimeException(e);}Importante: a variável e em multi-catch é final (ou effectively final). Você não pode atribuir outro valor a ela. Isso foi feito para segurança de tipos.
3. Try-with-resources: gerenciamento automático de recursos
Esta é, talvez, a melhoria mais poderosa para o tratamento de recursos. Ela fecha automaticamente os recursos que implementam a interface AutoCloseable (ou Closeable).
Compare com o exemplo clássico acima. Veja como fica com try-with-resources:
import java.io.*;
public class TryWithResourcesExample { public static void main(String[] args) { try (BufferedReader reader = new BufferedReader(new FileReader("file.txt")); BufferedWriter writer = new BufferedWriter(new FileWriter("output.txt"))) {
Strin