Ir para o conteúdo

Tratamento de Excepções

As excepções são um mecanismo fornecido pela plataforma Java para o tratamento e recuperação de situações de erro. São uma forma de notificar o programador de que um erro ocorreu, seja de hardware seja de software, e permitir que sejam tomadas medidas para a correcta recuperação do erro, quando possível, ou para que quaisquer recursos associados ao programa possam ser libertados antes de se terminar a aplicação.

Sempre que ocorrer um erro no programa, provocado por uma situação para a qual existe uma excepção1 definida, a plataforma Java criar um objecto do tipo apropriado, com as informações relevantes a entregar ao programador, e envia esse objecto para o código do programa. Cabe ao programador apanhar esse objecto e tratá-lo de forma adequada.

A criação e lançamento de excepções é feita pelo interpretador de bytecode que, ao encontrar uma situação de erro, tenta encontrar no código que está a executar, alguma definição de código que possa tratar a excepção que ocorreu. Este processo é feito de baixo para cima, em que o interpretador procura código para tratamento da excepção no local onde ela ocorreu, caso não encontre vai percorrendo todos os métodos que foram sendo chamados até encontrar o código que procura ou, caso não encontre, até chegar ao método main. Quando isto acontece, a informação da excepção é mostrada ao utilizador através da consola2

Vantagens

  1. Separação do código de tratamento de erros do restante código do programa;
  2. Permite a fácil propagação do erro. Um método pode tratar todos os erros mesmo que estes ocorram dentro do outros métodos invocados posteriormente dado que as excepções podem ser propagadas através da call stack;
  3. Os vários tipos de erros podem ser agrupados facilmente;
  4. A separação do código de tratamento torna a leitura e manutenção do programa mais simples.

Processo

O mecanismo de excepções oferece 3 tipos principais de excepções: Error, Exception e RuntimeException. Todas as excepções que existem descendem destas 3 classes.

Excepções do tipo Error definem condições de erro que não são esperados ou não devem ser tratados em condições normais. Este tipo de excepção é, normalmente, provocada por erros dos quais não deverá ser possível recuperar ou cuja recuperação está muito dependente da implementação, por exemplo VirtuaMachineError. Este tipo de excepção não é verificada pelo compilador.

Excepções do tipo Exception definem excepções verificadas pelo compilador e que o programador é obrigado a apanhar ou a relançar.

Excepções do tipo RuntimeException são um subtipo de Exception que não são verificadas pelo compilador, não sendo o programador obrigado a tratá-las e que ocorrem durante a execução do programa. Incluem, por exemplo, OutOfBoundsException que ocorre quando se tenta aceder a uma posição inválida num array.

Se uma excepção é verificada pelo compilador então tem de existir, no código, um tratamento explicito da excepção em questão. Por tratamento consideramos que a excepção é apanhada e tratada algures na hierarquia de métodos ou que é relançada ao longo da hierarquia.

Apanhar Excepções

Para apanhar uma excepção basta que coloquemos o código que queremos proteger dentro de um bloco try...cath. Este bloco de código é o que nos permite informar o compilador e o interpretador que pretendemos tratar excepções e quais as excepções que pretendemos tratar. Oferece também o local para a escrita de código de recuperação.

try {
    //código a proteger
    //...
} catch(NomeDaExcepcao ex) {
    //Código de tratamento do erro
}

(Re)Lançar Excepções

Se, em qualquer altura do nosso código, pretendermos lançar uma excepção devemos usar a palavra reservada throws. Esta palavra permite injectar a nossa excepção no mecanismo de excepções, de tal forma que se comportará como uma excepção que tenha sido criada pelo interpretador.

throw new Exception("mensagem de erro que pretendemos enviar...");

Associado ao throw existe o throws. Esta última palavra é colocada na declaração dos métodos e permite indicar que o método lança uma excepção.

private void lerURL() throws Exception {
    //código do método
}

Blocos finally

Em conjunto com os blocos try...catch é possível adicionar blocos finally que permitem a execução de código, independentemente de ter ocorrido uma excepção ou de o código ter executado sem erros.

Este bloco é sempre executado, não interessa se existem instruções return, se existem erros ou não, o código dentro deste bloco será sempre executado no fim de ter sido executado o código dentro do bloco try e o código dentro do bloco catch.

try {
    //código a proteger
    //...
    //Este return é executado se o código não originar excepções
    return;
} catch(NomeDaExcepcao ex) {
    //Código de tratamento do erro
    //Este return é executado se existir uma excepção
    return;
} finally {
    //Este bloco é sempre executado, independentemente de terem sido executados os return anteriores.
    //O interpretador só irá sair deste método depois de executar este bloco.
}

Como não usar

O sistema de excepções, embora poderoso e útil, acarreta algumas penalizações de performance nas aplicações e foi desenhado para permitir o tratamento de erros não para controlo de execução de código nem para substituir as validações que qualquer algoritmo deve conter.

Por exemplo, se acedermos a um índice inválido de um array, o Java vai lançar uma excepção no entanto, não é correcto usar o mecanismo de excepções para tratar este tipo de erros já que um simples if é mais simples, mais rápido e mais legível. Assim, se antes de aceder a uma posição de um array precisarmos saber se a posição existe é mais fácil verificar o tamanho do array.

O mesmo para verificar se um objecto foi instanciado, em vez de tentarmos invocar um método e apanhar a excepção NullPointerException, podemos simplesmente fazer uma comparação com null.

Além deste tipo de validações, o mecanismo de excepções não deve ser usado para controlar o fluxo do programa. O programa deve executar de forma sequencial usando as várias estruturas, ciclos e instruções que a linguagem oferece e não ser controlado com excepções3.

Exemplos

No exemplo seguinte são explorados algumas formas de lidar com excepções

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;

public class Main {

    public static void main(String[] args) {
        try {
            Main m = new Main();
            m.lerURL();
        } catch (Exception ex) {
            //Estamos a apanhar uma excepção porque somos forçados a isso pelo
            //throws do método que invocámos.
            System.out.println("Apanhada excepção propagada");
            System.out.println(ex.getCause().getMessage());
        }
    }

    private void lerURL() throws Exception {
        BufferedReader bf = null;
        String linha;

        try {
            bf = new BufferedReader(new FileReader(new File(
                    new URI("http://wiki.portugal-a-programar.org"))));

            while ((linha = bf.readLine()) != null) {
                //tratar conteúdo lido
                //...
                //Apenas como exemplo, estamos a lançar uma excepção do tipo
                //RuntimeExceptio que, apesar de ser uma excepção e poder ser
                //tratada, não força o programador a criar código expecífico para o
                //efeito
                throw new RuntimeException("Excepção Runtime que não somos obrigados a apanhar");
            }
            //Todas as excepções seguintes são tratadas porque somos forçados a isso
            //Os métodos que invocámos acima lançam excepções que são verificadas pelo
            //compilador. Se o compilador não encontrar o código para tratar a excepção
            //então o nosso código não compila.
        } catch (FileNotFoundException ex) {
            //Mostrar o stack trace na consola de erros (stderr)
            ex.printStackTrace();
        } catch (URISyntaxException ex) {
            //Mostrar algumas mensagens na consola (stdin)
            System.out.println("Ocorreu um erro ao aceder ao URL.");
            System.out.println(ex.getMessage());
        } catch (IOException ex) {
            //Lançar uma nova excepção baseada na excepção que apanhámos.
            throw new Exception("Ocorreu um erro ao ler o conteúdo", ex);
        } finally {
            //Bloco que é sempre executado, quer tenha ocorrido uma excepção
            //quer não tenha.
            if(bf != null) {
                bf.close();
            }
        }
    }
}

  1. Naturalmente, bugs introduzidos pelo programador não são detectados pelo mecanismo de excepções a não ser que esses erros provoquem situações anómalas que afectem alguma parte monitorizada pela plataforma Java. 

  2. Caso o programa não tenha arrancado numa consola o erro não é mostrado ao utilizador e a aplicação termina abruptamente. 

  3. Para os utilizadores que conhecerem a instrução GOTO, presente noutras linguagens, o mecanismo de excepções consegue fornecer ferramentas para se controlar a execução do programa como se estivéssemos a usar o GOTO