Capturar uma exceção em vez de travar
Você já sabe que, quando algo dá errado, o Java lança uma exceção, e uma exceção não tratada interrompe o programa com um stack trace. Um bloco try-catch é como você assume o controle: você envolve o código arriscado em try e, se ele lançar uma exceção, o Java pula para o bloco catch correspondente em vez de travar.
No instante em que 10 / 0 lança a exceção, o resto do bloco try é ignorado e o controle passa para o catch. Depois que o catch termina, a execução continua normalmente: o programa não morre.
A variável do catch guarda a exceção
O e em catch (ArithmeticException e) é um objeto de verdade. Ele carrega informações sobre o que deu errado, sendo a mais útil uma mensagem:
e.getMessage() retorna uma descrição curta. Durante a depuração, e.printStackTrace() despeja o rastreamento completo mostrando exatamente de onde veio a exceção; recorra a ele quando só uma mensagem não for suficiente para encontrar a causa.
Capture o tipo específico, não tudo
Um bloco catch só captura exceções que correspondem ao seu tipo declarado (ou a uma subclasse). O maior erro de iniciante é capturar Exception para fazer um problema "sumir":
// Não faça isso: esconde bugs reais
try {
doWork();
} catch (Exception e) {
// engole NullPointerException, erros de digitação, erros de lógica... tudo
}
Capture o tipo mais restrito que você realmente consegue tratar. Se você espera uma entrada numérica inválida, capture NumberFormatException. Qualquer coisa que você não previu deve poder se propagar, para que você de fato fique sabendo dela em vez de continuar silenciosamente em um estado quebrado.
Tratar vários tipos de exceção
Você pode empilhar vários blocos catch. O Java os verifica de cima para baixo e executa o primeiro que corresponde, então ordene-os do mais específico para o mais geral:
Quando dois tipos compartilham o mesmo tratamento, use um único multi-catch com | em vez de duplicar o bloco:
Uma armadilha: um tipo mais geral precisa vir depois dos específicos. Colocar catch (Exception e) primeiro torna os blocos seguintes, mais específicos, inalcançáveis, e o compilador rejeita isso.
finally sempre é executado
Um bloco finally é executado depois do try e de qualquer catch, independentemente do que aconteceu: sucesso, uma exceção capturada ou até um return antecipado. É onde fica a limpeza que sempre precisa acontecer.
"Closing resource" é impresso quer a exceção seja lançada ou não. Evite, porém, fazer return de dentro do finally: isso pode descartar silenciosamente uma exceção ou sobrescrever um valor retornado pelo bloco try.
try-with-resources fecha as coisas por você
Quando você trabalha com algo que precisa ser fechado (um arquivo, uma conexão de rede, um statement de banco de dados), declará-lo dentro de try (...) o fecha automaticamente quando o bloco termina, mesmo que uma exceção seja lançada. Funciona com qualquer tipo que implemente AutoCloseable.
Isso substitui o antigo padrão de abrir no try e fechar no finally, e é menos sujeito a erros porque você não pode esquecer de fechar. Prefira-o sempre que estiver lidando com um recurso que pode ser fechado.
Não use exceções para o fluxo de controle normal
try-catch é para situações excepcionais, não para condições comuns. Capturar uma exceção é mais caro do que uma verificação simples, e deixa o código mais difícil de ler. Se você consegue testar a condição antes, faça isso:
// Evite: usar um catch para testar uma chave ausente
try {
process(map.get(key).trim());
} catch (NullPointerException e) {
// tratar a chave ausente
}
// Prefira: verificar explicitamente
String value = map.get(key);
if (value != null) {
process(value.trim());
}
Reserve o catch para coisas genuinamente fora do seu controle: entrada de usuário inválida, arquivos ausentes, falhas de rede.
A seguir: NullPointerException
A exceção mais comum que você vai capturar (e causar) em Java é a NullPointerException: ela aparece no instante em que você chama um método em algo que acabou sendo null. A seguir, vamos investigar exatamente o que a dispara, como ler o stack trace dela e os hábitos que evitam que ela aconteça desde o começo.
Perguntas frequentes
Como se usa o try-catch em Java?
Coloque o código que pode lançar uma exceção dentro de um bloco try e adicione um bloco catch indicando o tipo de exceção que você quer tratar: try { risky(); } catch (IOException e) { ... }. Se o código do try lançar uma exceção correspondente, o Java pula direto para o bloco catch em vez de travar. A variável (e) guarda o objeto da exceção, então você pode ler a mensagem dela com e.getMessage().
Para que serve o bloco finally em Java?
Um bloco finally é executado depois do try/catch aconteça o que acontecer: seja se o código funcionou, lançou uma exceção ou até deu return antecipadamente. É o lugar para a limpeza que sempre precisa acontecer, como fechar um arquivo ou liberar um lock. Para fechar recursos, o try-with-resources costuma ser mais limpo porque fecha tudo automaticamente.
Você deve capturar Exception ou um tipo de exceção específico?
Capture o tipo mais específico que você realmente consegue tratar. Capturar Exception (ou pior, Throwable) engole todos os problemas, inclusive bugs como NullPointerException, e esconde a causa real. Capture NumberFormatException se for isso que você espera e deixe as exceções inesperadas se propagarem para que você fique sabendo delas.