Java com café: Sobrescrita e Exceções

Sobrescrita e Exceções

Introdução

Hoje vou escrever sobre um assunto que está presente no objetivo 1.5 da SCJP. O objetivo determina que o candidato saiba identificar se a sobrescrição de um método está correta. Eu já falei sobre sobrescrita num outro post, mas fiquei devendo uma explicação melhor sobre exceções.


Exceções Verificadas (checked)

As exceções verificadas são exceções que devem ser tratadas por você no seu código. O compilador irá forçá-lo a lidar adequadamente com essas exceções. Você pode capturar essas exceções ou declará-las na cláusula throws do método.
Geralmente, as exceções verificadas ocorrem devido a condições fora de seu controle, como IOException ou uma falha na conexão de rede. Mas sua aplicação deve prever essas condições e tratá-las corretamente para que não venha travar. É por isso que você é forçado pelo compilador a lidar com essas exceções. Se a conexão de rede é interrompida durante uma transferência de arquivo, o aplicativo deve lidar com isso e permitir que o usuário tente novamente.


Exceções Não verificadas (unchecked)

Por outro lado, exceções não-verificadas ocorrem devido a erros de codificação por parte do programador. Como uma exceção NullPointerException que pode ser evitada através de uma verificação para saber se a referência é nula.
Existem também algumas exceções unchecked que podem ocorrer que não são culpa do programador. Estas são as exceções que são subclasses da classe java.lang.Error. Quando essas exceções ocorrem, não há muito o que fazer, então você não é obrigado a lidar com elas já que não pode prever quando irão ocorrer. Como um OutOfMemoryError pode ocorrer a qualquer momento que um objeto é instanciado, você não pode colocar cada instanciação de objeto em blocos try-catch.

Exemplos

Vamos criar um exemplo para ver como o compilador nos força a tratar exceções verificadas.
class ExcecaoNaoTratada {
public static void main(String[] args) {
 throw new Exception();
}
}

O código acima não vai compilar porque não estamos tratando a exceção verificada. Para tornar o código compilável podemos colocar a instrução num bloco try-catch afim de capturá-la ou declarar a exceção numa cláusula throws do método main que irá lançá-la.

No caso de exceções não verificadas, se o método lançar uma exceção não verificada, o compilador não vai reclamar se ela não for tratada. Então o seguinte código compila normalmente:
class ExcecaoNaoTratada {
public static void main(String[] args) {
 throw new RuntimeException();
}
}


Hierarquia de classes

A classe java.lang.Throwable é uma exceção verificada. A API Java define duas subclasses de Throwable. Estes são java.lang.Exception e java.lang.Error. Error é uma classe de exceção não verificada, enquanto Exception é uma exceção verificada. A classe Exception em si tem a subclasse java.lang.RuntimeException (não verificada). As exceções não verificadas como NullPointerException, ClassCastException etc, são todas as sub-classes de RuntimeException. Todas as subclasses de RuntimeException e Error são exceções não verificadas. Outras classes são exceções verificadas.


Se você criar sua própria classe de exceção, se ela é verificada ou não, depende de qual classe Exception ela é subclasse. Se a sua classe estende uma classe de exceção verificada, então sua classe será uma exceção verificada. E se a sua classe estende uma classe de exceção não verificada, então a sua classe será uma exceção não verificada.

Existe uma regra com exceções verificadas que às vezes confunde as pessoas. Se no seu bloco catch você está capturando uma exceção verificada, então deve haver a possibilidade de sua ocorrência no bloco try. Assim, o código a seguir não vai compilar:
try {
System.out.println("...");
} catch(java.io.IOException ioe) {
}

O compilador irá informar que IOException nunca é lançada no bloco try, então você não pode capturá-la. Esta regra não se aplica às exceções não verificadas. Se você está capturando uma exceção não verificada, que não é lançada no bloco try, o compilador não vai reclamar. Agora fica um pouco confuso com as classes Exception e Throwable. Ambas são classes de exceções verificadas, mas você pode capturá-las sem qualquer exceção sendo lançada no bloco try. Então, esse código será compilado sem erros:
try {
System.out.println("...");
} catch(Exception ex) {
}

As regras são diferentes para Throwable e Exception e outras classes de exceção verificada. Isto porque tanto Exception e Throwable tem exceções não verificadas como subclasses. Assim, o compilador permite capturá-los pensando que você pode estar tentando pegar uma exceção não verificada. Lembre-se que o compilador não está preocupado com as exceções não verificadas. RuntimeException é uma subclasse de Exception e Error é uma subclasse de Throwable, e ambos RuntimeException e Error são classes de exceção não verificada. Então, quando escrever o código:
try {
System.out.println ("...");
} Catch (Exception ex) {
}

O compilador não vai reclamar porque Exception também pode capturar exceções não verificadas (RuntimeException e suas subclasses). Da mesma forma o código a seguir é permitido porque Throwable pode capturar exceções não verificadas (RuntimeException e Error e suas subclasses).
try {
System.out.println ("...");
} Catch (Throwable ex) {
}


Regras

Antes de continuar quero adiantar que os tópicos anteriores foram traduzidos deste site. Na verdade isso fugiu um pouco ao objetivo do exame mas serve como um esclarecimento sobre exceções. Agora vejamos ao que realmente importa no exame:
  1. O método novo não pode lançar exceções verificadas novas ou mais abrangentes;
  2. O método novo pode lançar exceções verificadas mais restritivas ou menos abrangentes;
  3. O método novo pode lançar qualquer exceção não verificada;
A maioria dos programadores Java utilizam alguma IDE para desenvolver. É muito mais produtivo. As vezes utilizamos Notepad só para fazer alguns testes, mas normalmente alguém em boas condições mentais não programaria no bloco de notas. As IDE´s quase sempre sabem quando estamos fazendo algo errado. Aí então damos um clique e está resolvido. As regras acima são exemplos de coisas que podemos deixar por conta da IDE, mas no exame não! Para o exame você deve ter um compilador rodando em seu cérebro. A cada código analisado, você deverá saber se está certo ou não. Então vamos treinar um pouco pra isso.
A primeira regra diz que os métodos novos não podem lançar excessões novas ou mais abrangentes (menos restritivas). Abaixo temos um código que será usado como exemplo, nele temos uma sobrescrição simples:
public class Excecoes extends Exemplo{

void A (){
// Sobrescreveu..
}

}

class Exemplo{

void A (){

}

}

A primeira coisa é que não se pode fazer isso:
void A () throws Exception {
// Sobrescreveu..
}

O método novo tentou lançar uma exceção verificada nova. O código não compila. Agora analise este caso:
import java.io.IOException;

public class Excecoes extends Exemplo{

void A () throws Exception { //Tentou lançar uma exeção mais abrangente...
// Sobrescreveu..
}

}

class Exemplo{

void A () throws IOException{

}

}

O código acima também não compila. O método novo tentou lançar uma exceção mais abrangente. Com isso vimos como funciona a primeira regra. Vamos agora pra segunda:
import java.io.IOException;

public class Excecoes extends Exemplo{

void A () throws IOException { //Excesão menos abrangente e mais restritiva
// Sobrescreveu..
}

}

class Exemplo{

void A () throws Exception{

}

}

O código acima compila. O novo método lançou uma exceção que é subclasse de Exception, por isso ela é menos abrangente. O método sobrescrito se arrisca a lançar qualquer tipo de Exception, mas o método novo não precisa declarar nada que nunca lançará.
E a terceira e última regra diz que o método novo pode lançar qualquer exceção não verificada. Isso significa que o seguinte código é válido:
public class Excecoes extends Exemplo{

void A () throws RuntimeException {
// Sobrescreveu..
}

}

class Exemplo{

void A (){

}

}

Outro detalhe importante também é que o método novo pode resolver não lançar nenhuma exceção. Portanto, se ver um método sendo sobrescrito e não lançar nenhuma exceção (mesmo que o método da superclasse o faça) saiba que isto é válido.

Conclusão

Espero ter deixado mais claro este assunto que é muito importante não só pra quem vai tirar uma certificação mas também pra quem está programando no dia a dia e sabe que o negócio não é só ficar lançando exceção ou capturando sem nenhuma codificação no bloco Catch.

Até a próxima!

6 comentários:

  1. Foi muito útil para mim, pois vou fazer a prova quarta, e me deparei com um método que sobrescrevia e não lançava exception... e o seu post exclareceu que isso é válido!

    Valeu.

    ResponderExcluir
  2. Também achei bem esclarecedor. Parabéns!

    ResponderExcluir