Pontifícia Universidade Católica de São Paulo Departamento de Ciência da Computação Apontamento 14 Maio de 2004 LP: Laboratório de Programação Prof. ISVega Mecanismo de Exceções em Java CONTEÚDO 14.1 Erros, Faltas e Falhas . . . . . . . . . . . . 14.2 Tratamento de Exceções em Java . . . . . 14.2.1Hierarquia de Exceções . . . . . . . 14.2.2Lançamento e Captura de Exceções Exercícios . . . . . . . . . . . . . . . . . . . . . . Objetivos . . . . . . . . . . . . . . . . . . . . 1 3 6 7 12 • Mostrar o mecanismo de exceções do ambiente Java. 14.1 Erros, Faltas e Falhas Erros Erros introduzem faltas nos programas que, ao serem executadas, provocam falhas de execução. Erros podem e devem ser corrigidos. Exemplo 14.1 Situações comuns de erros que geram faltas: • Divisão por zero. • Acesso a um array utilizando-se um índice inválido. • Tentativa de usar uma referência null. ¨ 1 Laboratório de Programação Maio de 2004 Exceções Uma exceção é um sinal indicando uma situação excepcional, provocada por um fator externo ao programa. A ocorrência deste problema deve ser tratada por um trecho de programa que varia em natureza e quantidade de software para software. Exemplo 14.2 Situações comuns que geram exceções (Figura 14.1): • Tentativa de abrir um arquivo inexistente. • Esgotamento de memória. Figura 14.1: Situações de erro exigem tratamento. ¨ Exceções são problemas causados por uma fonte externa ao programa. Quando uma falha acontece devido a esta fonte externa, a instrução faltosa gera uma exceção (throw an exception). Estes eventos podem ocorrer a qualquer momento, e o seu efeito é o de interromper a execução, caso a aplicação não tenha sido preparada para tratá-los (Figura 14.2). Figura 14.2: A execução de uma instrução faltora provoca a ocorrência de uma falha, gerando uma exceção. Estratégias de Tratamento de Exceções das para lidar com exceções: Três grandes estratégias podem ser aplica- 1. Ignorar todas, considerando apenas as mais importantes. O código fica mais claro, mas o programa irá se comportar de forma incorreta diante de uma situação inesperada. 2. Tratar cada possibilidade juntamente com a lógica principal. O código fica obscuro, mas o seu funcionamento é melhor. Nem todas as situções são previstas. c Copyright °1998-2004, Dr. Italo S. Vega 14-2 Laboratório de Programação Maio de 2004 3. Programar a lógica normal, separada do tratamento da exceção. O texto que trata a exceção está localizado na região onde a exceção pode ocorrer. A vantagem deste enfoque é a proximidade entre a região de ocorrência da exceção e a região onde ela é tratada. Exemplo 14.3 A Figura 14.3-a mostra o mapa de execução de um modelo de aplicação capaz de tratar exceções segundo esta última estratégia. Dois cenários de execução são apresentados: • Figura 14.3-b: um cenário normal de execução. Neste caso, não ocorre falha no ponto a. • Figura 14.3-c: um cenário snormal de execução. Neste caso, ocorre falha no ponto a, provocando um desvio para o trecho de rota que trata a falha. [ Exceção ] tratamento (a) a [ Exceção ] tratamento a [ Exceção ] a tratamento (b) (c) Figura 14.3: (a) Um mapa de execução com tratamento de exceção, (b) um cenário normal de execução, (c) e outro anormal. ¨ 14.2 Tratamento de Exceções em Java Blocos try Uma exceção que ocorre em um bloco try é normalmente tratada por um código especificado no bloco catch imediato: hPadrão de tratamento de exceções em Javai≡ try { // lógica "normal" (ignorando quaisquer exceções" } catch( <Tipo da Exceção> exceção ) { // tratamento da exceção } c Copyright °1998-2004, Dr. Italo S. Vega 14-3 Laboratório de Programação Maio de 2004 Exemplo 14.4 Considere o mapa de execução de uma aplicação que solicita um número para o usuário, mostrado na Figura 14.4(a). main() main() main() dado ← obterString() dado ← obterString() dado ← obterString() numero ← converterInt( dado ) numero ← converterInt( dado ) numero ← converterInt( dado ) mostrar( numero ) mostrar( numero ) exit( 0 ) exit( 0 ) (a) (b) (c) Figura 14.4: Um mapa de execução (a) para conversão de texto em número, um cenário normal (b) e outro anormal (c) de execução. Supondo que o valor do dado de entrada corresponda a 2, este mapa é percorrido do início ao fim sem a ocorrência de erros. Este cenário normal pode ser visualizado como na Figura 14.4(b). Entretanto, se 2.5 for usado como valor do dado de entrada, um cenário anormal é observado pela ocorrência de uma exceção, como mostrado na Figura 14.4(c). Este mapa pode ser implementado da seguinte forma em Java: hExemplo de um programa com faltai≡ import javax.swing.JOptionPane; public class Aplicacao { public static void main( String[] args ) { String dado = JOptionPane.showInputDialog( "Digite um numero:" ); int numero = Integer.parseInt( dado ); // <- falta System.out.println( numero ); System.exit( 0 ); } } A ocorrência da exceção é conseqüência de uma falha de execução. Tais exceções podem ser tratadas por programação Figura 14.5. c Copyright °1998-2004, Dr. Italo S. Vega 14-4 Laboratório de Programação Maio de 2004 main() dado ← obterString() [ Exceção ] mostrar ( “Dado não numérico” ) numero ← converterInt( dado ) mostrar( numero ) exit( 0 ) Figura 14.5: Mapa que mostra o tratamento da exceção. main() main() dado [ Exceção ] mostrar ( “Dado não numérico” ) numero obterString() converterInt( dado ) mostrar( numero ) exit( 0 ) (a) dado [ Exceção ] mostrar ( “Dado não numérico” ) numero obterString() converterInt( dado ) mostrar( numero ) exit( 0 ) (b) Figura 14.6: Um mapa de execução para conversão de texto em número, um cenário com tratamento da exceção (a) e outro normal (b) de execução. c Copyright °1998-2004, Dr. Italo S. Vega 14-5 Laboratório de Programação Maio de 2004 Em Java, pode-se tratar a exceção provocada pela falha de execução por um bloco try: hExemplo de um programa com falta e tratamento de exceçãoi≡ import javax.swing.JOptionPane; public class Aplicacao { public static void main( String[] args ) { String dado = JOptionPane.showInputDialog( "Digite um numero:" ); int numero = -1; try { numero = Integer.parseInt( dado ); } catch( NumberFormatException ref ) { JOptionPane.showMessageDialog( null, "Voce deve digitar um numero", "Formato de numero invalido", JOptionPane.ERROR_MESSAGE ); } System.out.println( numero ); System.exit( 0 ); } } ¨ 14.2.1 Hierarquia de Exceções A tecnologia Java classifica os eventos gerados pelas falhas de execução em erros e exceções. Estas, por sua vez, podem ser exceções pré-definidas ou definidas pelo usuário (Figura 14.7). Throwable Error RuntimeException Exception Usuário Figura 14.7: Classificação de eventos gerados por falhas (diagrama de classes UML). c Copyright °1998-2004, Dr. Italo S. Vega 14-6 Laboratório de Programação Maio de 2004 • Throwable – unifica as noções de erros e exceções. • Error - não deve ser tratado; deve ser corrigido. • Exception – pode ser tratado. – RuntimeException – recebem um tratamento padrão do ambiente de execução Java. – Usuário – para tratamento especificado pelo usuário. Dos diversos erros e exceções definidos pela tecnologia Java, as seguintes são as mais comuns: • IOException – um problema de comunicação externa. – FileNotFoundException – arquivo inexistente – EOFException – tentativa de leitura após o término do arquivo • NullPointerException (RuntimeException). – tentativa de usar um contexto null • NumberFormatException – tentativa converter um String não-numérico em número (RuntimeException). • OutOfMemoryError – programa que esgotou toda a memória disponível (Error). • IllegalArgumentException – argumentos ilegais na execução de um método. 14.2.2 Lançamento e Captura de Exceções Lançamento de Exceções O lançamento de uma exceção é feito quando se cria um objeto de uma classe Throwable, passando-a para o mecanismo throw. Exceções podem ser lançadas apenas durante a execução de um método, desde que haja uma indicação de quais classes de exceções ele poderá lançar. conteúdo capacidade delta Exemplo 14.5 Considere o tanque representado pelo esquema da Figura 14.8. Tanque Figura 14.8: Esquema de um tanque de água. c Copyright °1998-2004, Dr. Italo S. Vega 14-7 Laboratório de Programação Maio de 2004 Em Java, o tanque pode ser descrito da seguinte forma: hlab/tanque/Tanquei≡ public class Tanque { public int capacidade = 5000; // litros public int conteudo; // do tanque hTanque: métodosi } O método de construção de tanques vazios é imediato: hTanque: métodosi≡ // Construtor de tanques vazios: public Tanque() { conteudo = 0; } Entretanto, pode-se construir um tanque a partir de uma determinada quantidade inicial de água. Neste caso, duas situações deverão ser tratadas no modelo computacional do tanque: • quantidade inicial contendo um valor negativo, a qual deverá ser rejeitada, impedindo-se a construção do respectivo tanque. • quantidade inicial superando a capacidade do tanque, que também deverá ser rejeitada, impedindo-se a construção do respectivo tanque. Para impedir que um objeto da classe Tanque seja criado, lança-se uma exceção apropriada: hTanque: métodosi+≡ public Tanque( int inicial ) throws TanqueTransbordouException, IllegalArgumentException { if( inicial < 0 ) { throw new IllegalArgumentException(); } int delta = inicial - capacidade; if( delta > 0 ) { throw new TanqueTransbordouException( this, delta ); } conteudo = inicial; } c Copyright °1998-2004, Dr. Italo S. Vega 14-8 Laboratório de Programação Maio de 2004 Este construtor faz uso de duas classes de exceção: • IllegalArgumentException – indicando que a quantidade inicial é ilegal. Esta classe é oferecida pela bibliteca de classes do ambiente Java. • TanqueTransbordouException – criada para este programa, indicando que o tanque contém excesso de água. É um exemplo de exceção do usuário. A codificação da classe TanqueTransbordouException segue o roteiro estabelecido pela tecnologia Java: hlab/tanque/TanqueTransbordouException.javai≡ public class TanqueTransbordouException extends Exception { public Tanque tanqueTransbordado; public int excesso; public TanqueTransbordouException( Tanque novo, int quantidade ) { super(); tanqueTransbordado = novo; excesso = quantidade; } } O método para inserir uma determinada quantidade de água no tanque pode ser descrito da seguinte forma: hTanque: métodosi+≡ public void acrescentar( int quantidade ) throws TanqueTransbordouException, IllegalArgumentException { // 1: verificação de argumento if ( quantidade < 0 ) { throw new IllegalArgumentException(); } // 2: verificação de transbordamento int delta = ( quantidade + conteudo ) - capacidade; if ( delta > 0 ) { throw new TanqueTransbordouException( this, delta ); } // 3: confirmação de conteúdo conteudo += quantidade; } c Copyright °1998-2004, Dr. Italo S. Vega 14-9 Laboratório de Programação Maio de 2004 As duas primeiras verificações podem lançar exceções: uma devido a um valor negativo, outra devido ao transbordamento do tanque de água. Caso nenhuma exceção seja lançada, o conteúdo do tanque é alterado de acordo com a quantidade de água informada. A retirada de água segue um padrão similar, devendo-se considerar, no entanto, a possibilidade de retirada além do conteúdo atual do tanque: hTanque: métodosi+≡ public void retirar( int quantidade ) throws TanqueInsuficienteException, IllegalArgumentException { // 1: verificação de argumento if ( quantidade < 0) { throw new IllegalArgumentException(); } // 2: verificação de falta int delta = quantidade - conteudo; if( delta > 0 ) { throw new TanqueInsuficienteException( this, delta ); } // 3: confirmação de conteúdo conteudo -= quantidade; } Observa-se a utilização de TanqueInsuficienteException. uma nova classe de exceção: Esta classe segue o formato padrão sugerido pela tecnologia Java: hlab/tanque/TanqueInsuficienteException.javai≡ public class TanqueInsuficienteException extends Exception { public Tanque tanqueInsuficiente; public int falta; public TanqueInsuficienteException( Tanque novo, int quantidade ) { super(); tanqueInsuficiente = novo; falta = quantidade; } } c Copyright °1998-2004, Dr. Italo S. Vega 14-10 Laboratório de Programação Maio de 2004 ¨ Captura de Exceções As exceções lançadas durante a execução das instruções de um método podem ser capturadas pelo próprio programa, ou, em último caso, pelo ambiente de execução da tecnologia Java. A captura de uma exceção lançada é feita com o uso do mecanismo try-catch. No bloco try, inserem-se os métodos que, ao serem executados, podem lançar exceções. Caso alguma for lançada, a sua captura se dará em algum catch indicado junto ao mecanismo try. Exemplo 14.6 No caso do tanque, o mecanismo de captura de exceções poderia ser utilizado da seguinte forma: hlab/tanque/Cenario.javai≡ public class Controlador { public Tanque t1; public void criarTanque() { t1 = new Tanque(); } public void aumentar() { try { System.out.println( "Conteudo = " + t1.conteudo ); t1.acrescentar( 4000 ); System.out.println( "Conteudo +4000 = " + t1.conteudo ); t1.acrescentar( 2000 ); // <- (*) } catch( IllegalArgumentException e ) { System.err.println("Quantidade negativa"); } catch( TanqueTransbordouException e ) { // <- (**) System.err.println( "Excesso de " + e.excesso ); } } } Depois de criado um objeto da classe Controlador, ao se enviar a mensagem aumentar(), uma exceção será lançada quando o objeto t1 receber a mensagem marcada com (*). A captura desta exceção é feita no catch marcado com TanqueTransbordouException, na linha (**). ¨ c Copyright °1998-2004, Dr. Italo S. Vega 14-11 Laboratório de Programação Maio de 2004 E XERCÍCIOS 14.1 Escrever um programa que, ao ser executado, obtenha dois números do usuário (via teclado) e mostre a divisão entre eles. Tarefa 14.1.1 Crie o projeto ex14.1. Tarefa 14.1.2 Crie a classe Divisor, contendo o método dividir(a:int, b:int):int. Ao ser executado, este método divide o valor da variável a pelo valor da variável b, retornando o resultado. Tarefa 14.1.3 e b=6? Qual resultado é calculado quando os números de entrada são a=3 Tarefa 14.1.4 Qual exceção é gerada quanto se tenta calcular a divisão por ZERO (ou seja, b=0)? Como pode ser tratada? 14.2 No programa anterior, substitua os tipos das variáveis a e b por double e repita o experimento. Como se diferenciam os resultados? 14.3 P ROBLEMA DE S COTT Considere o seguinte problema, definido para intei- ros positivos: a) Escolha um número. b) Repetidamente, faça: • Se o número for 1, páre. • Se o número for par, divida-o pela metade. • Se o número for ímpar, multiplique-o por 3 e some 1. Ex.: começando pelo número 17, obtém-se a seqüência: h17, 52, 26, 13, 40, 20, 10, 5, 16, 8, 4, 2, 1i Este procedimento sempre pára qualquer entrada? Até agora, este é um problema sem solução: não se conseguiu provar que sempre pára, nem se conseguiu um contra-exemplo. Tarefa 14.3.1 Escreva uma aplicação que faça o seguinte: a) Imprima seu nome. b) Para cada número de 1 até 20, imprima a linha que começa com o número, seguido por dois pontos, seguido pela seqüência de Scott que começa com o número. Separe os números da seqüência por um espaço. Ex.: no caso do número 17: 17: 52 26 13 40 20 10 5 16 8 4 2 1 c Copyright °1998-2004, Dr. Italo S. Vega 14-12