As exceções são usadas na linguagem Java para assinalar que algo não correu como esperado.
São classes que herdam de java.lang.Exception e cujos objetos podem ser atirados (throw) e apanhados (caught).
As exceções que herdam de java.lang.RuntimeException (RTE) são chamadas exceções não verificadas (unchecked exceptions). Neste caso, o compilador não obriga o programador a declarar se apanha ou se atira. Por este motivo, qualquer linha de código pode atirar uma exceção destas. A mais conhecida é a NullPointerException (NPE).
As exceções que herdam de java.lang.Exception são chamadas exceções verificadas (checked exceptions), no sentido, em que a sua utilização é verificada pelo compilador. Nestes casos é preciso explicitar se se apanha a exceção (catch) ou se se lança (throws). Normalmente, se não se vai tentar recuperar a exceção, pode simplesmente dizer que se atira. É preferível atirar do que fazer um falso tratamento de exceção.
Podemos ter as seguintes abordagens em relação às exceções:
A abordagem 'apanhar-e-ignorar' é claramente errada porque perde informação e torna muito mais difícil diagnosticar e resolver problemas.
Os outputs seguintes foram produzidos por uma aplicação que usa sockets para comunicação, e ilustram diversas situações.
Na situação abaixo, o servidor tenta criar o socket com um porto fora do intervalo [0;65535].
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:293)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.lang.IllegalArgumentException: Port value out of range: 65536
at java.net.ServerSocket.<init>(ServerSocket.java:232)
at java.net.ServerSocket.<init>(ServerSocket.java:128)
at example.SocketServer.main(SocketServer.java:24)
... 6 more
Na situação abaixo, o cliente tenta ligar ao servidor por meio dum porto incorreto e não consegue.
java.lang.reflect.InvocationTargetException
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:497)
at org.codehaus.mojo.exec.ExecJavaMojo$1.run(ExecJavaMojo.java:293)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.net.ConnectException: Connection refused: connect
at java.net.DualStackPlainSocketImpl.connect0(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:79)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:345)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:206)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:188)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:172)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
at java.net.Socket.connect(Socket.java:589)
at java.net.Socket.connect(Socket.java:538)
at java.net.Socket.<init>(Socket.java:434)
at java.net.Socket.<init>(Socket.java:211)
at example.SocketClient.main(SocketClient.java:32)
... 6 more
As linhas mais importantes dos outputs são as começadas por Caused by. É aí que se podem encontrar as mensagens das exceções. Dado que uma exceção pode ter outra exceção aninhada (cause) pode ser necessário consultar várias linhas para perceber o que causou a exceção de topo.
As linhas começadas por at indicam o contexto de execução. Observando estas linhas é possível ver o conteúdo da pilha de execução do programa, que diz que parte do código estava a chamar que outra parte.
// ANTI-padrão 'apanhar-e-ignorar!'
// Ignorar a exceção sem dizer nada a ninguém.
// Evitar!
try {
doSomething();
} catch(Exception e) {
}
...
// ANTI-padrão 'apanhar-imprimir-e-ignorar!'
// Imprimir o stack trace, não resolve nada.
// O programa vai "rebentar" mais à frente, onde será mais difícil perceber porquê.
// Evitar também!
try {
doSomething();
} catch(Exception e) {
e.printStackTrace();
}
Ignorar a exceção torna muito mais difícil detetar e corrigir erros no código!
Vamos então ilustrar alguns bons exemplos:
// padrão 'deixar-passar'
// Se a exceção não vai ser tratada, mais vale lançá-la (throws)
public static void main(String[] args) throws Exception {
doSomething();
}
...
// padrão 'apanhar-imprimir-e-atirar'
// Registar onde foi apanhada a exceção, mas voltar a atirá-la para que seja tratada depois
try {
doSomething();
} catch(MyException e) {
System.err.println("Caught exception when doing something: " + e);
System.err.println("Rethrowing"):
throw e;
}
// padrão 'apanhar-embrulhar-e-atirar'
// Apanhar exceção da camada inferior
// Envolver com mais contexto (novo tipo, mensagem de erro melhor)
// Atirar
try {
doSomething();
} catch(MyLowerLevelException e) {
System.err.println("Caught exception when doing something: " + e);
System.err.println("Wrapping and throwing, adding meaningful message")
throw new MyHigherLevelException("Failed to do something.", e);
}
Em resumo, um bom tratamento de exceções é muito importante em Sistemas Distribuídos.
As melhores estratégias a seguir são: 'deixar-passar', 'apanhar-*-atirar' e 'apanhar-e-recuperar' quando possível.
© Docentes de Sistemas Distribuídos,
Dep. Eng. Informática,
Técnico Lisboa