W3docs

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:

  1. 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.
  2. 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 de Exception) — 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.
  • Exception verificada (tudo o mais sob Exception) — falhas recuperáveis que o programa deve antecipar. IOException, SQLException. O compilador exige que você as capture ou as declare na cláusula throws do 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:

  1. Desfaz a pilha — o método atual sai abruptamente, depois seu chamador faz o mesmo, e assim por diante.
  2. Em cada frame, verifica se há um bloco try { ... } catch (SomeType e) { ... } cujo catch corresponde ao tipo da exceção (ou a um supertipo).
  3. O primeiro catch correspondente vence. O controle salta para lá, a pilha para de ser desfeita e a execução retoma no bloco catch.
  4. 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ê.

java— editable, runs on the server

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/finally e para que serve cada cláusula.
  • Múltiplos catches e o atalho multi-catch.
  • try-with-resources para qualquer coisa que precise ser fechada.
  • Lançar exceções você mesmo com throw e declará-las com throws.
  • 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.

Prática

Prática
Um método `loadConfig()` lê um arquivo e lança `IOException` em caso de falha. O chamador envolve a chamada em `try { loadConfig(); } catch (RuntimeException e) { ... }`. O IO falha. O que acontece?
Um método `loadConfig()` lê um arquivo e lança `IOException` em caso de falha. O chamador envolve a chamada em `try { loadConfig(); } catch (RuntimeException e) { ... }`. O IO falha. O que acontece?
Was this page helpful?