É notável a grande quantidade de programadores e desenvolvedores que não sabem como bem tratar as exceções que ocorrem nas aplicações que os mesmos desenvolvem, sejam elas de um âmbito desktop, web ou mobile. Neste cenário, muitas perguntas os cercam, tais como: “Onde devo implementar a exceção?” ou “De que tipo ela deve ser?” ou ainda “Deve tratar ou relançar?”. São perguntas como essas que nos mostra o quão desenvolvedores das mais diversas linguagens de programação necessitam entender melhor como essas mesmas linguagens lidam com o tratamento de exceções nativo, assim como entender os padrões e conceitos que a comunidade tem como “aprovados” e funcionais.

Em se tratando de Java, especificamente, ao descermos mais o nível de detalhamento da tecnologia, chegamos a questões como hierarquia de exceções, formas de tratamento em relação às diversas versões da linguagem, assim como as velhas diferenças entre as exceções checadas e não checadas (checked e unchecked exceptions), objeto deste artigo. Inúmeros artigos e fóruns na web tratam do tema nos mais diversos contextos e cenários de negócio. Neste artigo, tentaremos representar algumas das principais dúvidas acerca dessa diferença através de exemplos, questões e explanações.

Diferenças

A principal diferença entre uma exceção checada e outra não checada é que a primeira obriga o desenvolvedor a tratá-la, isto é, usar umas das duas formas básicas de tratamento de exceção no Java: captura ou tratamento; enquanto a segunda exceção não o faz. O tratamento, no caso das unchecked exceptions se torna opcional e você trata apenas se quiser.

Outra forma de encarar essa diferença é pensar que você utilizará uma checked exception toda vez que você souber que ela irá acontecer. Como assim? Simples, se no seu método Java você sentir que determinado erro irá acontecer e que você precisa aponta-lo para as demais camadas ou trata-lo ali mesmo, então você terá, sem sombra de dúvidas, um caso de checked exception. Veja a Listagem 1, por exemplo. Nela é possível observar que o código objetiva o lançamento de uma exceção para que a mesma seja trata por outras classes que chamarem o método em questão, uma vez que você conhece a causa do erro e sabe que ele irá acontecer.

Listagem 1. Exemplo de checked exception

public double calcularIMC(double altura, double peso) 
  throws IMCException {
    double IMC = (peso)/(altura*altura);
    if (IMC <= 18.5) {
       throw new IMCException(“Abaixo do peso normal!”);
    }
    // Restante do código...
  }

Esse tipo de situação “força” o lançamento/tratamento da exceção. E difere das situações em que não sabemos que o erro vai acontecer. Mas como não saber que o erro “não vai acontecer...”? Também é simples, quando ele acontece em tempo de execução (runtime).

Para entender isso, você precisa primeiro entender como a hierarquia de exceções funciona. Veja a Figura 1.

Figura 1. Modelo de exceções checked e unchecked

Na cor azul temos os casos de exceções checadas, onde o uso de qualquer método em qualquer lugar da API Java que tenha estas exceções mapeadas será forçado a tratar/capturar a mesma. São exceções presentes em operações que representam risco à aplicação, como as exceções de acesso a arquivos ou dispositivos de IO (Entrada/Saída) - IOException.java, acesso à base de dados - SQLException.java, ou exceções de classes não encontradas - ClassNotFoundException.java. Essas exceções herdam diretamente de java.lang.Exception, classe pai de todas as exceções no Java.

Já as exceções do tipo unchecked tem hierarquia direta com RuntimeException (que por sua vez herda de Exception). Isto significa que o que qualifica uma exceção como unchecked é o fato de ela herdar diretamente desta classe e não de Exception como nas checked.

Provavelmente você já tenha sido obrigado a implementar várias vezes exceções do tipo SQLException quando estava trabalhando com banco de dados e sequer notou que você era de fato forçado a isso. Ao mesmo tempo, é mais que certo que já tenha se deparado com dúzias de exceções do tipo NullPointerException, entretanto, apenas no console de erros da sua IDE enquanto a aplicação estava sendo depurada ou executada. Neste caso em específico, não tem como saber quando uma NullPointerException vai acontecer, uma vez que só temos este cenário quando os valores estão sendo manipulados em tempo de execução, ou seja, em runtime.

Resumindo, podemos listar as diferenças em uma lista de pontos a considerar:

1 - Checked Exceptions

  • Representam condições inválidas em áreas fora do controle imediato do programa (problemas de entradas do usuário inválidas, banco de dados, falhas de rede, arquivos ausentes);
  • São subclasses de Exception;
  • Um método é obrigado a estabelecer uma política para todas as exceções verificadas lançadas por sua implementação (ou passar a exceção verificada mais acima na pilha, ou manipulá-lo de alguma forma).

2 - Unchecked Exceptions:

  • Representam defeitos no programa (bugs) - muitas vezes argumentos inválidos passados ​​para um método não privado. Na obra “A Linguagem de Programação Java”, por Gosling, Arnold, e Holmes, temos: "exceções de tempo de execução unchecked representam condições que, em geral, refletem erros na lógica do seu programa e não pode ser razoavelmente recuperados em tempo de execução.";
  • São subclasses de RuntimeException, e geralmente são implementadas usando IllegalArgumentException, NullPointerException, ou IllegalStateException;
  • Um método não é obrigado a estabelecer uma política para as exceções não verificadas lançadas por sua execução (e quase sempre não fazêm).

Para exemplificar uma unchecked não há classe melhor do que uma utilitária. As classes utilitárias estão repletas exceções não checadas dentro da API do Java. Veja na Listagem 2 alguns métodos que exemplificam isso, com base no resumo feito acima.

Listagem 2. Exemplo de classe utilitária com exceções unchecked.

public final class Utilitarios {  
  /**
   Se <code>texto</code> não satisfazer {@link Util#textoTemConteudo}, 
   uma exceção do tipo
   <code>IllegalArgumentException</code> será lançada.
  
   <P>Os textos usados em um aplicativo só tem sentido 
    se tiverem conteúdo visível.
  */
  public static void checarParaConteudo(String texto){
    if(!Util.textoTemConteudo(texto)){
      throw new IllegalArgumentException
        ("Texto não tem conteúdo visível!");
    }
  }
 
  /**
   Se {@link Util#estaNoIntervalo} retornar <code>false</code>, 
    uma exceção do tipo
   <code>IllegalArgumentException</code> será lançada. 
  
   @param menor é menor ou igual a <code>maior</code>.
  */
  public static void checarParaIntervalo
   (int numero, int menor, int maior) {
    if (!Util.estaNoIntervalo(numero, menor, maior)) {
      throw new IllegalArgumentException(numero + " não 
       está no intervalo " + menor + ".." + maior);
    }
  }
 
  /**
   Se <tt>numero</tt> for menor que <tt>1</tt>, 
    então uma exceção do tipo 
   <tt>IllegalArgumentException</tt> será lançada. 
  */
  public static void checarParaMais(int numero) {
    if (numero < 1) {
      throw new IllegalArgumentException(numero + " é menor que 1.");
    }
  }
 
  /**
   Se {@link Util#marcar} retornar <tt>false</tt>, 
   então uma exceção do tipo 
   <code>IllegalArgumentException</code> será lançada.
  */
  public static void checkForMatch(Pattern padrao, String texto){
    if (!Util.marcar(padrao, texto)) {
      throw new IllegalArgumentException(
        "Texto " + Util.quote(texto) + " não casa com '" 
        + padrao.pattern() + "'"
      );
    }
  }
  
  /**
   Se <code>objeto</code> for null, 
   então uma exceção do tipo 
   <code>NullPointerException</code> será lançada. 
  */
  public static void checarParaNull(Object objeto) {
    if (objeto == null) {
      throw new NullPointerException();
    }
   }                                       
}

Onde usá-las?

Essa dúvida assola a mente de muitos programadores, principalmente quando os mesmos são novatos ou tem pouco tempo de experiência na linguagem.

Determinada vez, em um grande fórum de tecnologia, uma questão bem aprofundada foi levantada sobre o assunto com questões explicando problemas e dúvidas reais do usuário. Este mesmo usuário iniciou seu discurso com uma frase do livre “Effective Java” que vale a pena ler:

“Use exceções verificadas para recuperar as condições e exceções de tempo de execução para erros de programação (item 58 em 2 ª edição)”.

Veja abaixo as cinco questões levantadas e suas respectivas respostas:


  1. O código abaixo pode ser considerado uma checked exception?

    try {
        String entrada = //read in user input
        Long id = Long.parseLong(entrada);
    } catch(NumberFormatException e) {
        id = 0; // regrida para id = 0
    }

  2. RunTimeException é uma unchecked exception?

    try {
        File file = new File("path");
        FileInputStream fis = new FileInputStream(file);   
    } catch(FileNotFoundException e) {
    ...

  3. O que eu deveria fazer aqui?

    
       // Eu deveria lancer uma "throw new FileNotFoundException
         ("Arquivo não encontrado");"?
       // Eu deveria logar?
       //Ou simplesmente usar um System.exit(0);?
    }

  4. O código abaixo deveria ser uma checked exception? Posso recuperar a situação assim?

    try{
        String filePath = …
        File file = new File(filePath);
        FileInputStream fis = new FileInputStream(file);   
    }catch(FileNotFoundException e) {
        // Por favor, solicitar ao usuário uma mensagem de erro
        // De alguma forma, pedir ao usuário para voltar a entrar no 
       // caminho do arquivo.
    }

  5. Por que se faz isso?

    public void someMethod throws Exception{
    }

Veja as respostas dadas:

  1. Não. NumberFormatException é unchecked (= é subclasse de RuntimeException).
  2. Sim, exatamente.
  3. Depende de onde este código é e o que você quer que aconteça. Se for na camada UI - pegá-lo e mostrar um aviso, se é na camada de serviço - não pegá-lo em tudo - deixá-lo fluir. Só não engula a exceção. Se ocorrer uma exceção na maioria dos casos, você deve escolher um destes:

    a. registrá-la e voltar;

    b. relançá-la (declará-la para ser jogado através do método);

    c. construir uma nova exceção, passando a atual no construtor.

  4. Poderia ter sido. Mas nada impede você de pegar a exceção não verificada.
  5. Na maioria das vezes - porque as pessoas são preguiçosas para considerar o que pegar e o que relançar. Jogando Exceção é uma prática ruim e deve ser evitado.

Links

http://stackoverflow.com/

http://www.javapractices.com/