Menu

Exceções em Java: erros, rastreamentos de pilha e lançamento

O que é uma exceção em Java, como ler um rastreamento de pilha, a distinção entre exceções verificadas e não verificadas, a hierarquia de exceções e como lançar as suas próprias.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

O que é uma exceção

Uma exceção é a maneira que o Java tem de dizer "algo deu errado e não consigo continuar normalmente". Em vez de retornar um valor ruim ou corromper seus dados silenciosamente, o ambiente de execução cria um objeto de exceção descrevendo o problema e o lança. A execução normal para nesse ponto, e o Java começa a procurar código que saiba como lidar com a situação.

Se ninguém a tratar, a exceção chega ao topo do seu programa, a JVM imprime um rastreamento de pilha e o processo encerra com um status diferente de zero.

Repare que "after" nunca é impresso. No instante em que numbers[5] é avaliado, uma ArrayIndexOutOfBoundsException é lançada e o restante de main é abandonado.

Lendo um rastreamento de pilha

Quando uma exceção fica sem tratamento, você recebe uma saída como esta. Parece intimidante, mas é apenas uma lista:

Exception in thread "main" java.lang.ArithmeticException: / by zero
	at Main.divide(Main.java:8)
	at Main.main(Main.java:4)

Leia de cima para baixo:

  • A primeira linha é o tipo (ArithmeticException) e uma mensagem (/ by zero).
  • Cada linha at ... é um quadro de pilha. O de cima é onde a exceção foi de fato lançada - divide na linha 8. Abaixo dele está o chamador, main na linha 4.

A primeira linha at é onde você olha primeiro. Os quadros abaixo dela respondem "como chegamos até aqui?".

Execute isto e o rastreamento aponta direto para a linha a / b, depois mostra main como o chamador. Essa cadeia de chamadas é a ferramenta de depuração mais útil que o Java oferece de graça.

A hierarquia de exceções

Toda exceção é um objeto, e todas descendem de Throwable. Os dois ramos que importam:

  • Error - problemas graves que a JVM levanta, como OutOfMemoryError ou StackOverflowError. Em geral você não os captura.
  • Exception - problemas que seu programa pode razoavelmente prever e tratar. Dentro dela fica RuntimeException, a mãe dos erros do dia a dia, como NullPointerException.
Throwable
├── Error                 (don't catch: OutOfMemoryError, StackOverflowError)
└── Exception
    ├── IOException        (checked)
    ├── SQLException       (checked)
    └── RuntimeException   (unchecked)
        ├── NullPointerException
        ├── ArithmeticException
        └── ArrayIndexOutOfBoundsException

Como são classes reais, uma exceção pode carregar uma mensagem e você pode perguntar a ela sobre si mesma:

getMessage() retorna o texto após os dois pontos no rastreamento de pilha; getClass().getSimpleName() dá o tipo da exceção pelo nome.

Verificadas versus não verificadas

Esta é a distinção que mais confunde os iniciantes.

  • Exceções não verificadas estendem RuntimeException. Geralmente significam um erro no seu código - um null que você não esperava, um índice ruim, uma divisão por zero. O compilador não obriga você a tratá-las.
  • Exceções verificadas estendem Exception, mas não RuntimeException (por exemplo, IOException). Representam condições fora do seu controle - um arquivo ausente, uma conexão de rede que caiu. O compilador obriga você a capturá-las ou declará-las.

Se um método pode lançar uma exceção verificada, ele precisa dizer isso com throws, e todo chamador precisa lidar com ela:

Remova throws Exception de main e o código nem sequer compila - é o compilador impondo o contrato das verificadas. Exceções não verificadas nunca exigem isso.

Lançando as suas próprias

Você não só reage às exceções - você pode levantá-las. Use throw com um novo objeto de exceção para sinalizar que um argumento ou estado é inválido. Isso é muito melhor do que retornar um valor mágico como -1 e torcer para que o chamador o verifique.

Inclua sempre uma mensagem que explique o que deu errado e, idealmente, o valor problemático - o seu eu do futuro lendo o rastreamento de pilha vai agradecer. IllegalArgumentException e IllegalStateException são as duas que você mais usará ao validar entradas.

Armadilhas comuns

  • Engolir exceções. Capturar uma exceção e não fazer nada (um bloco vazio) esconde bugs. No mínimo, registre-a; normalmente você deve tratá-la ou relançá-la.
  • Capturar Exception de forma ampla demais. Capturar a Exception base (ou pior, Throwable) pode mascarar problemas que você não pretendia tratar. Capture o tipo específico que você espera.
  • Ler o rastreamento de baixo para cima. O quadro mais relevante é a linha at de cima, não a de baixo. Comece por ela.
  • Confundir Error com Exception. Não tente se recuperar de OutOfMemoryError ou StackOverflowError; corrija a causa raiz.

Próximo: try-catch

Agora você sabe o que são as exceções e como lê-las. A próxima página aborda como realmente tratá-las: envolver código arriscado em um bloco try, recuperar-se no catch e executar código de limpeza no finally para que seu programa continue rodando em vez de quebrar.

Perguntas frequentes

O que é uma exceção em Java?

Uma exceção é um objeto que representa um problema detectado enquanto um programa é executado - como dividir por zero, acessar um índice além do final de um array ou chamar um método em null. Quando o problema ocorre, o Java lança a exceção: ele interrompe o fluxo normal e procura por código capaz de tratá-la. Se nada a tratar, o programa imprime um rastreamento de pilha e encerra.

Qual é a diferença entre uma exceção verificada e uma não verificada em Java?

Exceções verificadas (subclasses de Exception, mas não de RuntimeException, por exemplo IOException) precisam ser capturadas ou declaradas com throws - o compilador impõe isso. Exceções não verificadas (subclasses de RuntimeException, por exemplo NullPointerException, ArrayIndexOutOfBoundsException) geralmente indicam erros de programação e não exigem declaração. Error (como OutOfMemoryError) também é não verificada e, em geral, não deve ser capturada.

Como leio um rastreamento de pilha em Java?

Leia de cima para baixo. A primeira linha nomeia o tipo da exceção e a mensagem (por exemplo, java.lang.ArithmeticException: / by zero). Cada linha at ... abaixo é um quadro de pilha que mostra o método, o arquivo e o número da linha, começando por onde a exceção foi lançada e voltando pelos chamadores. A primeira linha at quase sempre é o lugar para olhar primeiro.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR