Seminário: Manipulação de exceções em Java Fabiana Piesigilli Rodrigo Mendes Leme MAC 441 - Programação Orientada a Objetos Introdução Introdução Mundo ideal: dados estão sempre na forma certa, arquivos desejados sempre existem, etc. Mundo real: dados ruins e bugs podem arruinar o programa. Introdução Necessidade de mecanismos para tratamento de erros. Antes da POO: Variável global inteira com valores de 0 até n. Na ocorrência de uma exceção: Variável assumia um valor. Remetia uma mensagem de erro. Encerrava o programa. Introdução Depois da POO: Classes de erros. Possíveis tipos de erros e seus tratamentos são agrupados. Não há necessidade de interromper o programa. O mesmo erro é tratado quantas vezes for necessário. Introdução Idéia básica: “código ruim não será executado”. Nem todos os erros podem ser detalhados em tempo de compilação. Os que não podem devem ser lidados em tempo de execução. Estes últimos são o alvo da manipulação de exceções. Introdução Premissa básica: separar o processamento normal da manipulação de erros. Vantagens desse mecanismo: Permite concentrar em lugares diferentes o “código normal” do tratamento do erro. Simplifica a criação de programas grandes usando menos código. Torna o código mais robusto, ao garantir que não há erros sem tratamento. Exceções - básico Hierarquia de exceções de Java Throwable Error Exception ... ... IOException RunTimeException ... ... Exceção: problema que impede a continuação do método ou escopo em execução. Importante: exceção problema normal. Problema normal: há informação suficiente no contexto atual para lidar com ele. Exceção: não há informação suficiente. Disparar uma exceção: sair do contexto atual e relegar a solução a um contexto mais abrangente. Exceções: Básico Ao disparar-se uma exceção, ocorre a seguinte sequência de eventos: Um objeto exceção é criado. A execução é interrompida. O mecanismo de manipulação de exceções assume o controle o procura o manipulador de exceção adequado. O manipulador da exceção trata o problema. Exemplo: seja t uma referência para um objeto, que pode não ter sido inicializado. if (t == null) throw new NullPointerException(); A palavra chave throw dispara uma exceção e dá início à sequência de eventos citada anteriormente. Outra versão: if (t == null) throw new NullPointerException (“t = null”); Este construtor permite colocar informações pertinentes na exceção, que posteriormente podem ser extraídas usando outros métodos. Em resumo, disparar uma exceção é fácil: 1) Escolha uma classe de exceção apropriada. 2) Instancie um objeto dessa classe. 3) Dispare-o. throw new EOFException(); (3) (2) (1) Capturando exceções Capturando exceções Quando uma exceção é disparada, em algum lugar ela deve ser capturada. Região protegida: trecho de código que pode gerar exceções. Manipuladores de exceções: tratam as exceções que ocorreram dentro da região protegida. Vêm imediatamente após a mesma. Em Java: try: indica a região protegida. catch: manipula uma exceção. Formato básico: try { // Código } catch(ClasseDeExceção e) { // Manipula aquele tipo de erro } Pode-se usar vários manipuladores: try { ... } catch(ClasseDeExcecao1 c1) { ... } catch(ClasseDeExcecao2 c2) { ... } catch(ClasseDeExcecao3 c3) { ... } ... Capturando exceções Processo: A exceção é disparada dentro de um bloco try. O mecanismo de manipulação de exceção procura o primeiro catch cujo tipo de exceção bata com a exceção disparada. O mecanismo entra no bloco do catch e o erro é tratado. Exemplo: método para ler caracteres de um arquivo. public static String readString() { int carac; String cad = “”; boolean terminou = false; while (!terminou) { try { carac = System.in.read(); if (carac < 0 || (char) carac == `\n`) terminou = true; else cad = cad + (char) carac; } catch(IOException e) { terminou = true; } ... CRIANDO SUAS PRÓPRIAS EXCEÇÕES class MyException extends Exception { public MyException() {} public MyException(String msg) { super(msg); } } public class Inheriting { public static void f() throws MyException { System.out.println("Throwing MyException from f()"); throw new MyException(); } public static void g() throws MyException { System.out.println("Throwing MyException from g()"); throw new MyException("Originated in g()"); } public static void main(String[] args) { try { f(); } catch(MyException e) { e.printStackTrace(); } try { g(); } catch(MyException e) { e.printStackTrace(); } } } Criando suas próprias exceções Throwing MyException from f() MyException at Inheriting.f(Inheriting.java:16) at Inheriting.main(Inheriting.java:24) Throwing MyException from g() MyException:Originated in g() at Inheriting.g(Inheriting.java:20) at Inheriting.main(Inheriting.java:29) Criando suas próprias exceções class MyException2 extends Exception { public MyException2() {} public MyException2(String msg) { super(msg); } public MyException2(String msg, int x) { super(msg); i = x; } public int val() { return i; } private int i; } public class Inheriting2 { public static void f() throws MyException2 { System.out.println( "Throwing MyException2 from f()"); throw new MyException2(); } public static void g() throws MyException2 { System.out.println( "Throwing MyException2 from g()"); throw new MyException2("Originated in g()"); } public static void h() throws MyException2 { System.out.println( "Throwing MyException2 from h()"); throw new MyException2( "Originated in 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.out.println("e.val() = " + e.val()); } } } Criando suas próprias exceções Throwing MyException2 from f() MyException2 at Inheriting2.f(Inheriting2.java:22) at Inheriting2.main(Inheriting2.java:34) Throwing MyException2 from g() MyException2: Originated in g() at Inheriting2.g(Inheriting2.java:26) at Inheriting2.main(Inheriting2.java:39) Throwing MyException2 from h() MyException2: Originated in h() at Inheriting2.h(Inheriting2.java:30) at Inheriting2.main(Inheriting2.java:44) e.val() = 47 Criando suas próprias exceções class SimpleException extends Exception { } Especificação de Exceções Especificação de Exceções void f() throws tooBig, tooSmall, divZero { //... } void f() { // ... } EXCEÇÕES PADRÃO DO JAVA Exceções padrão do Java Throwable: Error Exception Exceções padrão do Java http://java.sun.com Exceções padrão do Java Todas as classes herdam de java.lang.Exception Mas nem todas estão definidas em java.lang. Por exemplo: java.io.IOException Exceções padrão do Java RuntimeException Exceções padrão do Java: RuntimeException if(t == null) throw new NullPointerException(); Exceções padrão do Java: RuntimeException public class NeverCaught { static void f() { throw new RuntimeException("From f()"); } static void g() { f(); } public static void main(String[] args) { g(); } } Exceções padrão do Java: RuntimeException java.lang.RuntimeException: From f() at NeverCaught.f(NeverCaught.java:9) at NeverCaught.g(NeverCaught.java:12) at NeverCaught.main(NeverCaught.java:15) Exceções padrão do Java: RuntimeException Se uma RuntimeException chega ao main sem ser capturada, printStackTrace( ) é chamado, e o programa sai. Exceções padrão do Java: RuntimeException Uma RuntimeException significa um erro de programação: Um erro do programador cliente, que passou um ponteiro nulo, causando uma NullPointerException Um erro seu, que não verificou se estava acessando um índice válido do vetor, e causou uma ArrayIndexOutOfBoundsException Finally Frequentemente existe algum trecho de código que deve ser executado independente de uma exceção ter ou não ter sido disparada. Problema: se o método alocou um recurso e uma exceção foi disparada, o recurso pode não ter sido liberado. Apesar de Java possuir coleta de lixo, pode ser necessário retornar algum recurso não relacionado a memória para seu estado original. Exemplos: fechar um arquivo, fechar uma conexão de rede, redesenhar algum componente na tela, etc. Em Java: finally: indica o trecho de código que sempre será executado. Formato básico: try { // Código } catch(ClasseDeExcecao e) { // Manipulador da exceção } finally { ... // Código que será } // executado sempre Exemplo: em quaisquer circunstâncias, Java executará g.dispose(). Graphics g = image.getGraphics(); try { ... } catch(IOException e) { terminou = true; } finally { g.dispose(); } Possibilidades de execução do finally: O código não dispara exceções: tudo o que estiver no try e, em seguida, no finally, é executado. O código dispara uma exceção que é capturada por um catch: tudo o que estiver no try até a exceção ser disparada é executado. Depois, executa o código do catch e, por fim, o finally. O código dispara uma exceção que não é capturada por nenhum catch: tudo o que estiver no try até a exceção ser disparada é executado. Depois, executa o código do finally. Restrições a exceções Existe uma tendência a se abusar de exceções. Exceções podem diminuir muito o desempenho do código. Algumas dicas devem ser seguidas quando se está usando exceções. Testes simples não devem ser substituídos por manipulação de exceções. Exemplo: tentar desempilhar 1.000.000 de vezes uma pilha vazia. 1)if (!pilha.empty()) pilha.pop(); ---> 6 s 2)try { pilha.pop(); } ---> 64 s catch(EmptyStackException e){...} Conclusão: use exceções apenas para circunstâncias excepcionais. Não microgerencie exceções. Exemplo: gravar os elementos de uma pilha num arquivo. for (i = 0; i < 100; i++) { try { n = pilha.pop(); } catch(EmptyStackException s){...} try { out.writeInt(n); } catch(IOException e){...} } Se a pilha estiver vazia, continuará vazia; se houver um erro no arquivo, ele não sumirá. Faz mais sentido colocar toda a operação no try. try { for (i = 0; i < 100; i++) { n = pilha.pop(); out.writeInt(n); } catch(IOException e) {...} catch(EmptyStackException s){...} } Conclusão: o código fica mais limpo, garantindo a premissa básica. Não bloqueie exceções. Exemplo: carregar uma imagem de arquivo. Image loadImage(String nomearq) { try { // Inúmeras linhas } // de código catch (ClasseDeExcecao e) {} } Conclusão: o programador deve se esforçar para gerenciar exceções corretamente. Construtores import java.io.*; class InputFile { private BufferedReader in; InputFile(String fname) throws Exception { try { in = new BufferedReader(new FileReader(fname)); // Other code that might throw exceptions } catch(FileNotFoundException e) { System.out.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.out.println( "in.close() unsuccessful"); } throw e; } finally { // Don't close it here!!! } } String getLine() { String s; try { s = in.readLine(); } catch(IOException e) { System.out.println( "readLine() unsuccessful"); s = "failed"; } return s; } void cleanup() { try { in.close(); } catch(IOException e2) { System.out.println( "in.close() unsuccessful"); } } } Construtores public class Cleanup { public static void main(String[] args){ try { InputFile in = new InputFile("Cleanup.java"); String s; int i = 1; while((s = in.getLine()) != null) System.out.println(""+ i++ + ": " + s); in.cleanup(); } catch(Exception e) { System.out.println( "Caught in main, e.printStackTrace()"); e.printStackTrace(); } } } Contrutores String getLine() throws IOException { return in.readLine(); } Casamento de Exceções Casamento de Exceções class Annoyance extends Exception {} class Sneeze extends Annoyance {} public class Human { public static void main(String[] args) { try { throw new Sneeze(); } catch(Sneeze s) { System.out.println("Caught Sneeze"); } catch(Annoyance a) { System.out.println("Caught Annoyance"); } } } Casamento de Exceções try { throw new Sneeze(); } catch(Annoyance a) { System.out.println("Caught Annoyance"); } catch(Sneeze s) { System.out.println("Caught Sneeze"); } Conclusão Conclusão Use exceções para: 1) Consertar o problema e chamar o método que causou a exceção de novo 2) Contornar o erro e continuar sem tentar o método novamente 3) Calcular algum resultado alternativo em vez daquele que o método deveria produzir 4) Fazer o que for possível no contexto atual e lançar a mesma exceção para o contexto superior 5) Fazer o que for possível no contexto atual e lançar uma exceção diferente para o contexto superior 6) Terminar o programa 7) Simplificar. Se seu esquema de exceções complica as coisas, então ele será ruim para ser usado 8) Tornar sua biblioteca e seu programa mais seguros