MindView Inc.

Pensando em Java, 3ª ed. Revisão 4.0


[ Viewing Hints ] [ Book Home Page ] [ Free Newsletter ]
[ Seminars ] [ Seminars on CD ROM ] [ Consulting ]

Anterior Próximo Página Inicial Índice Conteúdo

9: Tratando Erros
com Exceções



A filosofia básica do Java é “código mal formado não deveria se executado.”



O instante ideal para capturar um erro é durante o tempo de compilação, antes mesmo de você tentar executar o programa. Porém, nem todos os erros podem ser detectados durante a compilação. O resto dos problemas deve ser tratado durante o tempo de execução, através de alguma formalidade que permita ao originador do erro passar informação apropriada para o receptor que saberá como tratar a dificuldade apropriadamente.Comentários (em inglês)



C e outras linguagens mais antigas freqüentemente possuíam múltiplos esquemas de tratamento de erros, e estes eram geralmente estabelecidos por convenção e não como parte da linguagem de programação. Tipicamente, você retornava um valor especial ou dava um determinado valor a um flag, e era esperado que o receptor verificasse o valor ou o flag e determinasse se algo tinha falhado. Porém, com o passar dos anos, foi descoberto que os programadores que usavam uma biblioteca tendia a pensar sobre si mesmos como invencíveis - como se, "Sim, erros podem acontecer com os outros, mas não no meu código". Assim não tão surpreendentemente, eles não verificam as condições de erros (e algumas vezes as condições de erro eram muito tolas para serem verificadas [40]). Se você fosse cuidadoso o suficiente para verificar por uma condição de rro cada vez que você chama um método, seu código poderia e tornar em um ilegível pesadelo. Como programadores ainda podiam gerar sistemas com estas linguagens, eles eram resistentes em admitir a verdade: Que esta alternativa de tratamento de erros era a principal limitação para criar programas grandes, robustos e de fácil manutenção.Comentários (em inglês)



A solução é tirar a natureza casual do tratamento de erros e obrigar a formalidade. Isto realmente é longa história, como as implementações de tratamento de erros vêm desde os sistemas operacionais dos anos 60, e até mesmo do "on error goto"do BASIC. Embora o tratamento de erros em C++ foi baseado no do Ada, e Java é baseado principalmente em C++ (embora pareça mais àquele no Object Pascal). Comentários (em inglês)



A palavra "exception" é usada no sentido de "Eu abro uma exceção para aquilo". No ponto onde o problema ocorre, você pode não saber o que fazer com ele, mas você sabe que você não pode continuar normalmente; você precisa parar e alguém, em algum lugar, precisa saber o que fazer. Mas você não tem informação suficiente no contexto atual para consertar o problema. Assim, você entrega o problema para um contexto superior onde alguém está qualificado para tomar a decisão (semelhante a uma hierarquia de decisões).Comentários (em inglês)



Outro benefício significativo das Exceções é que elas eliminam o código de tratamento de erro. Em lugar de verificar por um erro em particular e lidá-lo em múltiplos lugares no seu programa, não será mais necessário verificar após cada chamada método (já que é garantido que alguém a capturará). E você só necessita tratar o problema em um único lugar, o assim chamado tratamento de erros. Isto economiza código, e separa o código que descreve o que você quer fazer do código que é executado quando as coisas estão incorretas. De maneira geral, ler, escrever e "debugar" código se torna mais claro com exceções do que utilizando o antigo modo de tratamento de erros.Comentários (em inglês)



Como o tratamento de exceções é o único meio oficial que o Java reporta erros, e isto é forçado pelo compilador Java, há apenas alguns exemplos que podem ser escritos neste livro sem aprender sobre tratamento de erros. Este capítulo apresentará o código que você precisa escrever para tratar adequadamente as exceções, e a método para que você possa criar suas próprias exceções se um dos seus métodos encontrar um problema. Comentários (em inglês)

Exceções Básicas



Uma condição exepcional é um problema que impede o prosseguimento de um método ou escopo em que você está. É importante diferenciar uma condição excepcional de um problema normal, sobre o qual você tem informação suficiente no contexto atual para de algum modo lidar com a dificuldade. Dentro de uma condição excepcional, você não pode continuar processando porque você não tem a informação necessária para lidar com o problema no no contexto atual. Tudo que você pode fazer é sair do contexto atual e delegar o problema para um contexto superior. Isto é o que acontece quando você lança uma exceção.Comentários (em inglês)



Divisão é um exemplo simples. Se você vai dividir por zero, vale apenas verificar esta condição. Porém o qual o significado de dividir por zero? Talvez você saiba, no contexto do problema que você está tentando resolver em um método particular, como lidar com um denominador zero. Porém se for um valor inesperado, você pode não lidar com ele e deve lançar uma exception em lugar de continuar com o fluxo do execução.Comentários (em inglês)



Quando você lança uma exceção, várias coisas acontecem. Primeiro, o objeto exception é criado da mesma maneira que qualquer outro objeto Java é criado: no heap, com new. Então o fluxo de atual de execução (aquele que você não podia continuar) é interrompido e a referencia para o objeto da exceção é lançado a partir do contexto atual. Neste pondo o mecanismo de tratamento de erros assume e começa procurar um lugar apropriado para continuar o programa. Este lugar apropriado é o tratador de exceções, cujo trabalho é recuperar apartir de um problema possa tentar outra direção ou apenas continuar.Comentários (em inglês)



Como um simples exemplo sobre lançar uma exceção considere uma referência a objeto chamada t. É possível que você possa ter passado uma referência que não tenha sido inicializada, logo você pode querer checar antes de tentar chamar um método usando esta referência a objeto. Você pode mandar informação sobre o erro para um contexto maior por criar um objeto representando sua informação e "lançando"-o para fora do contexto atual. Isto é chamado de lançar uma exceção. É mais ou menos assim:



if(t == null)
  throw new NullPointerException();




Isto lança uma exceção, que permite —no contexto atual— abdicar da responsabilidade para pensar sobre este problema depois. É apenas tratado em algum outro lugar. Precisamente onde será mostrado a seguir. Comentários (em inglês)

Parâmetros de uma Exceção



Como qualquer objeto em Java, você sempre cria exceções no heap usando new, que aloca espaço e chama um construtor. Há dois construtores em exceções padrão: O primeiro é o construtor padrão, e o segundo toma como argumento um string que você pode colocar informação pertinente na exceção:



  throw new NullPointerException("t = null");




Este string pode ser posteriormente extraído usando vários métodos como você verá.Comentários (em inglês)



A palavra chave throw faz com que um número coisas relativamente mágicas aconteça. Tipicamente, você usará new para criar um objeto que representa a condição de erro. Você fornece a referência resultante para throw. O objeto é, como resultado, retornado do método, embora o tipo do objeto não seja normalmente aquele que o método foi projetado para retornar. Um modo simplista de pensar sobre tratamento de exceções é como um tipo diferente de mecanismo de retorno, embora você possa se confundir se você levar esta analogia muito longe. Você pode também sair de escopos comuns lançando uma exceção. Porém um valor é retornado e o método ou escopo termina.Comentários (em inglês)



Qualquer semelhança com um retorno comum de um método termina aqui, porque onde você retorna é completamente diferente de onde o retorna uma chamada de método normal. (Você termina em um tratador de exceção apropriado que pode ser muitos níveis distante pilha de chamadas de onde a exceção foi lançada.) Comentários (em inglês)



Adicionalmente, você pode lançar qualquer objetoThrowable (a classe raiz de exceção) que você quiser. Tipicamente você lançará uma classe diferente de exception para tipo diferente de erro. A informação sobre o erro é representada tanto internamente do objeto de exceção e implicitamente no nome da classe de exceção. (Freqüentemente, a única informação é o tipo de exceção, e nada significativo é armazenado dentro do objeto de exceção.) Comentários (em inglês)

Catching an exception



Se você lança uma exceção, assume-se que a exceção será “capturada” e tratada. Uma das vantagens do tratamento de exceção é que permite você se concentrar no problema que você está tentando resolver em um único lugar, e lidar com os erros daquele código em outro lugar. Comentários (em inglês)



Para ver como um exceção é capturada, você deve entender primeiro o conceito da região protegida. Esta é o trecho de código que pode produzir exceção e é seguida do código para tratar estas exceções. Comentários (em inglês)

O bloco try



Se você está dentro de um método e você lança uma exceção (ou outro método que você chamou dentro deste médoto lança uma exceção), aquele método entrará no processo lançamento. Se você não quer que um throw o retire do método você pode montar um bloco especial de código dentro deste método para capturar a exceção. Este é o chamado de bloco try (tentativa) porque se você “tenta” várias chamadas de métodos aqui. O bloco try é um escopo comum precedido pela palavra chave try: Comentários (em inglês)



try {
  // Código que pode gerar exceção
}




Se você estivesse verificando erros cuidadosamente em uma linguagem de programação que não suportasse tratamento de exceções, você teria que envolver cada chamada de método com código de teste de configuração e de erros, mesmo que você chamasse o mesmo método várias vezes. Com tratamento de exceções, você coloca tudo em um bloco try e captura as exceções em um único lugar. Isto significa que seu código é mais fácil de escrever e ler porque o objetivo do código não é confundido com a verificação de erros. Comentários (em inglês)

Tratadores de exceção



É claro, a exceção lançada deve acabar em algum lugar. Este “lugar” é o tratador de exceções,, e há um para cada tipo de exceção que você quiser capturar. Tratadores de exceção vêm logo após o bloco try e são demarcados pela palavra chave catch:



try {
  // Código que pode gerar exceções
} catch(Type1 id1) {
  // Tratamento de exceção do tipo Type1
} catch(Type2 id2) {
  // Tratamento de exceção do tipo Type2
} catch(Type3 id3) {
  // Tratamento de exceção do tipo Type3
}

// etc...




Cada sentença de catch (tratamento de exceção) é como um pequeno método que leva um único argumento de um tipo particular. O identificador (id1, id2, e assim por diante) pode ser usado dentro do tratador, apenas como um argumento de método. Algumas vezes você nunca usa o identificador porque o tipo da exceção fornece informação suficiente de como lidar com a exceção, porém o identificar ainda deve de permancer ali. Comentários (em inglês)



Os tratadores de exceções devem aparecer diretamente após o bloco de try. Se uma exceção é lançada, o mecanismo de tratamento de exceção deve buscar pelo primeiro tratador com um argumentoque combine com a exceção. Então ele entra na catch, e a exceção é considerada tratada. A busca por tratadores termina uma vez que a clausula tach termina. Apenas a clausula catch que combina; não é como um operador switch o qual você deve interromper com um break depois de cadacase para evitar que os restantes sejam executados. Comentários (em inglês)



Observe que dentro de um bloco try, um número qualquer de camadas de método diferentes podem geradar a mesma exceção, mas você precisa de apenas de um tratador. Comentários (em inglês)

Término versus Reinício



Há dois modelos básicos na teoria de tratamento de exceções. No término (que é suportado pelo Java e C++), você assume que o erro é tão crítico que não há modo de voltar a onde a exceção ocorreu. Quem quer que tenha lançado a exceção decidiu que não havia modo de salvar a situação, e não quis retornar. Comentários (em inglês)



A alternativa é chamada reinício. Significa que é esperado que o tratador de exceção faça alguma coisa para corrigir a situação, e então o método que falhou é tentado de novo, presumindo sucesso na segunda vez. Se você quer reinício, isto significa que você ainda tem esperança de continuar a execução depois da exceção ser tratada. Neste caso, sua exceção é mais parecida com uma chamada de método - que é como você deve montar situações no Java nas quais você quer comportamento tipo reinício. (Isto é, não lance uma exceção; chame um método que conserte o problema.) Alternativamente, coloque seu bloco de try dentro de um loop while que mantenha reentrando no bloco de try até o resultado ser satisfatório. Comentários (em inglês)



Historicamente, programadores utilizando sistemas operacionais que suportavam tratamento de exceções presumidas freqüentemente terminavam usando código tipo de término e pulando o reinício. Assim embora o reinício soasse atrativo a primeira vista, não era assim muito útil na prática. O motivo principal era acoplamento que resulta; seu tratador deve muitas vezes estar a par de onde a exceção é lançada, e conter código não genérico especifico para o local que lançou. Isto faz o código difícil de escrever e manter, especialmente para grandes sistema onde a exceção podia ser gerada a partir de muitos pontos. Comentários (em inglês)

Criando suas próprias exceções



Você não está preso a usar as exceções existentes em Java. A hierarquia de exceções do JDK não pode prever todos os erros que você pode querer reportar, portando você pode criar suas próprias para denotar um problema especial que sua biblioteca pode encontrar.Comentários (em inglês)



Para criar suas próprias classes de exceção, você deve herdar a partir de uma classe de exceção existente, preferivelmente uma que seja próxima do significado da sua nova exceção (embora isto freqüentemente não seja possível). O modo mais trivial de criar um novo tipo de exceção é apenas deixar o compilador criar o construtor padrão para você, assim ela não requer quase nenhum código.



//: c09:SimpleExceptionDemo.java
// Herdando suas próprias exceções
import com.bruceeckel.simpletest.*;

class SimpleException extends Exception {}

public class SimpleExceptionDemo {
  private static Test monitor = new Test();
  public void f() throws SimpleException {
    System.out.println("Lançando SimpleException a partir de f()");
    throw new SimpleException();
  }
  public static void main(String[] args) {
    SimpleExceptionDemo sed = new SimpleExceptionDemo();
    try {
      sed.f();
    } catch(SimpleException e) {
      System.err.println(Capturada!");
    }
    monitor.expect(new String[] {
      "Lançando SimpleException a partir de f()",
      "Capturada!"
    });
  }
} ///:~




O compilador cria um construtor padrão, que automaticamente (e invisivelmente) chama o construtor padrão da classe base. É claro, neste caso você não terá o construtor SimpleException(String) , mas na prática não é muito usado. Como você verá, a coisa mais importante sobre uma exceção é o nome da classe, assim a maior parte do tempo uma exceção como a mostrada aqui é satisfatório. Comentários (em inglês)



Aqui, o resultado é impresso no fluxo padrão de erro do terminal através da encrita no System.err. Este é geralmente o melhor lugar para enviar a informação de erro do que o System.out, que pode ser redirecionado. Se você envia a saída para System.err, ela não será redirecionada junto com o System.out portanto é mais provável que o usuário a perceba.Comentários (em inglês)



Você também pode criar uma classe de exceção que tenha um construtor com um parâmetroString:



//: c09:FullConstructors.java
import com.bruceeckel.simpletest.*;

class MyException extends Exception {
  public MyException() {}
  public MyException(String msg) { super(msg); }
}

public class FullConstructors {
  private static Test monitor = new Test();
  public static void f() throws MyException {
    System.out.println("Lançando MyException a partir de f()");
    throw new MyException();
  }
  public static void g() throws MyException {
    System.out.println("Lançando MyException a partir de g()");
    throw new MyException("Originada em g()");
  }
  public static void main(String[] args) {
    try {
      f();
    } catch(MyException e) {
      e.printStackTrace();
    }
    try {
      g();
    } catch(MyException e) {
      e.printStackTrace();
    }
    monitor.expect(new String[] {
      "Lançando MyException a partir de f()",
      "MyException",
      "%% \\tat FullConstructors.f\\\\(.*\\\\)",
      "%% \\tat FullConstructors.main\\\\(.*\\\\)",
      "Lançando MyException a partir de g()",
      "MyException: Originada em g()",
      "%% \\tat FullConstructors.g\\\\(.*\\\\)",
      "%% \\tat FullConstructors.main\\\\(.*\\\\)"
    });
  }
} ///:~




O código adicionado é pequeno: dois construtores que definem o modo que MyException é criada. No segundo construtor, o construtor da classe base com um parâmetro String é explicitamente invocado através da palavra chave super. Comentários (em inglês)



Nos tratadores, um dos métodos de Throwable (a partir dos quais Exception é herdada) é chamado: printStackTrace( ). Isto produz informação sobre a seqüência de métodos que foram chamados para obter o ponto onde a exceção aconteceu. Por definição, a informação vai para o fluxo de erro padrão, porém uma versão redefinida permite a você enviar o resultado para qualquer outro fluxo também.Comentários (em inglês)



O processo de criar suas próprias exceções pode ser levado além. Você pode adicionar construtores e membros:



//: c09:ExtraFeatures.java
// Embelezamento adicional da classe de exceção
import com.bruceeckel.simpletest.*;

class MyException2 extends Exception {
  private int x;
  public MyException2() {}
  public MyException2(String msg) { super(msg); }
  public MyException2(String msg, int x) {
    super(msg);
    this.x = x;
  }
  public int val() { return x; }
  public String getMessage() {
    return "Menssagem de Detalhe: "+ x + " "+ super.getMessage();
  }
}

public class ExtraFeatures {
  private static Test monitor = new Test();
  public static void f() throws MyException2 {
    System.out.println("Lançando MyException2 a partir de f()");
    throw new MyException2();
  }
  public static void g() throws MyException2 {
    System.out.println("Lançando MyException2 a partir de g()");
    throw new MyException2("Originada em g()");
  }
  public static void h() throws MyException2 {
    System.out.println("Lançando MyException2 a partir de h()");
    throw new MyException2("Originada em h()", 47);
  }
  public static void main(String[] args) {
    try {
      f();
    } catch(MyException2 e) {
      e.printStackTrace();
    }
    try {
      g();
    } catch(MyException2 e) {
      e.printStackTrace();
    }
    try {
      h();
    } catch(MyException2 e) {
      e.printStackTrace();
      System.err.println("e.val() = " + e.val());
    }
    monitor.expect(new String[] {
      "Lançando MyException2 a partir de f()",
      "MyException2: Mensagem de Detalhe: 0 null",
      "%% \\tat ExtraFeatures.f\\\\(.*\\\\)",
      "%% \\tat ExtraFeatures.main\\\\(.*\\\\)",
      "Lançando MyException2 a partir de g()",
      "MyException2: Mensagem de Detalhe: 0 Originada em g()",
      "%% \\tat ExtraFeatures.g\\\\(.*\\\\)",
      "%% \\tat ExtraFeatures.main\\\\(.*\\\\)",
      "Lançando MyException2 a partir de h()",
      "MyException2: Menssagem de Detalhe: 47 Originada em h()",
      "%% \\tat ExtraFeatures.h\\\\(.*\\\\)",
      "%% \\tat ExtraFeatures.main\\\\(.*\\\\)",
      "e.val() = 47"
    });
  }
} ///:~




O campo i foi adicionado, junto com um método que lê o valor e um construtor adicional que lhe atribui valor. Adicionalmente, Throwable.getMessage( ) foi redefinido para produzir uma mensagem de detalhe mais interessante. getMessage( ) é algo como toString( ) para classes de exceção. Comentários (em inglês)



Já que uma exceção é apenas outro tipo de objeto, você pode continuar este processo de embelezar o poder de suas classes de exceção. Tendo em mente, no entando, que toda esta maquiagem pode ser perdida pelos programadores de aplicações cliente que estiverem usando seus pacotes, visto que eles podem simplesmente esperar que a exceção seja lançada e nada mais. (Este é o modo que a maioria das exceções da biblioteca Java são usadas.) Comentários (em inglês)

A especificação de exceção



Em Java, você é encorajado a informado ao programador de aplicações cliente, que chama seu método, das exceções que podem ser lançadas a partir do seu método. Isto é civilizado, porque o quem chama pode saber exatamente que código escrever para capturar todas as exceções em potencial. É claro, se o código fonte estiver disponível, o programador de aplicações cliente pode caçar e procurar pelas sentenças throw , porém freqüentemente a biblioteca não vem com fontes. Para evitar que isto seja um problema, Java fornece sintaxe (e força você a usá-la) para permitir a você dizer polidamente ao programador de aplicações cliente quais exceções este método lança, assim ele pode tratá-las. Isto é a especificação de exceção e é parte da declaração do método, aparecendo depois da lista de parâmetros. Comentários (em inglês)



A especificação de exceção usa uma palavra chave adicional, throws, seguida por uma lista de todos os tipos potenciais de exceção. Assim a definição do seu método pode parecer como esta:



void f() throws MuitoGrande, MuitoPequena, DivZero { //... 




Se você disser



void f() { // ...




isto significa que nenhuma exceção é lançada a partir do método (exceto as exceções herdadas de RuntimeException, as quais podem ser lançadas de qualquer lugar sem especificação de exceção - isto será descrito depois). Comentários (em inglês)



Você não pode mentir sobre uma especificação de exceção. Se o código dentro de seu método provoca exceções, mas seu método não os trata, o compilador detectará isto e lhe dirá que você deve tratar a exceção ou indicar com uma especificação de exceção que ela deve pode ser lançada a partir de seu método. Através da verificação das especificações de exceção de cima para baixo, o Java garante que um certo nível de exatidão pode ser garantido em tempo de compilação.Comentários (em inglês)



Há um lugar onde você pode mentir: Você pode declarar que lança uma exceção que você realmente não lança. O compilador acredita em sua palavra e força os usuários do seu método a tratá-lo como se realmente lança-se aquela exceção. Isto tem o efeito benéfico de ser marcar o lugar para aquela exceção, assim você pode realmente começar a lançar a exceção depois sem necessitar modificar o código existente. Isto é importante para criar classes base abstratas e interfaces cujas classes derivadas ou implementações podem necessitar lançar exceções.Comentários (em inglês)



Exceções que são controladas e reforçadas em tempo de compilação são chamadas exceções controladas.. Comentários (em inglês)

Capturando qualquer exceção



É possível criar um tratador que capture qualquer tipo de exceção. Você faz isto capturando o tipo de exceção de classe base Exception (há outros tipos de exceções de base, mas Exception é a base que é pertinente para virtualmente todas as atividades de programação):



catch(Exception e) {
  System.err.println("Capturando uma exceção");
}




Isto capturará qualquer exceção, assim se você o usa irá querer colocá-lo no final da sua lista de tratadores para evitar tomar o lugar de qualquer tratador de exceção que possa de outra forma segui-lo.Comentários (em inglês)



Uma vez que a classe Exception é base para todas as classes de exceção que são importantes para o programador, você não obtém informação específica sobre a exceção, porém você pode chamar os métodos que vem de seu tipo base Throwable:



String getMessage( )
String getLocalizedMessage( )
Obtém a mensagem detalhada ou uma mensagem ajustada para este local em particular. Comentários (em inglês)



String toString( )
Retrona uma descrição curta do Throwable, incluindo a mensagem detalhada, se houver uma.Comentários (em inglês)



void printStackTrace( )
void printStackTrace(PrintStream)
void printStackTrace(java.io.PrintWriter)
Imprime o Throwable e sua pilha de chamadas. A pilha de chamadas mostra a seqüência de chamadas de métodos que trouxeram você ao ponto no qual a exceção foi lançada. A primeira versão imprime na saída padrão de erro, a segunda e a terceira imprimem no fluxo se sua escolha (no capítulo 12, você entenderá porque há dois tipos de fluxos). Comentários (em inglês)



Throwable fillInStackTrace( )
Registra informação dentro deste objeto Throwable sobre o estado atual das chamadas. É útil quando uma aplicação está relançando um erro ou uma exceção (isto será detalhado a seguir).Comentários (em inglês)



Adicionalmente, você obtêm alguns outros métodos do tipo base de Throwable o Object (tipo base de todo mundo). O principal que pode chegar a ser útil para exceções é getClass( ), que retorna um objeto representando a classe deste objeto. Você pode por sua vez consultar neste objeto Class pelo seu nome com getName( ). Você pode também fazer coisas mais sofisticadas com objetos Class que não são necessariamente tratamento de exceção. Comentários (em inglês)



Aqui está um exemplo que mostra o uso dos métodos básicos de Exception:



//: c09:ExceptionMethods.java
// Demonstrating the Exception Methods.
import com.bruceeckel.simpletest.*;

public class ExceptionMethods {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    try {
      throw new Exception("Minha Exceção");
    } catch(Exception e) {
      System.err.println("Exceção Capturada");
      System.err.println("getMessage():" + e.getMessage());
      System.err.println("getLocalizedMessage():" +
        e.getLocalizedMessage());
      System.err.println("toString():" + e);
      System.err.println("printStackTrace():");
      e.printStackTrace();
    }
    monitor.expect(new String[] {
      "Exceção Capturada",
      "getMessage(): Minha Exceção",
      "getLocalizedMessage(): Minha Exceção",
      "toString():java.lang.Exception: Minha Exceção",
      "printStackTrace():",
      "java.lang.Exception: Minha Exceção",
      "%% \\tat ExceptionMethods.main\\\\(.*\\\\)"
    });
  }
} ///:~




Você pode ver que os métodos fornecem sucessivamente mais informação - cada um é efetivamente um conjunto superior do anterior. Comentários (em inglês)

Relançando uma exceção



Algumas vezes você vai querer relançar a exceção que você acabou de capturar, particularmente quando você usar Exception para capturar qualquer exceção. Já que você tem uma referencia para a exceção atual, você pode simplesmente relançar aquela referencia.



catch(Exception e) {
  System.err.println("Uma exceção foi lançada");
  throw e;
}




Relançando uma exceção causa sua ida para um controlador de exceção no próximo contexto superior. Uma outra cláusula catch para o mesmo bloco try será ignorada. Acrescentando, tudo sobre a exceção será preservado, assim o controlador no contexto superior que captura um tipo de exceção específica pode extrair todas informações daquele objeto. Comentários (em inglês)



Se você implesmente relança a exceção atual, a informação que você imprime sobre aquela exceção no printStackTrace( ) pertencerá a exceção origem, não na posição onde você relançou-a. Se você quer gravar novas informações na pilha de execução, você pode fazer por chamandofillInStackTrace( ), quer retorna um objeto Throwable que ela cria por estufando a pilha de informação atual dentro do objeto de exceção antigo. Veja como: Comentários (em inglês)



//: c09:Rethrowing.java
// Demonstrating fillInStackTrace()
import com.bruceeckel.simpletest.*;

public class Rethrowing {
  private static Test monitor = new Test();
  public static void f() throws Exception {
    System.out.println("originating the exception in f()");
    throw new Exception("thrown from f()");
  }
  public static void g() throws Throwable {
    try {
      f();
    } catch(Exception e) {
      System.err.println("Inside g(),e.printStackTrace()");
      e.printStackTrace();
      throw e; // 17
      // throw e.fillInStackTrace(); // 18
    }
  }
  public static void
  main(String[] args) throws Throwable {
    try {
      g();
    } catch(Exception e) {
      System.err.println(
        "Caught in main, e.printStackTrace()");
      e.printStackTrace();
    }
    monitor.expect(new String[] {
      "originating the exception in f()",
      "Inside g(),e.printStackTrace()",
      "java.lang.Exception: thrown from f()",
      "%% \tat Rethrowing.f(.*?)",
      "%% \tat Rethrowing.g(.*?)",
      "%% \tat Rethrowing.main(.*?)",
      "Caught in main, e.printStackTrace()",
      "java.lang.Exception: thrown from f()",
      "%% \tat Rethrowing.f(.*?)",
      "%% \tat Rethrowing.g(.*?)",
      "%% \tat Rethrowing.main(.*?)"
    });
  }
} ///:~




Os números de linhas importantes estão comentadas. Com a linha 17 descomentada (como mostrado), a saída está como mostrado, assim a pilha exceção sempre o verdadeiro ponto da origem sem importar quantas vezes induz o relançamentoComentários (em inglês)



Com a linha 17 comentada e a linha 18 descomentada, fillInStackTrace( ) é usado, e o resultado é:



originating the exception in f()
Inside g(),e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.f(Rethrowing.java:9)
        at Rethrowing.g(Rethrowing.java:12)
        at Rethrowing.main(Rethrowing.java:23)
Caught in main, e.printStackTrace()
java.lang.Exception: thrown from f()
        at Rethrowing.g(Rethrowing.java:18)
        at Rethrowing.main(Rethrowing.java:23)




(E mais reclamação adicional do métodoTest.expect( ) .) Por causa dofillInStackTrace( ), linha 18 inicia um novo ponto de origem da exceção. Comentários (em inglês)



A classeThrowable deve aparecer na especificação da exceção para g( ) e main( ) por que fillInStackTrace( ) produz uma referência para um objetoThrowable. Assim Throwable é a classe base de Exception, é possível obter um objeto que é um Throwable mas não um Exception, assim o controle para Exception em main( ) pode falhar. Para assegurar qualquer coisa nessa ordem, o compilador força uma especificação de exceção para Throwable. Por exemplo, a exceção no seguinte programa não é capturada nomain( ): Comentários (em inglês)



//: c09:ThrowOut.java
// {ThrowsException}
public class ThrowOut {
  public static void
  main(String[] args) throws Throwable {
    try {
      throw new Throwable();
    } catch(Exception e) {
      System.err.println("Capturada em main()");
    }
  }
} ///:~




Também é possível relançar uma exceção diferente da que você capturou. Se você fizer isso, haverá um efeito similar a utilização de fillInStackTrace( )—a informação sobre o local da exceção original é perdido, e você fica somente com a informação relativa ao novo throw: Comentários (em inglês)



//: c09:RethrowNew.java
// Rethrow a different object from the one that was caught.
// {ThrowsException}
import com.bruceeckel.simpletest.*;

class OneException extends Exception {
  public OneException(String s) { super(s); }
}

class TwoException extends Exception {
  public TwoException(String s) { super(s); }
}

public class RethrowNew {
  private static Test monitor = new Test();
  public static void f() throws OneException {
    System.out.println("originating the exception in f()");
    throw new OneException("thrown from f()");
  }
  public static void
  main(String[] args) throws TwoException {
    try {
      f();
    } catch(OneException e) {
      System.err.println(
        "Caught in main, e.printStackTrace()");
      e.printStackTrace();
      throw new TwoException("from main()");
    }
    monitor.expect(new String[] {
      "originating the exception in f()",
      "Caught in main, e.printStackTrace()",
      "OneException: thrown from f()",
      "\tat RethrowNew.f(RethrowNew.java:18)",
      "\tat RethrowNew.main(RethrowNew.java:22)",
      "Exception in thread \"main\" " +
      "TwoException: from main()",
      "\tat RethrowNew.main(RethrowNew.java:28)"
    });
  }
} ///:~




A exceção final somente sabe que ela veio do main( ) e não do f( ). Comentários (em inglês)



Você nunca precisa se preocupar com a limpeza da exceção anterior, ou qualquer outra exceção. Todas elas são objetos localizados no heap, criados com new, de forma que o garbage collector limpa todas elas automaticamente. Comentários (em inglês)

Encadeamento de exceções



Often you want to catch one exception and throw another, but still keep the information about the originating exception—this is called exception chaining. Prior to JDK 1.4, programmers had to write their own code to preserve the original exception information, but now all Throwable subclasses may take a cause object in their constructor. The cause is intended to be the originating exception, and by passing it in you maintain the stack trace back to its origin, even though you’re creating and throwing a new exception at this point. Comentários (em inglês)



É interessante notar que somente subclasses Throwable que providenciam o argumento cause no construtor são as três classes fundamentais de exceção Error (usada pela JVM para reportar erros de sistema), Exception, e RuntimeException. Se você quiser canalizar qualquer outro tipo de exceção, você o fará através do método initCause( ) melhor que através do construtor. Comentários (em inglês)



Aqui está um exemplo que permite que você adicione dinamicamente campos ao objeto DynamicFields em tempo de execução:



//: c09:DynamicFields.java
// A Class that dynamically adds fields to itself.
// Demonstrates exception chaining.
// {ThrowsException}
import com.bruceeckel.simpletest.*;

class DynamicFieldsException extends Exception {}

public class DynamicFields {
  private static Test monitor = new Test();
  private Object[][] fields;
  public DynamicFields(int initialSize) {
    fields = new Object[initialSize][2];
    for(int i = 0; i < initialSize; i++)
      fields[i] = new Object[] { null, null };
  }
  public String toString() {
    StringBuffer result = new StringBuffer();
    for(int i = 0; i < fields.length; i++) {
      result.append(fields[i][0]);
      result.append(": ");
      result.append(fields[i][1]);
      result.append("\n");
    }
    return result.toString();
  }
  private int hasField(String id) {
    for(int i = 0; i < fields.length; i++)
      if(id.equals(fields[i][0]))
        return i;
    return -1;
  }
  private int
  getFieldNumber(String id) throws NoSuchFieldException {
    int fieldNum = hasField(id);
    if(fieldNum == -1)
      throw new NoSuchFieldException();
    return fieldNum;
  }
  private int makeField(String id) {
    for(int i = 0; i < fields.length; i++)
      if(fields[i][0] == null) {
        fields[i][0] = id;
        return i;
      }
    // No empty fields. Add one:
    Object[][]tmp = new Object[fields.length + 1][2];
    for(int i = 0; i < fields.length; i++)
      tmp[i] = fields[i];
    for(int i = fields.length; i < tmp.length; i++)
      tmp[i] = new Object[] { null, null };
    fields = tmp;
    // Reursive call with expanded fields:
    return makeField(id);
  }
  public Object
  getField(String id) throws NoSuchFieldException {
    return fields[getFieldNumber(id)][1];
  }
  public Object setField(String id, Object value)
  throws DynamicFieldsException {
    if(value == null) {
      // Most exceptions don't have a "cause" constructor.
      // In these cases you must use initCause(),
      // available in all Throwable subclasses.
      DynamicFieldsException dfe =
        new DynamicFieldsException();
      dfe.initCause(new NullPointerException());
      throw dfe;
    }
    int fieldNumber = hasField(id);
    if(fieldNumber == -1)
      fieldNumber = makeField(id);
    Object result = null;
    try {
      result = getField(id); // Get old value
    } catch(NoSuchFieldException e) {
      // Use constructor that takes "cause":
      throw new RuntimeException(e);
    }
    fields[fieldNumber][1] = value;
    return result;
  }
  public static void main(String[] args) {
    DynamicFields df = new DynamicFields(3);
    System.out.println(df);
    try {
      df.setField("d", "A value for d");
      df.setField("number", new Integer(47));
      df.setField("number2", new Integer(48));
      System.out.println(df);
      df.setField("d", "A new value for d");
      df.setField("number3", new Integer(11));
      System.out.println(df);
      System.out.println(df.getField("d"));
      Object field = df.getField("a3"); // Exception
    } catch(NoSuchFieldException e) {
      throw new RuntimeException(e);
    } catch(DynamicFieldsException e) {
      throw new RuntimeException(e);
    }
    monitor.expect(new String[] {
      "null: null",
      "null: null",
      "null: null",
      "",
      "d: A value for d",
      "number: 47",
      "number2: 48",
      "",
      "d: A new value for d",
      "number: 47",
      "number2: 48",
      "number3: 11",
      "",
      "A value for d",
      "Exception in thread \"main\" " +
      "java.lang.RuntimeException: " +
      "java.lang.NoSuchFieldException",
      "\tat DynamicFields.main(DynamicFields.java:98)",
      "Caused by: java.lang.NoSuchFieldException",
      "\tat DynamicFields.getFieldNumber(" +
      "DynamicFields.java:37)",
      "\tat DynamicFields.getField(DynamicFields.java:58)",
      "\tat DynamicFields.main(DynamicFields.java:96)"
    });
  }
} ///:~




Each DynamicFields object contains an array of Object-Object pairs. The first object is the field identifier (a String), and the second is the field value, which can be any type except an unwrapped primitive. When you create the object, you make an educated guess about how many fields you need. When you call setField( ), it either finds the existing field by that name or creates a new one, and puts in your value. If it runs out of space, it adds new space by creating an array of length one longer and copying the old elements in. If you try to put in a null value, then it throws a DynamicFieldsException by creating one and using initCause( ) to insert a NullPointerException as the cause. Comentários (em inglês)



As a return value, setField( ) also fetches out the old value at that field location using getField( ), which could throw a NoSuchFieldException. If the client programmer calls getField( ), then they are responsible for handling NoSuchFieldException, but if this exception is thrown inside setField( ), it’s a programming error, so the NoSuchFieldException is converted to a RuntimeException using the constructor that takes a cause argument. Comentários (em inglês)

Standard Java exceptions



A classe javaThrowabledescreve qualquer coisa que pode ser lançada como uma exceção. Há dois tipos gerais de objetosThrowable (“types of” = “inherited from”). Error representa erros em tempo de compilação e erros de sistema com os quais você não precisa preocupar-se em capturar (exceto em casos especiais). Exception é o tipo básico que deve ser capturado por quaisquer dos métodos das classes das bibliotecas padrão do Java e pelos seus métodos e incidentes de run-time. Logo, o tipo que habitualmente interessa ao programador Java é usualmente Exception. Comentários (em inglês)



The best way to get an overview of the exceptions is to browse the HTML Java documentation that you can download from java.sun.com. It’s worth doing this once just to get a feel for the various exceptions, but you’ll soon see that there isn’t anything special between one exception and the next except for the name. Also, the number of exceptions in Java keeps expanding; basically, it’s pointless to print them in a book. Any new library you get from a third-party vendor will probably have its own exceptions as well. The important thing to understand is the concept and what you should do with the exceptions. Comentários (em inglês)



The basic idea is that the name of the exception represents the problem that occurred, and the exception name is intended to be relatively self-explanatory. The exceptions are not all defined in java.lang; some are created to support other libraries such as util, net, and io, which you can see from their full class names or what they are inherited from. For example, all I/O exceptions are inherited from java.io.IOException. Comentários (em inglês)

The special case of RuntimeException



The first example in this chapter was



if(t == null)
  throw new NullPointerException();




It can be a bit horrifying to think that you must check for null on every reference that is passed into a method (since you can’t know if the caller has passed you a valid reference). Fortunately, you don’t—this is part of the standard run-time checking that Java performs for you, and if any call is made to a null reference, Java will automatically throw a NullPointerException. So the above bit of code is always superfluous. Comentários (em inglês)



There’s a whole group of exception types that are in this category. They’re always thrown automatically by Java and you don’t need to include them in your exception specifications. Conveniently enough, they’re all grouped together by putting them under a single base class called RuntimeException, which is a perfect example of inheritance; It establishes a family of types that have some characteristics and behaviors in common. Also, you never need to write an exception specification saying that a method might throw a RuntimeException (or any type inherited from RuntimeException), because they are unchecked exceptions. Because they indicate bugs, you don’t usually catch a RuntimeException—it’s dealt with automatically. If you were forced to check for RuntimeExceptions, your code could get too messy. Even though you don’t typically catch RuntimeExceptions, in your own packages you might choose to throw some of the RuntimeExceptions. Comentários (em inglês)



What happens when you don’t catch such exceptions? Since the compiler doesn’t enforce exception specifications for these, it’s quite plausible that a RuntimeException could percolate all the way out to your main( ) method without being caught. To see what happens in this case, try the following example:



//: c09:NeverCaught.java
// Ignoring RuntimeExceptions.
// {ThrowsException}
import com.bruceeckel.simpletest.*;

public class NeverCaught {
  private static Test monitor = new Test();
  static void f() {
    throw new RuntimeException("From f()");
  }
  static void g() {
    f();
  }
  public static void main(String[] args) {
    g();
    monitor.expect(new String[] {
      "Exception in thread \"main\" " +
      "java.lang.RuntimeException: From f()",
      "        at NeverCaught.f(NeverCaught.java:7)",
      "        at NeverCaught.g(NeverCaught.java:10)",
      "        at NeverCaught.main(NeverCaught.java:13)"
    });
  }
} ///:~




You can already see that a RuntimeException (or anything inherited from it) is a special case, since the compiler doesn’t require an exception specification for these types. Comentários (em inglês)



So the answer is: If a RuntimeException gets all the way out to main( ) without being caught, printStackTrace( ) is called for that exception as the program exits. Comentários (em inglês)



Keep in mind that you can only ignore exceptions of type RuntimeException (and subclasses) in your coding, since all other handling is carefully enforced by the compiler. The reasoning is that a RuntimeException represents a programming error:

  1. An error you cannot anticipate. For example, a null reference that is outside of your control. Feedback
  2. An error that you, as a programmer, should have checked for in your code (such as ArrayIndexOutOfBoundsException where you should have paid attention to the size of the array). An exception that happens from point #1 often becomes an issue for point #2. Feedback


You can see what a tremendous benefit it is to have exceptions in this case, since they help in the debugging process. Comentários (em inglês)



It’s interesting to notice that you cannot classify Java exception handling as a single-purpose tool. Yes, it is designed to handle those pesky run-time errors that will occur because of forces outside your code’s control, but it’s also essential for certain types of programming bugs that the compiler cannot detect. Comentários (em inglês)

Performing cleanup
with finally



There’s often some piece of code that you want to execute whether or not an exception is thrown within a try block. This usually pertains to some operation other than memory recovery (since that’s taken care of by the garbage collector). To achieve this effect, you use a finally clause[41] at the end of all the exception handlers. The full picture of an exception handling section is thus:



try {
  // The guarded region: Dangerous activities
  // that might throw A, B, or C 
} catch(A a1) {
  // Handler for situation A
} catch(B b1) {
  // Handler for situation B
} catch(C c1) {
  // Handler for situation C
} finally {
  // Activities that happen every time
}




Para demonstrar que a clausula finally sempre acontece, tente este programa: Comentários (em inglês)



//: c09:FinallyWorks.java
// The finally clause is always executed.
import com.bruceeckel.simpletest.*;

class ThreeException extends Exception {}

public class FinallyWorks {
  private static Test monitor = new Test();
  static int count = 0;
  public static void main(String[] args) {
    while(true) {
      try {
        // Post-increment is zero first time:
        if(count++ == 0)
          throw new ThreeException();
        System.out.println("No exception");
      } catch(ThreeException e) {
        System.err.println("ThreeException");
      } finally {
        System.err.println("In finally clause");
        if(count == 2) break; // out of "while"
      }
    }
    monitor.expect(new String[] {
      "ThreeException",
      "In finally clause",
      "No exception",
      "In finally clause"
    });
  }
} ///:~




Nas saídas, você pode ver que sendo ou não lançada uma exceçãi, a clausula finally é sempre executada. Comentários (em inglês)



This program also gives a hint for how you can deal with the fact that exceptions in Java (like exceptions in C++) do not allow you to resume back to where the exception was thrown, as discussed earlier. If you place your try block in a loop, you can establish a condition that must be met before you continue the program. You can also add a static counter or some other device to allow the loop to try several different approaches before giving up. This way you can build a greater level of robustness into your programs. Comentários (em inglês)

What’s finally for?



In a language without garbage collection and without automatic destructor calls,[42] finally is important because it allows the programmer to guarantee the release of memory regardless of what happens in the try block. But Java has garbage collection, so releasing memory is virtually never a problem. Also, it has no destructors to call. So when do you need to use finally in Java? Comentários (em inglês)



The finally clause is necessary when you need to set something other than memory back to its original state. This is some kind of cleanup like an open file or network connection, something you’ve drawn on the screen, or even a switch in the outside world, as modeled in the following example:



//: c09:Switch.java
public class Switch {
  private boolean state = false;
  public boolean read() { return state; }
  public void on() { state = true; }
  public void off() { state = false; }
} ///:~




//: c09:OnOffException1.java
public class OnOffException1 extends Exception {} ///:~




//: c09:OnOffException2.java
public class OnOffException2 extends Exception {} ///:~




//: c09:OnOffSwitch.java
// Why use finally?

public class OnOffSwitch {
  private static Switch sw = new Switch();
  public static void f()
  throws OnOffException1,OnOffException2 {}
  public static void main(String[] args) {
    try {
      sw.on();
      // Code that can throw exceptions...
      f();
      sw.off();
    } catch(OnOffException1 e) {
      System.err.println("OnOffException1");
      sw.off();
    } catch(OnOffException2 e) {
      System.err.println("OnOffException2");
      sw.off();
    }
  }
} ///:~




The goal here is to make sure that the switch is off when main( ) is completed, so sw.off( ) is placed at the end of the try block and at the end of each exception handler. But it’s possible that an exception could be thrown that isn’t caught here, so sw.off( ) would be missed. However, with finally you can place the cleanup code from a try block in just one place: Comentários (em inglês)



//: c09:WithFinally.java
// Finally Guarantees cleanup.

public class WithFinally {
  static Switch sw = new Switch();
  public static void main(String[] args) {
    try {
      sw.on();
      // Code that can throw exceptions...
      OnOffSwitch.f();
    } catch(OnOffException1 e) {
      System.err.println("OnOffException1");
    } catch(OnOffException2 e) {
      System.err.println("OnOffException2");
    } finally {
      sw.off();
    }
  }
} ///:~




Here the sw.off( ) has been moved to just one place, where it’s guaranteed to run no matter what happens. Comentários (em inglês)



Even in cases in which the exception is not caught in the current set of catch clauses, finally will be executed before the exception handling mechanism continues its search for a handler at the next higher level:



//: c09:AlwaysFinally.java
// Finally is always executed.
import com.bruceeckel.simpletest.*;

class FourException extends Exception {}

public class AlwaysFinally {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    System.out.println("Entering first try block");
    try {
      System.out.println("Entering second try block");
      try {
        throw new FourException();
      } finally {
        System.out.println("finally in 2nd try block");
      }
    } catch(FourException e) {
      System.err.println(
        "Caught FourException in 1st try block");
    } finally {
      System.err.println("finally in 1st try block");
    }
    monitor.expect(new String[] {
      "Entering first try block",
      "Entering second try block",
      "finally in 2nd try block",
      "Caught FourException in 1st try block",
      "finally in 1st try block"
    });
  }
} ///:~




The finally statement will also be executed in situations in which break and continue statements are involved. Note that, along with the labeled break and labeled continue, finally eliminates the need for a goto statement in Java. Comentários (em inglês)

Pitfall: the lost exception



Unfortunately, there’s a flaw in Java’s exception implementation. Although exceptions are an indication of a crisis in your program and should never be ignored, it’s possible for an exception to simply be lost. This happens with a particular configuration using a finally clause: Comentários (em inglês)



//: c09:LostMessage.java
// How an exception can be lost.
// {ThrowsException}
import com.bruceeckel.simpletest.*;

class VeryImportantException extends Exception {
  public String toString() {
    return "A very important exception!";
  }
}

class HoHumException extends Exception {
  public String toString() {
    return "A trivial exception";
  }
}

public class LostMessage {
  private static Test monitor = new Test();
  void f() throws VeryImportantException {
    throw new VeryImportantException();
  }
  void dispose() throws HoHumException {
    throw new HoHumException();
  }
  public static void main(String[] args) throws Exception {
    LostMessage lm = new LostMessage();
    try {
      lm.f();
    } finally {
      lm.dispose();
    }
    monitor.expect(new String[] {
      "Exception in thread \"main\" A trivial exception",
      "\tat LostMessage.dispose(LostMessage.java:24)",
      "\tat LostMessage.main(LostMessage.java:31)"
    });  }
} ///:~




You can see that there’s no evidence of the VeryImportantException, which is simply replaced by the HoHumException in the finally clause. This is a rather serious pitfall, since it means that an exception can be completely lost, and in a far more subtle and difficult-to-detect fashion than the preceding example. In contrast, C++ treats the situation in which a second exception is thrown before the first one is handled as a dire programming error. Perhaps a future version of Java will repair this problem (on the other hand, you will typically wrap any method that throws an exception, such as dispose( ), inside a try-catch clause). Comentários (em inglês)

Exception restrictions



When you override a method, you can throw only the exceptions that have been specified in the base-class version of the method. This is a useful restriction, since it means that code that works with the base class will automatically work with any object derived from the base class (a fundamental OOP concept, of course), including exceptions. Comentários (em inglês)



This example demonstrates the kinds of restrictions imposed (at compile time) for exceptions:



//: c09:StormyInning.java
// Overridden methods may throw only the exceptions
// specified in their base-class versions, or exceptions
// derived from the base-class exceptions.

class BaseballException extends Exception {}
class Foul extends BaseballException {}
class Strike extends BaseballException {}

abstract class Inning {
  public Inning() throws BaseballException {}
  public void event() throws BaseballException {
    // Doesn't actually have to throw anything
  }
  public abstract void atBat() throws Strike, Foul;
  public void walk() {} // Throws no checked exceptions
}

class StormException extends Exception {}
class RainedOut extends StormException {}
class PopFoul extends Foul {}

interface Storm {
  public void event() throws RainedOut;
  public void rainHard() throws RainedOut;
}

public class StormyInning extends Inning implements Storm {
  // OK to add new exceptions for constructors, but you
  // must deal with the base constructor exceptions:
  public StormyInning() 
    throws RainedOut, BaseballException {}
  public StormyInning(String s) 
    throws Foul, BaseballException {}
  // Regular methods must conform to base class:
//! void walk() throws PopFoul {} //Compile error
  // Interface CANNOT add exceptions to existing
  // methods from the base class:
//! public void event() throws RainedOut {}
  // If the method doesn't already exist in the
  // base class, the exception is OK:
  public void rainHard() throws RainedOut {}
  // You can choose to not throw any exceptions,
  // even if the base version does:
  public void event() {}
  // Overridden methods can throw inherited exceptions:
  public void atBat() throws PopFoul {}
  public static void main(String[] args) {
    try {
      StormyInning si = new StormyInning();
      si.atBat();
    } catch(PopFoul e) {
      System.err.println("Pop foul");
    } catch(RainedOut e) {
      System.err.println("Rained out");
    } catch(BaseballException e) {
      System.err.println("Generic baseball exception");
    }
    // Strike not thrown in derived version.
    try {
      // What happens if you upcast?
      Inning i = new StormyInning();
      i.atBat();
      // You must catch the exceptions from the
      // base-class version of the method:
    } catch(Strike e) {
      System.err.println("Strike");
    } catch(Foul e) {
      System.err.println("Foul");
    } catch(RainedOut e) {
      System.err.println("Rained out");
    } catch(BaseballException e) {
      System.err.println("Generic baseball exception");
    }
  }
} ///:~




In Inning, you can see that both the constructor and the event( ) method say they will throw an exception, but they never do. This is legal because it allows you to force the user to catch any exceptions that might be added in overridden versions of event( ). The same idea holds for abstract methods, as seen in atBat( ). Comentários (em inglês)



The interface Storm is interesting because it contains one method (event( )) that is defined in Inning, and one method that isn’t. Both methods throw a new type of exception, RainedOut. When StormyInning extends Inning and implements Storm, you’ll see that the event( ) method in Storm cannot change the exception interface of event( ) in Inning. Again, this makes sense because otherwise you’d never know if you were catching the correct thing when working with the base class. Of course, if a method described in an interface is not in the base class, such as rainHard( ), then there’s no problem if it throws exceptions. Comentários (em inglês)



The restriction on exceptions does not apply to constructors. You can see in StormyInning that a constructor can throw anything it wants, regardless of what the base-class constructor throws. However, since a base-class constructor must always be called one way or another (here, the default constructor is called automatically), the derived-class constructor must declare any base-class constructor exceptions in its exception specification. Note that a derived-class constructor cannot catch exceptions thrown by its base-class constructor. Comentários (em inglês)



The reason StormyInning.walk( ) will not compile is that it throws an exception, but Inning.walk( ) does not. If this were allowed, then you could write code that called Inning.walk( ) and that didn’t have to handle any exceptions, but then when you substituted an object of a class derived from Inning, exceptions would be thrown so your code would break. By forcing the derived-class methods to conform to the exception specifications of the base-class methods, substitutability of objects is maintained. Comentários (em inglês)



The overridden event( ) method shows that a derived-class version of a method may choose not to throw any exceptions, even if the base-class version does. Again, this is fine since it doesn’t break any code that is written—assuming the base-class version throws exceptions. Similar logic applies to atBat( ), which throws PopFoul, an exception that is derived from Foul thrown by the base-class version of atBat( ). This way, if you write code that works with Inning and calls atBat( ), you must catch the Foul exception. Since PopFoul is derived from Foul, the exception handler will also catch PopFoul. Comentários (em inglês)



The last point of interest is in main( ). Here, you can see that if you’re dealing with exactly a StormyInning object, the compiler forces you to catch only the exceptions that are specific to that class, but if you upcast to the base type, then the compiler (correctly) forces you to catch the exceptions for the base type. All these constraints produce much more robust exception-handling code.[43] Comentários (em inglês)



It’s useful to realize that although exception specifications are enforced by the compiler during inheritance, the exception specifications are not part of the type of a method, which comprises only the method name and argument types. Therefore, you cannot overload methods based on exception specifications. In addition, just because an exception specification exists in a base-class version of a method doesn’t mean that it must exist in the derived-class version of the method. This is quite different from inheritance rules, where a method in the base class must also exist in the derived class. Put another way, the “exception specification interface” for a particular method may narrow during inheritance and overriding, but it may not widen—this is precisely the opposite of the rule for the class interface during inheritance. Comentários (em inglês)

Constructors



When writing code with exceptions, it’s particularly important that you always ask “If an exception occurs, will this be properly cleaned up?” Most of the time you’re fairly safe, but in constructors there’s a problem. The constructor puts the object into a safe starting state, but it might perform some operation—such as opening a file—that doesn’t get cleaned up until the user is finished with the object and calls a special cleanup method. If you throw an exception from inside a constructor, these cleanup behaviors might not occur properly. This means that you must be especially diligent while you write your constructor. Comentários (em inglês)



Since you’ve just learned about finally, you might think that it is the correct solution. But it’s not quite that simple, because finally performs the cleanup code every time, even in the situations in which you don’t want the cleanup code executed until the cleanup method runs. Thus, if you do perform cleanup in finally, you must set some kind of flag when the constructor finishes normally so that you don’t do anything in the finally block if the flag is set. Because this isn’t particularly elegant (you are coupling your code from one place to another), it’s best if you try to avoid performing this kind of cleanup in finally unless you are forced to. Comentários (em inglês)



In the following example, a class called InputFile is created that opens a file and allows you to read it one line (converted into a String) at a time. It uses the classes FileReader and BufferedReader from the Java standard I/O library that will be discussed in Chapter 12, but which are simple enough that you probably won’t have any trouble understanding their basic use:



//: c09:Cleanup.java
// Paying attention to exceptions in constructors.
import com.bruceeckel.simpletest.*;
import java.io.*;

class InputFile {
  private BufferedReader in;
  public InputFile(String fname) throws Exception {
    try {
      in = new BufferedReader(new FileReader(fname));
      // Other code that might throw exceptions
    } catch(FileNotFoundException e) {
      System.err.println("Could not open " + fname);
      // Wasn't open, so don't close it
      throw e;
    } catch(Exception e) {
      // All other exceptions must close it
      try {
        in.close();
      } catch(IOException e2) {
        System.err.println("in.close() unsuccessful");
      }
      throw e; // Rethrow
    } finally {
      // Don't close it here!!!
    }
  }
  public String getLine() {
    String s;
    try {
      s = in.readLine();
    } catch(IOException e) {
      throw new RuntimeException("readLine() failed");
    }
    return s;
  }
  public void dispose() {
    try {
      in.close();
      System.out.println("dispose() successful");
    } catch(IOException e2) {
      throw new RuntimeException("in.close() failed");
    }
  }
}

public class Cleanup {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    try {
      InputFile in = new InputFile("Cleanup.java");
      String s;
      int i = 1;
      while((s = in.getLine()) != null)
        ; // Perform line-by-line processing here...
      in.dispose();
    } catch(Exception e) {
      System.err.println("Caught Exception in main");
      e.printStackTrace();
    }
    monitor.expect(new String[] {
      "dispose() successful"
    });
  }
} ///:~




The constructor for InputFile takes a String argument, which is the name of the file you want to open. Inside a try block, it creates a FileReader using the file name. A FileReader isn’t particularly useful until you turn around and use it to create a BufferedReader that you can actually talk to—notice that one of the benefits of InputFile is that it combines these two actions. Comentários (em inglês)



If the FileReader constructor is unsuccessful, it throws a FileNotFoundException, which must be caught separately. This is the one case in which you don’t want to close the file, because it wasn’t successfully opened. Any other catch clauses must close the file because it was opened by the time those catch clauses are entered. (Of course, this is trickier if more than one method can throw a FileNotFoundException. In that case, you might want to break things into several try blocks.) The close( ) method might throw an exception so it is tried and caught even though it’s within the block of another catch clause—it’s just another pair of curly braces to the Java compiler. After performing local operations, the exception is rethrown, which is appropriate because this constructor failed, and you wouldn’t want the calling method to assume that the object had been properly created and was valid. Comentários (em inglês)



In this example, which doesn’t use the aforementioned flagging technique, the finally clause is definitely not the place to close( ) the file, since that would close it every time the constructor completed. Because we want the file to be open for the useful lifetime of the InputFile object, this would not be appropriate. Comentários (em inglês)



The getLine( ) method returns a String containing the next line in the file. It calls readLine( ), which can throw an exception, but that exception is caught so getLine( ) doesn’t throw any exceptions. One of the design issues with exceptions is whether to handle an exception completely at this level, to handle it partially and pass the same exception (or a different one) on, or whether to simply pass it on. Passing it on, when appropriate, can certainly simplify coding. In this situation, the getLine( ) method converts the exception to a RuntimeException to indicate a programming error. Comentários (em inglês)



The dispose( ) method must be called by the user when finished using the InputFile object. This will release the system resources (such as file handles) that are used by the BufferedReader and/or FileReader objects. You don’t want to do this until you’re finished with the InputFile object, at the point you’re going to let it go. You might think of putting such functionality into a finalize( ) method, but as mentioned in Chapter 4, you can’t always be sure that finalize( ) will be called (even if you can be sure that it will be called, you don’t know when). This is one of the downsides to Java; All cleanup—other than memory cleanup—doesn’t happen automatically, so you must inform the client programmer that they are responsible, and possibly guarantee that cleanup occurs using finalize( ). Comentários (em inglês)



In Cleanup.java an InputFile is created to open the same source file that creates the program, the file is read in a line at a time, and line numbers are added. All exceptions are caught generically in main( ), although you could choose greater granularity. Comentários (em inglês)



One of the benefits of this example is to show you why exceptions are introduced at this point in the book—there are many libraries (like I/O, mentioned earlier) that you can’t use without dealing with exceptions. Exceptions are so integral to programming in Java, especially because the compiler enforces them, that you can accomplish only so much without knowing how to work with them. Comentários (em inglês)

Exception matching



When an exception is thrown, the exception handling system looks through the “nearest” handlers in the order they are written. When it finds a match, the exception is considered handled, and no further searching occurs. Comentários (em inglês)



Matching an exception doesn’t require a perfect match between the exception and its handler. A derived-class object will match a handler for the base class, as shown in this example:



//: c09:Human.java
// Catching exception hierarchies.
import com.bruceeckel.simpletest.*;

class Annoyance extends Exception {}
class Sneeze extends Annoyance {}

public class Human {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    try {
      throw new Sneeze();
    } catch(Sneeze s) {
      System.err.println("Caught Sneeze");
    } catch(Annoyance a) {
      System.err.println("Caught Annoyance");
    }
    monitor.expect(new String[] {
      "Caught Sneeze"
    });
  }
} ///:~




The Sneeze exception will be caught by the first catch clause that it matches, which is the first one, of course. However, if you remove the first catch clause, leaving only: Comentários (em inglês)



    try {
      throw new Sneeze();
    } catch(Annoyance a) {
      System.err.println("Caught Annoyance");
    }




the code will still work because it’s catching the base class of Sneeze. Put another way, catch(Annoyance e) will catch an Annoyance or any class derived from it. This is useful because if you decide to add more derived exceptions to a method, then the client programmer’s code will not need changing as long as the client catches the base class exceptions. Comentários (em inglês)



If you try to “mask” the derived-class exceptions by putting the base-class catch clause first, like this:



    try {
      throw new Sneeze();
    } catch(Annoyance a) {
      System.err.println("Caught Annoyance");
    } catch(Sneeze s) {
      System.err.println("Caught Sneeze");
    }




o compilador dará uma mensagem de erro, já que ele verifica que a clausula catchSneezenunca será alcançada Comentários (em inglês)

Alternativas aproximadas



An exception-handling system is a trap door that allows your program to abandon execution of the normal sequence of statements. The trap door is used when an “exceptional condition” occurs, such that normal execution is no longer possible or desirable. Exceptions represent conditions that the current method is unable to handle. The reason exception handling systems were developed is because the approach of dealing with each possible error condition produced by each function call was too onerous, and programmers simply weren’t doing it. As a result, they were ignoring the errors. It’s worth observing that the issue of programmer convenience in handling errors was a prime motivation for exceptions in the first place. Comentários (em inglês)



One of the important guidelines in exception handling is “don’t catch an exception unless you know what to do with it.” In fact, one of the important goals of exception handling is to move the error-handling code away from the point where the errors occur. This allows you to focus on what you want to accomplish in one section of your code, and how you’re going to deal with problems in a distinct separate section of your code. As a result, your mainline code is not cluttered with error-handling logic, and it’s much easier to understand and maintain. Comentários (em inglês)



Checked exceptions complicate this scenario a bit, because they force you to add catch clauses in places where you may not be ready to handle an error. This results in the “harmful if swallowed” problem:



try {
  // ... to do something useful
} catch(ObligatoryException e) {} // Gulp!




Programmers (myself included, in the first edition of this book) would just do the simplest thing, and swallow the exception—often unintentionally, but once you do it, the compiler has been satisfied, so unless you remember to revisit and correct the code, the exception will be lost. The exception happens, but it vanishes completely when swallowed. Because the compiler forces you to write code right away to handle the exception, this seems like the easiest solution even though it’s probably the worst thing you can do. Comentários (em inglês)



Horrified upon realizing that I had done this, in the second edition I “fixed” the problem by printing the stack trace inside the handler (as is still seen—appropriately—in a number of examples in this chapter). While this is useful to trace the behavior of exceptions, it still indicates that you don’t really know what to do with the exception at that point in your code. In this section we’ll look at some of the issues and complications arising from checked exceptions, and options that you have when dealing with them. Comentários (em inglês)



This topic seems simple. But it is not only complicated, it is also an issue of some volatility. There are people who are staunchly rooted on either side of the fence and who feel that the correct answer (theirs) is blatantly obvious. I believe the reason for one of these positions is the distinct benefit seen in going from a poorly-typed language like pre-ANSI C to a strong, statically-typed language (that is, checked at compile-time) like C++ or Java. When you make that transition (as I did), the benefits are so dramatic that it can seem like strong static type checking is always the best answer to most problems. My hope is to relate a little bit of my own evolution, that has brought the absolute value of strong static type checking into question; clearly, it’s very helpful much of the time, but there’s a fuzzy line we cross when it begins to get in the way and become a hindrance (one of my favorite quotes is: “All models are wrong. Some are useful.”). Comentários (em inglês)

History



Exception handling originated in systems like PL/1 and Mesa, and later appeared in CLU, Smalltalk, Modula-3, Ada, Eiffel, C++, Python, Java, and the post-Java languages Ruby and C#. The Java design is similar to C++, except in places where the Java designers felt that the C++ design caused problems. Comentários (em inglês)



To provide programmers with a framework that they were more likely to use for error handling and recovery, exception handling was added to C++ rather late in the standardization process, promoted by Bjarne Stroustrup, the language’s original author. The model for C++ exceptions came primarily from CLU. However, other languages existed at that time that also supported exception handling: Ada, Smalltalk (both of which had exceptions but no exception specifications) and Modula-3 (which included both exceptions and specifications). Comentários (em inglês)



In their seminal paper[44] on the subject, Liskov and Snyder note that a major defect of languages like C that report errors in a transient fashion is that:



“...every invocation must be followed by a conditional test to determine what the outcome was. This requirement leads to programs that are difficult to read, and probably inefficient as well, thus discouraging programmers from signaling and handling exceptions.”



Note that one of the original motivations of exception handling was to prevent this requirement, but with checked exceptions in Java we commonly see exactly this kind of code. They go on to say:



“...requiring that the text of a handler be attached to the invocation that raises the exception would lead to unreadable programs in which expressions were broken up with handlers.” Comentários (em inglês)



Following the CLU approach when designing C++ exceptions, Stroustrup stated that the goal was to reduce the amount of code required to recover from errors. I believe that he was observing that programmers were typically not writing error-handling code in C because the amount and placement of such code was daunting and distracting. As a result, they were used to doing it the C way, ignoring errors in code and using debuggers to track down problems. To use exceptions, these C programmers had to be convinced to write “additional” code that they weren’t normally writing. Thus, to draw them into a better way of handling errors, the amount of code they would need to “add” must not be onerous. I think it’s important to keep this goal in mind when looking at the effects of checked exceptions in Java. Comentários (em inglês)



C++ brought an additional idea over from CLU: the exception specification, to programmatically state in the method signature what exceptions may result from calling that method. The exception specification really has two purposes. It can say “I’m originating this exception in my code, you handle it.” But it can also mean “I’m ignoring this exception that can occur as a result of my code, you handle it.” We’ve been focusing on the “you handle it” part when looking at the mechanics and syntax of exceptions, but here I’m particularly interested in the fact that often we ignore exceptions and that’s what the exception specification can state. Comentários (em inglês)



In C++ the exception specification is not part of the type information of a function. The only compile-time checking is to ensure that exception specifications are used consistently; for example, if a function or method throws exceptions, then the overloaded or derived versions must also throw those exceptions. Unlike Java, however, no compile-time checking occurs to determine whether or not the function or method will actually throw that exception, or whether the exception specification is complete (that is, whether it accurately describes all exceptions that may be thrown). That validation does happen, but only at run time. If an exception is thrown that violates the exception specification, the C++ program will call the standard library function unexpected( ). Comentários (em inglês)



It is interesting to note that, because of the use of templates, exception specifications are not used at all in the standard C++ library. Exception specifications, then, may have a significant impact on the design of Java generics (Java’s version of C++ templates, expected to appear in JDK 1.5). Comentários (em inglês)

Perspectives



First, it’s worth noting that Java effectively invented the checked exception (clearly inspired by C++ exception specifications and the fact that C++ programmers typically don’t bother with them). It has been an experiment, which no language since has chosen to duplicate. Comentários (em inglês)



Secondly, checked exceptions appear to be an obvious good thing when seen in introductory examples and in small programs. It has been suggested that the subtle difficulties begin to appear when programs start to get large. Of course, largeness usually doesn’t happen overnight; it creeps. Languages that may not be suited for large-scale projects are used for small projects that grow, and at some point we realize that things have gone from manageable to difficult. This is what I’m suggesting may be the case with too much type checking; in particular, with checked exceptions. Comentários (em inglês)



The scale of the program seems to be a significant issue. This is a problem because most discussions tend to use small programs as demonstrations. One of the C# designers observed that:



“Examination of small programs leads to the conclusion that requiring exception specifications could both enhance developer productivity and enhance code quality, but experience with large software projects suggests a different result—decreased productivity and little or no increase in code quality.” [45] Comentários (em inglês)



In reference to uncaught exceptions, the CLU creators stated:



“We felt it was unrealistic to require the programmer to provide handlers in situations where no meaningful action can be taken.” [46] Comentários (em inglês)



When explaining why a function declaration with no specification means that it can throw any exception, rather than no exceptions, Stroustrup states:



“However, that would require exception specifications for essentially every function, would be a significant cause for recompilation, and would inhibit cooperation with software written in other languages. This would encourage programmers to subvert the exception-handling mechanisms and to write spurious code to suppress exceptions. It would provide a false sense of security to people who failed to notice the exception.” [47]



We see this very behavior—subverting the exceptions—happening with checked exceptions in Java. Comentários (em inglês)



Martin Fowler (author of UML Distilled, Refactoring, and Analysis Patterns) wrote the following to me:



“...on the whole I think that exceptions are good, but Java checked exceptions are more trouble than they are worth.” Comentários (em inglês)



I now think that Java’s important step was to unify the error reporting model, so that all errors are reported using exceptions. This wasn’t happening with C++, because for backward compatibility with C the old model of just ignoring errors was still available. But if you have consistent reporting with exceptions, then the exceptions can be used if desired, and if not, they will propagate out to the highest level (the console or other container program). When Java changed the C++ model so that exceptions were the only way to report errors, the extra enforcement of checked exceptions may have become less necessary. Comentários (em inglês)



In the past, I have been a strong believer that both checked exceptions and strong static type checking were essential to robust program development. However, both anecdotal and direct experience[48] with languages that are more dynamic than static have lead me to think that the great benefits actually come from:

  1. A unified error-reporting model via exceptions, regardless of whether the programmer is forced by the compiler to handle them.
  2. Type checking, regardless of when it takes place. That is, as long as proper use of a type is enforced, it doesn’t matter if it happens at compile time or run time. Feedback


On top of this, there are very significant productivity benefits to reducing the compile-time constraints upon the programmer. Indeed, reflection (and eventually, generics) is required to compensate for the over-constraining nature of strong static typing, as you shall see in the next chapter and in a number of examples throughout the book. Comentários (em inglês)



I’ve already been told by some that what I say here constitutes blasphemy, and by uttering these words my reputation will be destroyed, civilizations will fall, and a higher percentage of programming projects will fail. The belief that the compiler can save your project by pointing out errors at compile time runs strong, but it’s even more important to realize the limitation of what the compiler is able to do; in Chapter 15, I emphasize the value of an automated build process and unit testing, which give you far more leverage than you get by trying to turn everything into a syntax error. It’s worth keeping in mind that:



A good programming language is one that helps programmers write good programs. No programming language will prevent its users from writing bad programs.[49] Comentários (em inglês)



In any event, the likelihood of checked exceptions ever being removed from Java seems dim. It would be too radical of a language change, and proponents within Sun appear to be quite strong. Sun has a history and policy of absolute backwards compatibility—to give you a sense of this, virtually all Sun software runs on all Sun hardware, no matter how old. However, if you find that some checked exceptions are getting in your way, or especially if you find yourself being forced to catch exceptions, but you don’t know what to do with them, there are some alternatives. Comentários (em inglês)

Passing exceptions to the console



In simple programs, like many of those in this book, the easiest way to preserve the exceptions without writing a lot of code is to pass them out of main( ) to the console. For example, if you want to open a file for reading (something you’ll learn about in detail in Chapter 12), you must open and close a FileInputStream, which throws exceptions. For a simple program, you can do this (you’ll see this approach used in numerous places throughout this book): Comentários (em inglês)



//: c09:MainException.java
import java.io.*;

public class MainException {
  // Pass all exceptions to the console:
  public static void main(String[] args) throws Exception {
    // Open the file:
    FileInputStream file =
      new FileInputStream("MainException.java");
    // Use the file ...
    // Close the file:
    file.close();
  }
} ///:~




Note that main( ) is also a method that may have an exception specification, and here the type of exception is Exception, the root class of all checked exceptions. By passing it out to the console, you are relieved from writing try-catch clauses within the body of main( ). (Unfortunately, file I/O is significantly more complex than it would appear to be from this example, so don’t get too excited until after you’ve read Chapter 12). Comentários (em inglês)

Converting checked to unchecked exceptions



Throwing an exception from main( ) is convenient when you’re writing a main( ), but not generally useful. The real problem is when you are writing an ordinary method body, and you call another method and realize “I have no idea what to do with this exception here, but I don’t want to swallow it or print some banal message.” With JDK 1.4 chained exceptions, a new and simple solution prevents itself. You simply “wrap” a checked exception inside a RuntimeException, like this: Comentários (em inglês)



try {
  // ... to do something useful
} catch(IDontKnowWhatToDoWithThisCheckedException e) {
  throw new RuntimeException(e);
}




This seems to be an ideal solution if you want to “turn off” the checked exception—you don’t swallow it, and you don’t have to put it in your method’s exception specification, but because of exception chaining you don’t lose any information from the original exception. Comentários (em inglês)



This technique provides the option to ignore the exception and let it bubble up the call stack without being required to write try-catch clauses and/or exception specifications. However, you may still catch and handle the specific exception by using getCause( ), as seen here: Comentários (em inglês)



//: c09:TurnOffChecking.java
// "Turning off" Checked exceptions.
import com.bruceeckel.simpletest.*;
import java.io.*;

class WrapCheckedException {
  void throwRuntimeException(int type) {
    try {
      switch(type) {
        case 0: throw new FileNotFoundException();
        case 1: throw new IOException();
        case 2: throw new RuntimeException("Where am I?");
        default: return;
      }
    } catch(Exception e) { // Adapt to unchecked:
      throw new RuntimeException(e);
    }
  }
}

class SomeOtherException extends Exception {}

public class TurnOffChecking {
  private static Test monitor = new Test();
  public static void main(String[] args) {
    WrapCheckedException wce = new WrapCheckedException();
    // You can call f() without a try block, and let
    // RuntimeExceptions go out of the method:
    wce.throwRuntimeException(3);
    // Or you can choose to catch exceptions:
    for(int i = 0; i < 4; i++)
      try {
        if(i < 3)
          wce.throwRuntimeException(i);
        else
          throw new SomeOtherException();
      } catch(SomeOtherException e) {
          System.out.println("SomeOtherException: " + e);
      } catch(RuntimeException re) {
        try {
          throw re.getCause();
        } catch(FileNotFoundException e) {
          System.out.println(
            "FileNotFoundException: " + e);
        } catch(IOException e) {
          System.out.println("IOException: " + e);
        } catch(Throwable e) {
          System.out.println("Throwable: " + e);
        }
      }
    monitor.expect(new String[] {
      "FileNotFoundException: " +
      "java.io.FileNotFoundException",
      "IOException: java.io.IOException",
      "Throwable: java.lang.RuntimeException: Where am I?",
      "SomeOtherException: SomeOtherException"
    });
  }
} ///:~




WrapCheckedException.throwRuntimeException( ) contains code that generates different types of exceptions. These are caught and wrapped inside RuntimeException objects, so they become the “cause” of those exceptions. Comentários (em inglês)



In TurnOffChecking, you can see that it’s possible to call throwRuntimeException( ) with no try block because the method does not throw any checked exceptions. However, when you’re ready to catch exceptions, you still have the ability to catch any exception you want by putting your code inside a try block. You start by catching all the exceptions you explicitly know might emerge from the code in your try block—in this case, SomeOtherException is caught first. Lastly, you catch RuntimeException and throw the result of getCause( ) (the wrapped exception). This extracts the originating exceptions, which can then be handled in their own catch clauses. Comentários (em inglês)



The technique of wrapping a checked exception in a RuntimeException will be used when appropriate throughout the rest of this book. Comentários (em inglês)

Exception guidelines



Use exceptions to:

  1. Handle problems at the appropriate level. (Avoid catching exceptions unless you know what to do with them).
  2. Fix the problem and call the method that caused the exception again.
  3. Patch things up and continue without retrying the method.
  4. Calculate some alternative result instead of what the method was supposed to produce.
  5. Do whatever you can in the current context and rethrow the same exception to a higher context.
  6. Do whatever you can in the current context and throw a different exception to a higher context.
  7. Terminate the program.
  8. Simplify. (If your exception scheme makes things more complicated, then it is painful and annoying to use.)
  9. Make your library and program safer. (This is a short-term investment for debugging, and a long-term investment for application robustness.) Feedback

Summary



Improved error recovery is one of the most powerful ways that you can increase the robustness of your code. Error recovery is a fundamental concern for every program you write, but it’s especially important in Java, where one of the primary goals is to create program components for others to use. To create a robust system, each component must be robust. By providing a consistent error-reporting model with exceptions, Java allows components to reliably communicate problems to client code. Comentários (em inglês)



Os objetivos para tratar exceções em java são simplificar a criação de programas grandes e seguros usando menos código do que possível atualmente, também com mais segurança do que se sua aplicação não tivesse um tratamento de erros. Exceções não são difíceis de aprender, e são uma das características que fornecem benefícios imediatos e significativos para seu projeto. Comentários (em inglês)

Exercícios



Soluções para os exercícios selecionados podem ser encontradas no documento eletrônico The Thinking in Java Annotated Solution Guide, disponível por uma pequena taxa em www.BruceEckel.com.

  1. Crie uma classe com um main( ) que jogue um objeto da classe Exception dentro de um bloco try. Dar ao construtor por Exception um argumento String. Capture a exceção dentro de uma clausula catch e imprima o argumento String. Adicione uma clausula finally e imprima uma mensagem para provar você esteve lá. Análise
  2. Crie sua própria classe de exceção usando a palavra chave extends. Escreva um construtor para esta classe que tome um argumento String e armazene-o dentro do objeto com uma referência String. Escreva um método que exiba a Stringarmazenada. Crie uma clausula try-catch para exercitar sua nova exceção. Análise
  3. Escreva uma classe com um método que jogue uma exceção do tipo criado no Exercício 2. Tente compilá-la sem uma especificação de exceção para ver o que o compilado diz. Adicione a especificação de exceção apropriada. Teste sua classe e a exceção dela dentro de uma clausula try-catch. Análise
  4. Define an object reference and initialize it to null. Try to call a method through this reference. Now wrap the code in a try-catch clause to catch the exception. Feedback
  5. Create a class with two methods, f( ) and g( ). In g( ), throw an exception of a new type that you define. In f( ), call g( ), catch its exception and, in the catch clause, throw a different exception (of a second type that you define). Test your code in main( ). Feedback
  6. Repeat the previous exercise, but inside the catch clause, wrap g( )’s exception in a RuntimeException.
  7. Create three new types of exceptions. Write a class with a method that throws all three. In main( ), call the method but only use a single catch clause that will catch all three types of exceptions. Feedback
  8. Write code to generate and catch an ArrayIndexOutOfBoundsException. Feedback
  9. Create your own resumption-like behavior by using a while loop that repeats until an exception is no longer thrown. Feedback
  10. Create a three-level hierarchy of exceptions. Now create a base-class A with a method that throws an exception at the base of your hierarchy. Inherit B from A and override the method so it throws an exception at level two of your hierarchy. Repeat by inheriting class C from B. In main( ), create a C and upcast it to A, then call the method. Feedback
  11. Demonstrate that a derived-class constructor cannot catch exceptions thrown by its base-class constructor. Feedback
  12. Show that OnOffSwitch.java can fail by throwing a RuntimeException inside the try block. Feedback
  13. Show that WithFinally.java doesn’t fail by throwing a RuntimeException inside the try block. Feedback
  14. Modify Exercise 7 by adding a finally clause. Verify that your finally clause is executed, even if a NullPointerException is thrown. Feedback
  15. Create an example where you use a flag to control whether cleanup code is called, as described in the second paragraph after the heading “Constructors.” Feedback
  16. Modify StormyInning.java by adding an UmpireArgument exception type and methods that throw this exception. Test the modified hierarchy. Feedback
  17. Remove the first catch clause in Human.java and verify that the code still compiles and runs properly. Feedback
  18. Add a second level of exception loss to LostMessage.java so that the HoHumException is itself replaced by a third exception. Feedback
  19. Add an appropriate set of exceptions to c08:GreenhouseControls.java. Feedback
  20. Add an appropriate set of exceptions to c08:Sequence.java.
  21. Change the file name string in MainException.java to name a file that doesn’t exist. Run the program and note the result.



[40] The C programmer can look up the return value of printf( ) for an example of this.



[41] C++ exception handling does not have the finally clause because it relies on destructors to accomplish this sort of cleanup.



[42] A destructor is a function that’s always called when an object becomes unused. You always know exactly where and when the destructor gets called. C++ has automatic destructor calls, and C# (which is much more like Java) has a way that automatic destruction can occur.



[43] ISO C++ added similar constraints that require derived-method exceptions to be the same as, or derived from, the exceptions thrown by the base-class method. This is one case in which C++ is actually able to check exception specifications at compile time.



[44] Barbara Liskov and Alan Snyder: Exception Handling in CLU, IEEE Transactions on Software Engineering, Vol. SE-5, No. 6, November 1979. This paper is not available on the Internet, only in print form so you’ll have to contact a library to get a copy.



[45] http://discuss.develop.com/archives/wa.exe?A2=ind0011A&L=DOTNET&P=R32820



[46] ibid



[47] Bjarne Stroustrup, The C++ Programming Language, 3rd edition, Addison-Wesley 1997, pp 376.



[48] Indiretamente com Smalltalk através de conversas com programadores experientes nesta linguagem; diretamente com Python (www.Python.org).



[49] (Kees Koster, projetista da linguagem CDL, citado por Bertrand Meyer, projetista da linguagem Eiffel). http://www.elj.com/elj/v1/n1/bm/right/.


Anterior Próximo Página Inicial Índice Conteúdo