As exceções em Java são um pouco diferentes se comparadas com as de outras linguagens de programação. Eu não sei se esse é o seu caso, mas, quando comecei a estudar Java, eu sentia essa diferença enquanto programava e não sabia explicar qual era o motivo.
Por exemplo, por que algumas exceções produzem erros de compilação quando não são tratadas enquanto outras não? E, em relação a esses erros produzidos, por que eles são “magicamente” resolvidos ao especificar os tipos das exceções após a palavra-chave throws
, na assinatura do método pelo qual são lançadas?
No final das contas, a resposta é simples (ou nem tanto, conforme explico na última seção). Basta entender como as exceções em Java foram projetadas e a razão pela qual elas foram organizadas da forma que foram. É isso que eu tentarei te explicar até o fim deste artigo.
Introdução às exceções
Por baixo do pano, todo método define uma espécie de contrato com o código que o consome. Nele, especifica-se detalhes como a forma de utilizá-lo (ou seja, seus parâmetros) e o comportamento esperado ao chamá-lo (produz um valor de retorno ou efeitos colaterais).
No entanto, há situações em que um método não é capaz de seguir o seu fluxo padrão, geralmente em decorrência de fatores externos, como o ambiente em que o programa está sendo executado.
Nesse contexto, um exemplo comumente utilizado é o de um método que recebe do usuário o nome de um arquivo e tenta abri-lo: caso o nome informado não exista, o arquivo não pode ser aberto, e o método é impedido de prosseguir da forma esperada.
É esse aspecto que as exceções representam. Por meio delas, é possível sinalizar a ocorrência de situações excepcionais que podem acontecer ao invocar um método, e essas situações podem ser detectadas e “tratadas” pelo desenvolvedor por meio de um mecanismo adequado — os famosos blocos try-catch.
// código omitido
public void doSomething() {
try {
doAnotherThing(); // potencialmente lança ExampleException
} catch (ExampleException e) {
// tratamento da exceção
}
}
// código omitido
Tradicionalmente, o tratamento de uma exceção não é obrigatório e não precisa ser feito pelo método que a lançou (nem mesmo por outros métodos que o tenham invocado!), o que é uma vantagem até certo ponto, pois possibilita que as exceções “borbulhem” (bubble) de baixo para cima na call stack até o local mais adequado para o tratamento. A figura 1 ilustra esse comportamento.
Figura 1 - As exceções “borbulham” na call stack até que um método as capture.
Checked vs. Unchecked Exceptions
Ocorre que a linguagem Java considera as exceções lançadas por um método uma parte do seu próprio contrato, e sua sintaxe obriga que elas sejam informadas a qualquer código que o invoque. Em outras palavras, as exceções precisam constar na assinatura do método, da mesma forma que seus parâmetros e valor de retorno.
Esse requisito é justificado pela noção de que o desenvolvedor deve ser capaz de antecipar e contornar exceções, seja exibindo uma mensagem de erro ao usuário, seja buscando uma rota alternativa para a ação solicitada. É por esse motivo que especificamos as exceções após a palavra-chave throws
.
// código omitido
public void doAnotherThing() throws ExampleException {
// código omitido
if (condition) {
/*
ExampleException representa uma situação que é prevista ao
invocar o método doAnotherThing, mas que ocorre apenas em
condições excepcionais (por exemplo, se "condition"
for verdadeiro). Espera-se que o código consumidor
antecipe essa situação e a trate de maneira adequada.
*/
throw new ExampleException()
}
// código omitido
}
// código omitido
Dessa forma, sempre que lidamos com um código que possivelmente lança exceções, temos duas opções: ou as tratamos imediatamente (com try-catch) ou as tornamos parte do próprio contrato do método no qual trabalhamos (com throws
em sua assinatura), esperando que um outro método faça o tratamento. Esse comportamento é denominado Catch or Specify Requirement.
Figura 2 - Todos os métodos que trabalham com uma exceção, mas não a tratam, precisam torná-la parte do seu próprio contrato com “throws”.
Para garantir que o desenvolvedor tome conhecimento sobre as exceções possivelmente lançadas por um método, o compilador Java acusa um erro de compilação caso uma exceção não tenha sido tratada (ou, ao menos, postergada com throws
). Sendo assim, as exceções em Java são conhecidas como checked exceptions (exceções verificadas).
No entanto, algumas exceções são naturalmente internas à aplicação (não devem ser expostas pelo contrato) e não podem ser contornadas de forma satisfatória, pois geralmente decorrem de defeitos de programação (como uma tentativa de acessar campos em um valor nulo ou realizar uma divisão por zero). Essas exceções são conhecidas como unchecked exceptions (exceções não verificadas).
Unchecked exceptions vs. Runtime exceptions
Como o nome sugere, as unchecked exceptions não são verificadas pelo compilador e, portanto, não é obrigatório tratá-las, o que as torna semelhantes às presentes em outras linguagens de programação. Isso ocorre pois, embora seja possível capturá-las, o ideal seria detectar e corrigir os bugs que as causam o mais rápido possível.
Na prática, todas as exceções em Java são derivadas da classe Exception
e são verificadas por padrão. Porém, é possível criar exceções não verificadas a partir da classe RuntimeException
(que também é uma Exception
!). A figura 3 ilustra a hierarquia de exceções em Java.
Figura 3 - Hierarquia de exceções em Java. As classes Throwable e Error foram omitidas para fins de simplificação.
Controvérsia
Os princípios básicos sobre quando utilizar cada tipo de exceção foram abordados até agora, mas há uma controvérsia entre os desenvolvedores Java, que questionam as vantagens e desvantagens de checked e unchecked exceptions.
Em meio a vários outros argumentos, alguns afirmam que checked exceptions dificultam a manutenção da base de código em projetos de larga escala, enquanto outros argumentam que elas são essenciais para garantir que desenvolvedores sejam capazes de antecipar situações excepcionais ao consumir uma API.
Não entrarei em mais detalhes, pois trata-se de um assunto complexo e que foge do escopo deste artigo, porém, disponibilizarei algumas leituras que encontrei sobre o assunto juntamente às referências.
De modo geral, me parece ser um consenso que as checked exceptions têm, de fato, a sua utilidade, desde que não abusemos do seu uso, sob o risco de dificultar a manutenção do código. Além disso, convém nos lembrarmos de sempre manter a consistência com as convenções já utilizadas dentro de um projeto.
Conclusão
As exceções em Java derivam da classe Exception
e são consideradas parte do contrato do método pelo qual são lançadas, devendo ser especificadas em sua assinatura por meio da cláusula throws
. Nesse mesmo sentido, essas exceções são chamadas de checked exceptions, pois têm seu tratamento garantido pelo compilador, que acusa um erro caso não sejam tratadas.
No entanto, há exceções especiais — chamadas de unchecked exceptions — que se originam da classe RuntimeException
(também derivada de Exception
) e não são verificadas pelo compilador, pois são internas ao escopo de um método e, por esse motivo, não são expostas pelo seu contrato.
Referências
-
The Call Stack | Baeldung on Computer Science (baeldung.com)
-
Unchecked Exceptions — The Controversy (The Java™ Tutorials > Essential Java Classes > Exceptions) (oracle.com)
-
The Catch or Specify Requirement (The Java™ Tutorials > Essential Java Classes > Exceptions) (oracle.com)
-
artima - The Trouble with Checked Exceptions (www.artima.com)
-
java - The case against checked exceptions - Stack Overflow (stackoverflow.com)
-
What Is an Exception? (The Java™ Tutorials > Essential Java Classes > Exceptions) (oracle.com)
-
Google Answers: The origin of checked exceptions (archive.org)
Discussões sobre o uso de checked x unchecked exceptions
-
artima - The Trouble with Checked Exceptions (www.artima.com)
-
java - The case against checked exceptions - Stack Overflow (stackoverflow.com)
-
Bruce Eckel’s MindView, Inc: Does Java need Checked Exceptions? (archive.org)
-
Java’s checked exceptions were a mistake (and here’s what I would like to do about it) (archive.org)
-
java - When to choose checked and unchecked exceptions - Stack Overflow (stackoverflow.com)
Atribuições
- Fotografia da capa por Gabrielle Henderson em Unsplash