Programação Orientada a Objetos: Uma Abordagem com Java/Princípios da programação na linguagem Java/Exceções

Exceções editar

O mecanismo de manipulação de exceções em Java, embora apresente suas particularidades, teve seu projeto inspirado no mecanismo equivalente de C++, que por sua vez foi inspirado em Ada. Uma exceção é um sinal que indica que algum tipo de condição excepcional ocorreu durante a execução do programa. Assim, exceções estão associadas a condições de erro que não tinham como ser verificadas durante a compilação do programa.

As duas atividades associadas à manipulação de uma exceção são:

geração
a sinalização de que uma condição excepcional (por exemplo, um erro) ocorreu, e
captura
a manipulação (tratamento) da situação excepcional, onde as ações necessárias para a recuperação da situação de erro são definidas.

Para cada exceção que pode ocorrer durante a execução do código, um bloco de ações de tratamento (um exception handler) deve ser especificado. O compilador Java verifica e força que toda exceção “não-trivial” tenha um bloco de tratamento associado.

A exceção provê um mecanismo adequado à manipulação de erros síncronos, para situações onde a recuperação do erro é possível. A sinalização da exceção é propagada a partir do bloco de código onde ela ocorreu através de toda a pilha de invocações de métodos, até que a exceção seja capturada por um bloco manipulador de exceção. Eventualmente, se tal bloco não existir em nenhum ponto da pilha de invocações de métodos, a sinalização da exceção atinge o método main(), fazendo com que o interpretador Java apresente uma mensagem de erro e aborte sua execução.

Tratamento de exceções editar

A captura e o tratamento de exceções em Java se dá através da especificação de blocos try, catch e finally, definidos através destas mesmas palavras reservadas da linguagem. A estruturação desses blocos obedece à seguinte sintaxe:

try { 
    // bloco normal
} 
catch (XException ex) { 
    // erros x
} 
catch (YException ey) { 
    // erros y 
} 
finally { 
    // bloco final
}

sendo:

bloco normal
código que inclui comandos/invocações de métodos que podem gerar uma situação de exceção, sem nenhum tratamento de erro. É o chamado cenário cor-de-rosa, que supõe a execução completa sem ocorrência de nenhuma situação de erro;
erros x
bloco de tratamento associado à condição de exceção XException ou a qualquer uma de suas subclasses, identificada aqui pelo objeto com referência ex;
erros y
bloco de tratamento para a situação de exceção YException ou a qualquer uma de suas subclasses;
bloco final
código que sempre será executado após o bloco try, independentemente de sua conclusão ter ocorrido normalmente ou ter sido interrompida por alguma condição de exceção.

XException e YException deveriam ser substituídos pelo nome do tipo de exceção. Os blocos não podem ser separados por outros comandos — um erro de sintaxe seria detectado pelo compilador Java neste caso. Cada bloco try pode ser seguido por zero ou mais blocos catch; dois blocos catch com o mesmo corpo podem ser combinados usando o formato multicatch, como em

catch (XException | YException exc) {
   //...
}

O bloco finally, quando presente, é sempre executado. Em geral, ele inclui comandos que liberam recursos que eventualmente possam ter sido alocados durante o processamento do bloco try e que podem ser liberados, independentemente de a execução ter encerrado com sucesso ou ter sido interrompida por uma condição de exceção. A presença desse bloco é opcional.

Alguns exemplos de exceções já definidas no pacote java.lang incluem:

ArithmeticException
indica situações de erros em processamento aritmético, tal como uma divisão inteira por 0. A divisão de um valor real por 0 não gera uma exceção (o resultado é o valor infinito);
NumberFormatException
indica que tentou-se a conversão de uma string para um formato numérico, mas seu conteúdo não representava adequadamente um número para aquele formato. É uma subclasse de IllegalArgumentException;
IndexOutOfBounds
indica a tentativa de acesso a um elemento de um agregado aquém ou além dos limites válidos. É a superclasse de ArrayIndexOutOfBoundsException, para arranjos, e de StringIndexOutOfBounds, para strings;
NullPointerException
indica que a aplicação tentou usar uma referência a um objeto que não foi ainda definida;
ClassNotFoundException
indica que a máquina virtual Java tentou carregar uma classe mas não foi possível encontrá-la durante a execução da aplicação.

Além disso, outros pacotes especificam suas exceções, referentes às suas funcionalidades. Por exemplo, no pacote java.io define-se IOException, que indica a ocorrência de algum tipo de erro em operações de entrada e saída. É a superclasse para condições de exceção mais específicas desse pacote, tais como EOFException (fim de arquivo ou stream), FileNotFoundException (arquivo especificado não foi encontrado) e InterruptedIOException (operação de entrada ou saída foi interrompida).

Uma exceção contém pelo menos uma string que a descreve, que pode ser obtida pela aplicação do método getMessage(), mas pode eventualmente conter outras informações. Outra informação que pode sempre ser obtida de uma exceção é a sequência de métodos no momento da exceção, obtenível a partir do método printStackTrace(). Já a exceção InterruptedIOException, do pacote java.io, inclui um atributo público do tipo inteiro, bytesTransferred, para indicar quantos bytes foram transferidos antes da interrupção da operação ocorrer.

Como exceções fazem parte de uma hierarquia de classes, exceções mais genéricas (mais próximas do topo da hierarquia) englobam aquelas que são mais específicas. Assim, a forma mais genérica de um bloco try-catch é

try { 
    //... 
} 
catch (Exception e) { 
    // ... 
}

pois todas as exceções são derivadas de Exception, definida em java.lang. Se dois blocos catch especificam exceções em um mesmo ramo da hierarquia, a especificação do tratamento da exceção derivada deve preceder aquela da mais genérica; caso contrário, a mais específica (derivada) nunca será capturada.

Erros e exceções de runtime editar

Exceções consistem de um caso particular de um objeto da classe Throwable, definida em java.lang. Apenas objetos dessa classe ou de suas classes derivadas podem ser gerados, propagados e capturados através do mecanismo de tratamento de exceções. Além de Exception, outra classe derivada de Throwable é a classe Error, que é a raiz das classes que indicam situações que a aplicação não tem como ou não deve tentar tratar. Usualmente indica situações anormais, que não deveriam ocorrer.

Entre os erros definidos em Java, no pacote java.lang, estão StackOverflowError e OutOfMemoryError. São situações onde não é possível uma correção a partir de um tratamento realizado pelo próprio programa que está executando.

Há também exceções que não precisam ser explicitamente capturadas e tratadas. São aquelas derivadas de RuntimeException, uma classe derivada diretamente de Exception. São exceções que podem ser geradas durante a operação normal de uma aplicação para as quais o compilador Java não irá exigir que o programador proceda a algum tratamento ou que propague a exceção. Entre essas incluem-se ArithmeticException, IllegalArgumentException, IndexOutOfBoundsException e NullPointerException.

Propagação de exceções editar

Embora toda exceção que não seja derivada de RuntimeException deva ser tratada, nem sempre é possível tratar uma exceção no mesmo escopo do método cuja invocação gerou a exceção. Nessas situações, é possível propagar a exceção para um nível acima na pilha de invocações. Para tanto, o método que está deixando de capturar e tratar a exceção faz uso da cláusula throws na sua declaração:

void métodoQueNãoTrataExceção() throws Exception { 
    invoca.métodoQuePodeGerarExceção(); 
}

Nesse caso, métodoQueNãoTrataExceção() reconhece que em seu corpo há a possibilidade de haver a geração de uma exceção, mas não se preocupa em realizar o tratamento dessa exceção em seu escopo. Ao contrário, ele repassa essa responsabilidade para o método anterior na pilha de chamadas. Eventualmente, também o outro método pode repassar a exceção adiante. Porém, pelo menos no método main() as exceções deverão ser tratadas ou o programa pode ter sua interpretação interrompida.

Definição e geração de exceções editar

Exceções são classes. Assim, é possível que uma aplicação defina suas próprias exceções por meio do mecanismo de definição de classes. Por exemplo, considere que fosse importante para uma aplicação diferenciar a condição de divisão por zero de outras condições de exceções aritméticas. Neste caso, uma classe DivideByZeroException poderia ser criada:

public class DivideByZeroException extends ArithmeticException { 
    public DivideByZeroException() { 
        super("O denominador na divisão inteira tem valor zero"); 
    }
}

Neste caso, o argumento para o construtor da superclasse especifica a mensagem que seria impressa quando o método getMessage() fosse invocado para essa exceção. Essa é a mesma mensagem que é apresentada para um RuntimeException que não é capturado e tratado.

Para gerar uma condição de exceção durante a execução de um método, um objeto dessa classe deve ser criado e, pelo comando throw, propagado para os métodos anteriores na pilha de execução:

public double calculaDivisao (double numerador, int denominador) throws DivideByZeroException { 
    if (denominador == 0) {
        throw new DivideByZeroException();
    }
    return numerador / denominador;
}

O mesmo comando throw pode ser utilizado para repassar uma exceção após sua captura — por exemplo, após um tratamento parcial da condição de exceção:

public void usaDivisao() throws DivideByZeroException { 
    //... 
    try { 
        d = calculaDivisao(x, y); 
    } 
    catch (DivideByZeroException dbze) { 
        // ... tratamento parcial 
        throw dbze; 
    } 
    ... 
}

Nesse caso, a informação associada à exceção (como o seu ponto de origem, registrado internamente no atributo do objeto que contém a pilha de invocações) é preservada. Caso fosse desejado mudar essa informação, uma nova exceção poderia ser gerada ou, caso a exceção seja a mesma, o método fillInStackTrace() poderia ser utilizado, como em

throw dbze.fillInStackTrace();