Manipulação de Exceções
Uma exception representa uma condição excepcional
que altera o fluxo normal do programa. Quando um evento deste tipo
ocorre no Java, uma exceção é lançada e a
execução do programa é transferida para o
código responsável por tratar esta exceção.
Este mecanismo de tratamento de exceções oferece
várias vantagens. Através dele é possível
isolar o código responsável pelo tratamento do erro em
blocos separados, deixando o código principal mais limpo.
Também é possível tratar erros similares com um
único bloco de tratamento, eliminando código duplicado.
Existe até a possibilidade de transferir o tratamento de uma
exceção para outros métodos da pilha.
Para indicar a JVM qual código deve ser executado quando
acontece uma exceção, são utilizados blocos
try/catch. Dentro do try é colocado o código que
pode gerar uma exceção, e a seguir são colocados
um ou mais blocos catch, correspondentes às
exceções que podem ocorrer. Os blocos catch devem estar
logo em seguida do bloco try, e recebem o objeto da
exceção como argumento, que sempre será de uma
subclasse de Exception.
Exemplo:
01. try {
02. // Código que pode gerar exceção
03. // Mais uma linha do bloco try
04. }
05. catch(PrimeiraException e1) {
06. // Código para tratar a exceção PrimeiraException
07. // Pode ser utilizados métodos para obter mais informações, tais como e1.printStackTrace();
08. }
09. catch(SegundaException e2) {
10. // Código para tratar a exceção SegundaException
11. }
12. // Código normal começa aqui
As linhas 2 e 3 constituem a região que tentará ser
executada pelo bloco try. As 6 e 7 tratam exceções do
tipo PrimeiraException e a 10 do tipo SegundaException.
A execução inicia-se na linha 2, e se tudo ocorrer bem
até a linha 3, o programa retomará na linha 12.
Porém, se acontecer uma exceção do tipo
PrimeiraException nas linhas 2 ou 3, a execução
irá imediatamente para a linha 6, passará por todo o
bloco catch até a linha 7 e então irá para a linha
12, em que volta a execução normal do código. Note
que se uma exceção ocorrer na linha 2, o resto do bloco
try não será executado. Desta forma, o código que
é dependente de alguma operação de risco
normalmente é agrupado dentro do bloco try. Por exemplo, na
primeira linha você tenta abrir um arquivo, e dentro do mesmo
bloco try tenta ler os dados deste arquivo. Se o Java não
conseguir abrir o arquivo, irá diretamente ao bloco catch,
não tentando ler os dados do arquivo.
Um bloco catch pode tratar de qualquer exceção que
seja da mesma classe ou uma subclasse daquela declarada. As
exceções que não são tratadas em blocos
catch correspondentes a onde foram ocasionadas são passadas para
o método anterior da pilha. Esse propagação ocorre
sucessivamente até que algum método faça o catch
ou até passar do main, chegando a JVM, que pára a
aplicação e mostra a stack trace no output padrão.
É possível fazer um bloco catch para uma
exceção específica e outro para todas as outras
subclasses de uma determinada Exception. Porém, é
necessário que o catch mais específico (subclasse)
apareça antes que o catch mais genérico (superclasse).
Caso contrário o programa não irá compilar,
apresentando uma mensagem de erro dizendo que a exceção
já é tratada anteriormente.
O bloco finally pode aparecer logo após o try/catch. Nele
fica o código que deve sempre ser executado, ocorrendo uma
exceção ou não. Um bom uso é para liberar
recursos que são utilizados no try (fechar um arquivo, a
conexão com banco de dados, etc). O finally é executado
sempre, até mesmo se existir um retorno do método
(return) dentro do try. Ele só não é executado se
a JVM for desligada, através de um System.exit() ou um erro
irreversível. Quando ocorre uma exceção, ele
é executado logo após o catch, caso contrário,
logo após o try.
Tanto a cláusula finally como a catch são opcionais,
porém é necessário ter pelo menos uma delas para
cada bloco try.
A classe Exception é uma subclasse de Throwable, que provê
alguns métodos muito úteis para obter
informações sobre uma exceção, tais como o
printStackTrace(). Outra subclasse de Throwable é a Error.
Errors e RuntimeExceptions são considerados uncheked,
portando o compilador não obriga que exista tratamento para
eles. Erros normalmente representam situações
críticas, que não deveriam acontecer, como por exemplo
acabar a memória da JVM. Já RuntimeExceptions (que
são uma subclasse de Exception) indicam erros de
programação ou condições especiais,
difíceis de serem tratadas. As outras Exceptions costumam
indicar que uma condição necessária para a
execução de um programa não está presente,
como por exemplo um recurso não disponível. Para todas as
subclasse de Throwable é possível utilizar o throw/throws
e o catch.
As exceções que um método lança precisam
ser declaradas através da palavra throws, caso sejam do
tipo checked. Isso não significa que ele
obrigatoriamente irá lançar a exceção,
apenas que isso pode acontecer. Se o método não faz o
tratamento de uma exceção que ele recebe, também
será necessário declará-la para passá-la
adiante pela pilha.
Exemplo:
void metodo() throws UmaException, DuasException {
// código do método
}
Cada método precisa tratar todas as exceções (checked)
através de métodos catch, ou então listar em sua
declaração cada exceção que não
é tratada. Para repassar uma exceção que é
recebida em uma cláusula catch também é
necessário que ela seja declarada no método.
Para criar sua própria exceção basta estender a
classe Exception, da seguinte forma:
class MinhaException extends Exception { }
A nova exceção criada deverá obedecer às
regras de manipulação de exceções, sendo
tratada ou lançada ao próximo método.