Exceções em Java
Visão geral das exceções em Java — o que são, a hierarquia de exceções e por que o tratamento de exceções é importante.
Uma exceção é o que acontece quando seu programa encontra uma situação que não consegue lidar no caminho atual — um arquivo que não existe, um número dividido por zero, um índice de array fora dos limites. Em vez de retornar uma resposta errada ou corromper o estado silenciosamente, o Java interrompe o método atual, cria um objeto de exceção descrevendo o que deu errado e começa a procurar código que saiba como lidar com isso. Aprender o mecanismo de exceções é aprender como o Java diz o que deu errado, onde e o que você pode fazer a respeito.
O que é uma exceção na prática
Uma exceção é um objeto Java comum. Especificamente, uma instância de uma classe que herda de java.lang.Throwable. Quando algo dá errado, a JVM (ou seu próprio código) cria um desses objetos e o lança. A partir desse momento, o programa segue um fluxo de controle diferente até que um bloco catch aceite a exceção ou a thread termine.
O objeto carrega informações: um tipo (a classe — NullPointerException, IOException, etc.), uma mensagem (uma descrição legível por humanos) e um stack trace (a cadeia de chamadas congelada no momento em que a exceção foi criada). Quando você lê uma exceção no console, está lendo essas três coisas.
Exception in thread "main" java.lang.ArithmeticException: / by zero
at calc.Money.divide(Money.java:42)
at calc.App.main(App.java:11)A primeira linha é o tipo + mensagem. As linhas recuadas são o stack trace, com a chamada mais recente primeiro.
Por que exceções em vez de códigos de erro
Linguagens mais antigas — C é o exemplo clássico — sinalizam falhas com códigos de retorno: cada função retorna um int, você o verifica, e se for negativo algo deu errado. Essa abordagem tem dois problemas:
- Você pode ignorar o valor de retorno. Um chamador que esquece de verificá-lo não vê nenhuma falha imediata, e o bug aparece mais tarde em um lugar confuso.
- O caminho de erro polui o caminho feliz. Cada linha de trabalho real é envolvida em
if (err) return err;.
As exceções invertem isso. O padrão é que uma exceção não tratada interrompe a execução, de forma visível. Você opta por tratá-la onde realmente tem uma estratégia. O caminho feliz permanece limpo; o caminho de recuperação fica em seu próprio bloco.
As três coisas que podem dar errado
O Java separa tudo que é Throwable em três categorias, e a diferença importa porque a linguagem as trata de forma diferente:
Error— a própria JVM está com problemas.OutOfMemoryError,StackOverflowError. Você não captura esses em código normal. Geralmente não há nada útil que você possa fazer.RuntimeException(uma subclasse deException) — bugs de programação que aparecem em tempo de execução.NullPointerException,IndexOutOfBoundsException,ClassCastException. O compilador não força você a tratá-los, porque em código correto eles não deveriam ocorrer.Exceptionverificada (tudo o mais sobException) — falhas recuperáveis que o programa deve antecipar.IOException,SQLException. O compilador exige que você as capture ou as declare na cláusulathrowsdo seu método.
A linha entre "bug de programação" (exceção de tempo de execução) e "falha antecipada" (exceção verificada) é uma das decisões de design mais debatidas do Java. Voltaremos a isso em exceções verificadas vs. não verificadas em Java.
Como lançar e capturar funcionam
Quando um throw é executado, a JVM:
- Desfaz a pilha — o método atual sai abruptamente, depois seu chamador faz o mesmo, e assim por diante.
- Em cada frame, verifica se há um bloco
try { ... } catch (SomeType e) { ... }cujocatchcorresponde ao tipo da exceção (ou a um supertipo). - O primeiro
catchcorrespondente vence. O controle salta para lá, a pilha para de ser desfeita e a execução retoma no bloco catch. - Se nada corresponder, a thread morre. Em um programa de thread única, isso significa que a JVM imprime o stack trace e encerra.
É por isso que uma exceção lançada pode percorrer muitos métodos antes de ser capturada — cada lançamento não capturado sobe mais um frame.
Um primeiro contato
Você não precisa escrever throw para encontrar uma exceção — o próprio Java as lança quando algo dá errado. Aqui está o exemplo mais simples: dividir um inteiro por zero. A JVM cria um ArithmeticException para você.
Sem o try/catch, a terceira iteração travaria o programa inteiro e a quarta nunca seria executada. Com ele, a falha fica contida: recebemos uma mensagem de erro e o loop continua. Essa capacidade — de limitar o dano de uma falha — é o objetivo de todo o mecanismo de exceções.
O que esta parte do livro aborda
Os capítulos restantes desta parte formam o vocabulário de trabalho do tratamento de exceções em Java, um conceito por vez:
- A forma de
try/catch/finallye para que serve cada cláusula. - Múltiplos catches e o atalho multi-catch.
try-with-resourcespara qualquer coisa que precise ser fechada.- Lançar exceções você mesmo com
throwe declará-las comthrows. - Verificadas vs. não verificadas: qual usar e quando.
- A hierarquia completa de classes.
- Criar seus próprios tipos de exceção para o seu domínio.
- Os princípios que distinguem um bom tratamento de exceções de ruído defensivo.
Leia na ordem — cada capítulo pressupõe o anterior.
O que vem a seguir
O tratamento de exceções começa com o constructo mais fundamental: o bloco try/catch. Continue para Java try...catch.