Melhorias no tratamento de Exceptions em Java 7

Post traduzido e adaptado do Forum da Oracle.
Texto original de Manfred Riem.

Saiba como aproveitar melhor o tratamento de exceções, uma de muitas mudanças úteis encontradas no Project Coin, em Java SE 7.

O Project Coin é um dos projetos que foram desenvolvidos para a implementação do Java 7. A ideia do projeto era adicionar pequenas mudanças na linguagem, sem a necessidade de depender de outros projetos do java 7, e sem forçar os desenvolvedores a aderirem à nova sintaxe.

Algumas das melhorias implementadas pelo Project Coin são:

  • String no uso de Switch
  • Melhorias na escrita de literais do tipo Integral (byte, short, int e long)
  • Multiplos Catch em Exception, tratados aqui
  • Definição de tipo melhorada para criação de instâncias genéricas (“diamond”)
  • Try-com-recursos, tratados aqui
  • Invocação simplificada do método varargs

Exceções com Multi-Catch

Exceções com Multi-Catch foram adicionadas ao Java SE 7 para mais fácil e mais conciso a captura de exceções. Veja abaixo como migrar o seu código de manipulação de exceções de um código pré-Java SE 7 para um código Java SE 7.

Exemplo 1:

public class ExampleExceptionHandling
{
   public static void main( String[] args )
   {
   	try {
   		URL url = new URL("http://www.yoursimpledate.server/");
   		BufferedReader reader = new
				BufferedReader(newInputStreamReader(url.openStream()));
   		String line = reader.readLine();
   		SimpleDateFormat format = new SimpleDateFormat("MM/DD/YY");
   		Date date = format.parse(line);
   	}
   	catch(ParseException exception) {
   		// Capturando problemas de conversão de URL.
   	}
   	catch(IOException exception) {
   		// Capturando problemas de I/O.
   	}
   	catch(ParseException exception) {
   		// Capturando problemas de conversão de datas.
   	}
   }
}

No passado, se você quisesse ter a mesma lógica para tratar dois ou três casos de Exception, como por exemplo ParseException e IOException, você tinha que copiar e colar o mesmo código em ambos os blocos Catch. Um programador inexperiente ou preguiçoso poderia pensar que estaria tudo bem fazer o seguinte:

Exemplo 2:

public class ExampleExceptionHandlingLazy
{
   public static void main( String[] args )
   {
   	try {
   		URL url = new URL("http://www.yoursimpledate.server/");
   		BufferedReader reader = new
				BufferedReader(new InputStreamReader(url.openStream()));
   		String line = reader.readLine();
   		SimpleDateFormat format = new SimpleDateFormat("MM/DD/YY");
   		Date date = format.parse(line);
   	}
   	catch(Exception exception) {
   		// Sou um programador inexperiente ou preguiçoso.
   	}
   }
}

O maior problema com o código do Exemplo 2 é que ele poderia apresentar efeitos colaterias indesejados. Qualquer código no bloco try pode lançar uma exceção que seria engolida com uma cláusula catch (Exception). Se uma exceção que não é um ParseException ou IOException for lançada (por exemplo, um SecurityException), o código vai pegá-lo, mas o usuário pode não ficar ciente do que realmente aconteceu. Absorver uma exceção como esta, faz com que o código fique difícil de debugar.

A fim de facilitar o trabalho do programador, Java SE 7 agora inclui uma declaração multi-catch. Isto permite que o programador combine uma clausula catch em um simples bloco de código sem a necessidade de usar uma perigosa cláusula catch-all ou copiar o mesmo código entre vários blocos.

Exemplo 3:

public class ExampleExceptionHandlingNew
{
   public static void main( String[] args )
   {
   	try {
   		URL url = new URL("http://www.yoursimpledate.server/");
   		BufferedReader reader = new BufferedReader(
   			new InputStreamReader(url.openStream()));
   		String line = reader.readLine();
   		SimpleDateFormat format = new SimpleDateFormat("MM/DD/YY");
   		Date date = format.parse(line);
   	}
   	catch(ParseException | IOException exception) {
   		// Trate seus problemas aqui.
   	}
   }
}

O exemplo 3 mostra como combinar adequadamente as duas declarações em um bloco catch. Observe a sintaxe para a cláusula catch (ParseException | IOException). Esta cláusula catch vai pegar tanto ParseException e IOException.

Então, se você quer agora compartilhar o mesmo código de captura de exceção para duas distintas exceções, você pode usar a sintaxe “Pipe”: (ExceptionType | … | ExceptionType variavle).

Relançar Exceptions

Ao fazer o tratamento de exceções, há momentos em que você quer re-lançar uma exceção que esteja tratando. Um programador inexperiente pode pensar em fazer o seguinte código:

Exemplo 4:

public class ExampleExceptionRethrowInvalid
{
   public static void demoRethrow()throws IOException {
   	try {
 		// Forçando uma IOException aqui como um exemplo,
   		// normalmente algum código começa aqui.
		throw new IOException(“Error”);
   	}
   	catch(Exception exception) {
  		/*
   		 * faça alguma tratativa e então relance a exception.
   		 */
   		throw exception;
   	}
   }

   public static void main( String[] args )
   {
   	try {
   		demoRethrow();
  		}
   	catch(IOException exception) {
   	System.err.println(exception.getMessage());
   	}
   }
}

Mas o compilador não irá compilar o código no Exemplo 4. Exemplo 5 mostra uma maneira de lidar com a exceção e, em seguida, “relançar” ele:

Exemplo 5:

public class ExampleExceptionRethrowOld
{
   public static demoRethrow() {
   	try {
   		throw new IOException("Error");
   	}
   	catch(IOException exception) {
   		/*
   	 	 * Do some handling and then rethrow.
   	 	 */
   		throw new RuntimeException(exception);
   	}
   }

   public static void main( String[] args )
   {
   	try {
   		demoRethrow();
   	}
   	catch(RuntimeException exception) {
   	System.err.println(exception.getCause().getMessage());
   	}
   }
}

O problema com o Exemplo 5 é que não está realmente relançando a excepção original. O código está envolvendo a exceção com uma outra exceção, o que significa que o código seguinte precisa estar ciente de que a exceção foi envolvida. Assim, para tornar possível a captura efetiva da exceção original, uma mudança foi necessária em Java SE (mostrado no exemplo 6).

Exemplo 6:

public class ExampleExceptionRethrowSE7
{
   public static demoRethrow() throws IOException {
   	try {
   		throw new IOException("Error");
   	}
   	catch(Exception exception) {
   		/*
   		 * Do some handling and then rethrow.
   		 */
   		throw exception;
   	}
   }

   public static void main( String[] args )
   {
   	try {
   		demoRethrow();
   	}
   	catch(IOException exception) {
   	System.err.println(exception.getMessage());
   	}
   }
}

Try-Com-Recursos

Você deve ter notado que há um problema com o Exemplo 1 (que é por isso que você nunca deve usar o código do exemplo, em um ambiente de produção sem saber o que ele faz). O problema é que não existe nenhuma limpeza dos recursos utilizados no interior do bloco de teste. Exemplo 7 é uma versão atualizada que descreve como, antes do Java SE 7, um programador resolve este problema.

Exemplo 7:

public class ExampleTryResources
{
   public static void main(String[] args)
   {
   	BufferedReader reader = null;

   	try {
   		URL url = new URL("http://www.yoursimpledate.server/");
   		reader = new BufferedReader(new
				InputStreamReader(url.openStream()));
   		String line = reader.readLine();
   		SimpleDateFormat format = new SimpleDateFormat("MM/DD/YY");
   		Date date = format.parse(line);
   	}
   	catch (MalformedURLException exception) {
   		// Capturando problemas de conversão de URL.
   	} catch (IOException exception) {
   		// Capturando problemas de I/O.
   	} catch (ParseException exception) {
   		// Capturando problemas de conversão de datas.
   	} finally {
   		if (reader != null) {
   			try {
   				reader.close();
  				 } catch (IOException ex) {
   				ex.printStackTrace();
   			}
   		}
   	}
   }
}

Observe que tivemos que adicionar um bloco finally que fecha o BufferedReader caso ele tenha sido inicializado. Além disso, note que agora temos a variável leitora fora do bloco try. Isso é um pouco mais de código do que necessitamos se a única coisa que você precisa fazer é fechar o leitor se uma exceção I/O acontecer.

No Java SE 7, isto pode ser feito de uma forma mais concisa e limpa, com omostrado no exemplo 8. A nova sintaxe permite que você declare recursos que fazem parte do bloco try. O que isto significa é que você define os recursos antes do tempo e o tempo de execução fecha automaticamente os recursos (caso já não estejam fechados), após a execução do bloco try.

Exemplo 8:

public static void main (String [] args)
 {
    tentar (BufferedReader reader = new BufferedReader (
   	 new InputStreamReader (
   	 nova URL ("http://www.yoursimpledate.server/"). OpenStream ())))
    {
   	 Seqüência de linha = reader.readLine ();
   	 SimpleDateFormat formato = new SimpleDateFormat ("MM / DD / AA");
   	 Data = Data format.parse (linha);
    } Catch (ParseException | exceção IOException) {
   	 // Lidar com os problemas de I / O.
    }
 }

Note que no exemplo 8, a abertura atual acontece na declaração do try (…). Esteja ciente de que este recurso só funciona em classes que implementam a interface AutoCloseable.

Conclusão

A mudanças na manipulação de exceções do Java SE 7 permite que você não apenas programe de forma mais concisa, como demonstrado nos exemplos de multi-catch, mas eles também permitem que você manipule parcialmente uma exceção e, em seguida, relance, como mostrado nos exemplos. Java SE 7 vai também facilitar a criação de códigos para tratativas de erros mais limpos, assim como vimos nos exemplos de try-com-recursos. Estes recursos, juntamente com outros oferecidos pelo Project Coin, permitem que os desenvolvedores Java sejam mais produtivos e escrevam códigos mais eficientes.