POO-F-11-Autorreferência this | POO-F-13-Tratamento de exceções |
Na plataforma de programação Java, assim como em outras linguagens OO modernas, a política de tratamento de problemas ocorridos durante a execução de um programa utiliza a estratégia de criação, lançamento e tratamento de exceções e erros.
Então, é essencial realizar a distinção entre:
- Exceções, que são problemas de severidade variável, tratáveis pelo programador; e
- Erros, problemas de severidade alta que não tratáveis pelo programador.
Exceções e Erros
Uma exceção (exception) é um objeto especial, de uma classe específica pertencente a hierarquia da API Java, a classe Exception do pacote java.lang, que é destinada a sinalização de situações que podem ser tratadas pelo programador [4][7].
Isto significa que uma exceção indica a ocorrência de uma anormalidade no código, a qual poderia ser solucionada pelo próprio programa, por meio do uso de um dado alternativo, de uma outra estratégia de cálculo, pela obtenção de um novo dado junto ao usuário do programa, ou eventualmente outra ação considerada apropriada.
Um erro (error) é também um objeto especial, de outra classe específica pertencente a hierarquia Java que é java.lang.Error, mas reservada para indicar situações severas ou complexas, que não deveriam ser tratadas pelo programador [4][7].
Assim, um erro ocorrido durante a execução do código muito provavelmente não pode ser solucionado pelo próprio programa, pois pode ter origem num defeito do hardware, num problema junto ao sistema operacional ou ainda na própria máquina virtual Java (JVM).
Enquanto uma exceção pode estar relacionada a um problema previsível, que pode ser resolvido com algum cuidado extra no projeto do sistema; um erro é uma situação rara, imprevista, severa e crítica, cuja prudência recomenda apenas informar o ocorrido e parar imediatamente o programa.
As exceções (exceptions) geralmente estão relacionadas a situações que, embora anormais, são previsíveis no projeto como o recebimento de argumentos inválidos, entradas de dados inconsistentes, recursos (arquivos ou urls) não encontrados ou indisponíveis, assim como o time-out das operações executadas.
Já os erros (errors) estão sempre associados a situações anormais, atípicas e não previsíveis pelo programador, tais como erros internos da JVM, falhas oriundas do mal funcionamento do hardware (processador e memória), defeitos (bugs) no sistema operacional, assim inconsistências decorrentes de ataques ao sistema.
Por conta disso, o termo tratamento de exceções é visto com frequência, sendo uma necessidade comum da programação. Já o tratamento de erros é raro e, por esta razão, não será tratado neste material.
A classe Throwable
Na hierarquia de classes da API Java existe a classe Throwable, do pacote java.lang, que é a superclasse de todos os erros e exceções da linguagem Java, como mostra a figura que segue. Apenas objetos deste tipo ou de suas subclasses são lançados pela JVM e podem ser lançados pelo uso da diretiva throw.
De maneira análoga, apenas objetos deste tipo, ou de suas subclasses, podem ser apanhados por cláusulas catch, que fazem parte da diretiva try.
A diretiva try e a cláusula catch, ou simplesmente try/catch, compõem uma das formas mais comuns do tratamento de exceções. Já a diretiva throw serve para que um programa sinalize a ocorrência de uma situação anormal por meio de do lançamento de uma exceção [7].
Funcionamento das exceções
As exceções são criadas nos locais onde as situações problemáticas são encontradas no código do programa. Assim, como qualquer objeto, as exceções devem ser criadas e depois lançadas para o contexto superior (trecho de código que invocou a execução do trecho específico onde o problema foi encontrado) [7]. A figura que segue ilustra o funcionamento das exceções.
Isto significa que as exceções podem ser monitoradas e tratadas em locais diferentes do código (em diferentes contextos superiores), ou seja, a ocorrência anormal pode ser tratada em outro local específico do código do programa, melhorando sua organização.
A ideia central é que quando um erro ou problema ocorre, é criada e lançada uma exceção (to throw an exception). A exceção é sempre lançada para um contexto superior, isto é, para quem acionou o trecho de código problemático, separando o problema de seu tratamento. Para tratar uma exceção é necessário apanhá-la (to catch an exception). Exceções apanhadas podem ser relançadas para outro contexto superior. Caso o contexto mais alto alcance o nível da JVM, nível onde o programa foi acionado, a execução é interrompida imediatamente e sinalizada para o usuário [7].
A grande vantagem do uso do lançamento e tratamento de exceções é a possibilidade de colocar o tratamento do problema em contextos (ou escopos) diferentes. Assim, seu emprego evita o uso de códigos de erro genéricos; ou de variáveis globais para sinalização, identificação e tratamento de erros. A experiência já mostrou que o tratamento de exceções é uma estratégia muito flexível para lidar com os problemas do código de qualquer programa [4][7].
Classes de exceção
Existem muitas classes de exceção no Java, mas a classe básica é java.lang.Exception, que indica um problema de natureza geral. Todas as demais classes de exceção são derivadas de Exception, por isso tem sempre o sufixo Exception em seus nomes, como nos exemplos que seguem, onde os nomes dos tipos das exceções também indicam seu propósito específico:
- ArrayIndexOutOfBoundsException, sinaliza o uso de índices inválidos em arrays;
- IOException, indica problemas em operações de entrada e saída;
- NullPointerException, ocorre quando uma referência nula (null) é usada ao invés de um objeto válido;
- NumberFormatException, aponta problemas de representação (formato) de valores numéricos;
- RuntimeException, serve para indicar erros gerais durante a execução de um trecho do código do programa.
A exceção java.lang.RuntimeException é bastante importante, pois toda a família derivada desta exceção tem tratamento opcional, ou seja, são exceções consideradas não monitoradas (unchecked exceptions).
Lançamento de exceções com cláusula throw
Para lançar uma exceção basta instanciar um objeto de exceção to tipo desejado e lançá-lo, por exemplo:
throw new Exception();
Usualmente as classes de exceção aceitam mensagens como argumento de seus construtores, como:
throw new NumberFormatException(“Valor inválido”);
Tais mensagens podem incluir detalhes do problema ocorrido, que podem ser usados em na parte do programa destinada ao tratamento da exceção.
Assim, na ocorrência de um problema na execução do código, sugere-se fortemente o lançamento de uma exceção de tipo adequado, que pode ser escolhida entre as muitas existentes na API Java.
Tipos de exceções
Existem dois tipos de exceções: as exceções não monitoradas (unchecked exceptions) e as exceções monitoradas (checked exceptions).
As exceções não monitoradas (unchecked exceptions) são aquelas em que o tratamento com try/catch não é obrigatório. Estas exceções são implicitamente lançadas por diversos métodos presentes na API Java (o compilador não faz menção a sua ocorrência), assim como por métodos que podem ser criados pelos programadores. De alguma forma, sinalizam problemas eventuais, considerados de menor severidade.
Já as exceções monitoradas (checked exceptions) são aquelas em que o tratamento com try/catch é obrigatório, pois sua severidade é maior, requerendo tratamento. Estas exceções são explicitamente lançadas por muitos métodos presentes na API Java, de modo que o compilador indica como erro a ausência de tratamento próprio. Como antes, o programador pode criar métodos que lancem este tipo de exceção.
Independentemente de serem monitoradas ou não monitoradas, todas as exceções do Java, quando ocorrem, são lançadas para o contexto superior e, se alcançam a JVM, provocam a interrupção do programa. Apenas o tratamento é considerado opcional no caso de exceções não monitoradas (unchecked exceptions).
Lançamento de exceções não monitoradas
O código dos programas Java tipicamente se encontra distribuído nos métodos que compõem as classes dos objetos usados. Assim, quando o código de um método produz ou (explicitamente) lança exceções não monitoradas, não é obrigatório o tratamento local nem a indicação explícita de seu lançamento, como no método converteESoma(String, String), que segue, que recebe dois argumentos do tipo String, convertendo-se em números reais e retornando sua soma.
public double converteESoma(String s1, String s2) {
double d1 = Double.parseDouble(s1);
double d2 = Double.parseDouble(s2);
return d1 + d2;
}
Dois tipos de exceções podem ser lançadas por este método:
- NullPointerException, quando o argumento s1, ou o argumento s2 é null, isto é, não indica um objeto válido;
- NumberFormatException, quando o argumento s1, ou o argumento s2 é um objeto válido do tipo String, mas que não tem o formato adequado de um número em ponto flutuante ou inteiro.
A primeira exceção ocorre se fizermos:
converteESoma(null, "13");
converteESoma("-7.56", null);
converteESoma(null, null);
Já a segunda exceção ocorre quando algum dos argumentos não é uma String com um número válido:
converteESoma("", "64");
converteESoma("19.85", "dois");
converteESoma("0,23534", "3/4");
Observe que no código deste método, o lançamento destes dois tipos de exceção é implícito, ou seja, sem o uso da diretiva throw. Além disso, o código do método não trata a ocorrência destas exceções, nem faz qualquer indicação de seu lançamento.
Isto é possível porque as exceções lançadas são do tipo não monitorado (unchecked exceptions), que não exigem o tratamento da exceção ou indicação explícita de seu lançamento.
Nestas situações o programador deve avaliar e antever as possibilidades de ocorrências de exceções em métodos como este.
Uso da cláusula throws
A cláusula throws pode ser aplicada em um método para indicar explicitamente as exceções possivelmente lançadas em seu código, desobrigando seu tratamento local, quaisquer sejam seus tipos (checked ou unchecked). O mesmo método converteESoma(String, String) poderia ser modificado como:
public double converteESoma(String s1, String s2)
throws NumberFormatException, NullPointerException {
double d1 = Double.parseDouble(s1);
double d2 = Double.parseDouble(s2);
return d1 + d2;
}
A cláusula throws, que segue a lista de argumentos do método, lista as exceções, monitoradas ou não monitoradas, que podem ser lançadas pelo método. Isto auxilia o programador a prever os problemas decorrentes do uso do método, além de desonerar o tratamento das exceções listadas no código do próprio método.
No caso de exceções não monitoradas (unchecked exceptions), sua indicação com a cláusula throws não é obrigatória, mas torna explícito seu lançamento possível, auxiliando o programador.
No caso de exceções monitoradas (checked exceptions), sua indicação com a cláusula throws é obrigatória quando não são tratadas no código do método.
Lançamento de exceções monitoradas
No código dos métodos das classes de um programa também podem ocorrer exceções monitoradas (checked exceptions), isto é, situações anormais que devem ser tratadas pelo programador. Exemplos deste tipo de exceção são as operações de entrada e saída envolvendo o sistema de arquivos, ou de escrita e leitura em dispositivos de rede.
Se o código de um método produz exceções monitoradas, ou tais exceções são tratadas localmente, ou são explicitamente indicadas com o uso da cláusula throws.
No exemplo que segue, o método contaLinhasArquivo(String) recebe um argumento do tipo String contendo o nome do arquivo, abrindo-o e percorrendo-o para a contagem de suas linhas, fechando-o e retornando o resultado.
public int contaLinhasArquivo(String arquivo)
throws IOException {
int linhas = 0;
BufferedReader br = new BufferedReader(
new FileReader(arquivo));
:
return linhas;
}
Embora apenas um fragmento deste método seja exibido para simplificação do exemplo, podemos observar que a operação de abertura do arquivo pode lançar uma exceção do tipo IOException. Tal exceção é indicada na lista da cláusula throws do método, explicitando sua ocorrência e adiando seu tratamento para um contexto superior.
Se o argumento arquivo, do tipo String, for null, poderá ser lançada uma exceção NullPointerException, mas que é do tipo não monitorada, não requerendo indicação explícita de lançamento, tão pouco tratamento.
Se for preferível o tratamento local, surge uma diretiva try/catch, envolvendo o trecho onde uma exceção a ser tratada pode ocorrer, como esquematizado abaixo.
public int contaLinhasArquivo(String arquivo) {
int linhas = 0;
try {
BufferedReader br = new BufferedReader(
new FileReader(arquivo));
:
} catch (IOException ioe) {
// tratamento de erros
}
return linhas;
}
Com o tratamento local, não se emprega a cláusula throws para a exceção tratada.
Na próxima lição veremos como efetuar o tratamento de exceções por meio da diretiva try e suas cláusulas catch e finally.
POO-F-11-Autorreferência this | POO-F-13-Tratamento de exceções |
Referências Bibliográficas
[1] JAMSA, K.; KLANDER, L.. Programando em C/C++: a bíblia. São Paulo: Makron Books, 1999.
[2] PAGE_JONES, M.. Fundamentos do Desenho Orientado a Objeto com UML. São Paulo: Makron Books, 2001.
[3] SOMMERVILLE, I.. Software Engineering. 6th. Ed. Harlow: Pearson, 2001.
[4] DEITEL, H.M.; DEITEL, P.J.. Java: como programar. 6a. Ed. São Paulo: Pearson Prentice-Hall, 2005.
[5] SAVITCH, W.. C++ Absoluto. São Paulo: Pearson Addison-Wesley, 2004.
[6] JANDL JR., P. Introdução ao C++. São Paulo: Futura, 2003.
[7] JANDL JR., P.. Java - guia do programador. 3a. ed. São Paulo: Novatec, 2015.
[8] RUMBAUGH, J.; BLAHA, M.; PREMERLANI, W.; EDDY, F.; LORENSEN, W.. Object-oriented modeling and design. Englewoods Cliffs: Prentice-Hall, 1991.
[9] STROUSTRUP, B.. The C++ Programming Language. 3rd Ed. Reading: Addison-Wesley, 1997.
[10] LANGSAM, Y.; AUGENSTEIN, M. J.; TENENBAUM, A. M.. Data structures using C and C++. 2nd Ed. Upper Saddle River: Prentice-Hall, 1996.
[11] WATSON, K.; NAGEL, C.; PEDERSEN, J.H.; REID, J.D.; SKINNER, M.; WHITE, E.. Beginning Microsoft Visual C# 2008. Indianapolis: Wiley Publishing, 2008.