Java try...catch
Trate erros em tempo de execução no Java com blocos try...catch para manter seu programa funcionando quando algo dá errado.
try/catch é a menor unidade de tratamento de exceções em Java. Você envolve o código arriscado em try e o segue com um ou mais blocos catch que dizem o que fazer se um determinado tipo de exceção for lançada. Juntos, eles formam uma única instrução; não é possível ter um sem o outro (ou sem um finally, que veremos em um capítulo posterior).
Anatomia
try {
// code that might throw
} catch (ExceptionType e) {
// code that runs only if a matching exception was thrown
}Alguns detalhes passam despercebidos à primeira vista:
- A variável no
catch(epor convenção) tem escopo limitado ao bloco catch. Você pode nomeá-la como quiser;eeexsão as escolhas mais comuns. - O parâmetro do catch é efetivamente final — você pode lê-lo, mas não deve reatribuí-lo. Tratá-lo como imutável torna o código mais fácil de acompanhar.
- O bloco
trytem seu próprio escopo. Variáveis declaradas dentro dele não são visíveis para ocatchnem após a instrução. Se você precisar de um valor calculado dentro dotrymais tarde, declare-o fora.
String content;
try {
content = Files.readString(path);
} catch (IOException e) {
content = ""; // works because `content` was declared outside the try
}
System.out.println(content);Quais catches correspondem
Um bloco catch (T e) é executado quando a exceção lançada é uma instância de T — incluindo qualquer subtipo de T. Portanto:
catch (Exception e)captura quase tudo:IOException,NullPointerException, suas próprias exceções personalizadas.catch (RuntimeException e)captura erros de tempo de execução, mas não exceções verificadas comoIOException.catch (NullPointerException e)captura apenas NPEs (e subclasses, das quais tipicamente não há nenhuma).
Dentro do try, apenas o primeiro catch correspondente é executado. Se você listar um tipo mais abrangente antes de um mais específico, o catch mais específico ficará inacessível e o compilador recusará:
try { ... }
catch (Exception e) { ... } // matches everything
catch (IOException e) { ... } // ERROR: unreachableSempre liste os catches do mais específico ao mais geral.
O que fazer em um catch
Um bloco catch não é um lugar para fazer as exceções desaparecerem. É um lugar para decidir o que fazer diante de uma falha conhecida. As opções realistas são:
- Recuperar — repetir a tentativa, usar um valor padrão, mudar para um recurso diferente. Este é o melhor caso, e é mais raro do que se pensa.
- Registrar e relançar — gravar detalhes e deixar a exceção continuar se propagando para um tratador de nível superior que saiba o que fazer.
- Encapsular e relançar — traduzir uma exceção de baixo nível para uma que se encaixe no vocabulário desta camada (
IOException→ConfigLoadException). - Traduzir para um valor de retorno — quando a falha é genuinamente esperada (por exemplo, ao analisar a entrada do usuário), retorne
Optional.empty()ou um valor sentinela.
O que não se deve fazer é capturar uma exceção e continuar silenciosamente sem registrá-la ou agir sobre ela. É assim que os bugs desaparecem no ar. Voltaremos a isso no capítulo de boas práticas.
Lendo informações de exceção
O parâmetro do catch é um objeto. Três métodos que você usará constantemente:
e.getMessage()— a descrição legível por humanos. Pode sernull.e.toString()— nome da classe mais a mensagem, por exemplojava.io.IOException: file not found.e.printStackTrace()— escreve o rastreamento emSystem.err. Conveniente durante a depuração, mas use um logger real no código de produção (ele encaminha o rastreamento pelo mesmo canal que todo o resto).
Um quarto método, e.getCause(), retorna a exceção subjacente quando uma foi encapsulada — útil quando você precisa entender a falha original dentro de uma camada de tradução.
Capturar Exception vs. capturar Throwable
Você pode escrever catch (Throwable t) e isso capturará tudo — incluindo Errors como OutOfMemoryError. Não faça isso. Errors significam que a JVM está em apuros e o seu código não está em condições de reagir a eles de forma sensata. Capturar Throwable mascara bugs que deveriam travar o processo.
Atenha-se a Exception ou a um de seus subtipos. Se você realmente precisar registrar o inesperado antes de deixá-lo morrer, o formato correto é catch (RuntimeException e) { log; throw; }.
Um exemplo prático
Um pequeno utilitário que analisa um inteiro de cada linha de entrada. Algumas linhas são números válidos, outras não, e uma é null. Usamos try/catch para continuar passando pelas linhas inválidas e contabilizar o que aconteceu. Chamar line.trim() lança uma NullPointerException na entrada null, enquanto Integer.parseInt lança uma NumberFormatException em qualquer coisa que não seja um inteiro — dois tipos distintos de exceção, cada um com seu próprio catch.
A saída é:
not an integer: "hello"
null line — skipped
not an integer: "3.14"
---
parsed: 3, failed: 3, sum: 118
Dois pontos a observar. Primeiro, o programa termina. Sem os catches, a primeira linha inválida encerraria a execução e nada depois dela seria contabilizado. Segundo, as duas falhas são distinguidas: a linha null segue o ramo NullPointerException e exibe sua própria mensagem, enquanto "hello" e "3.14" seguem o ramo NumberFormatException. Selecionar catches por tipo é como você evita que falhas diferentes se misturem.
O que vem a seguir
Um único catch é o caso mais simples. O código real geralmente precisa lidar com várias falhas diferentes ao mesmo tempo. Continue para Múltiplos blocos catch e multi-catch em Java. Depois disso, o bloco finally e o try-with-resources completam a sintaxe, enquanto throw e throws cobrem o outro lado: sinalizar falhas a partir do seu próprio código.