Java 7: Try-with-resources ou Automatic Resource Management

Com o build 105 do Java 7, está disponível a sintaxe para fechar automaticamente arquivos ou outros recursos que precisem de tratamento especial para serem fechados.
Baixem o build em : http://download.java.net/jdk7/
Vejam o blog do Joseph Darcy, http://blogs.sun.com/darcy/
A spec está em: http://blogs.sun.com/darcy/entry/project_coin_updated_arm_spec
O Thingol postou um exemplo simples desse recurso.
http://thingol-guj.blogspot.com/2010/08/java-7-automatic-resource-management.html

Dá para fazer um exemplo mais elaborado.
Em resumo: o equivalente do seguinte recurso em C#, que chama automaticamente o “Dispose” (equivalente do método Java “close”):

using (StreamWriter sw = new StreamWriter ("teste.txt")) {
    sw.WriteLine ("teste");
}

é, em Java 7,

try (PrintWriter pw = new PrintWriter ("teste.txt")) {
    pw.println ("teste");
}

Para economizar palavras-chave, foi reusada a palavra-chave “try”. Uma coisa que é um pouco diferente no Java em relação ao C# é que esse try continua a ter o “catch” e o “finally”, então você pode ao mesmo tempo fechar o arquivo e tratar as exceções, tudo dentrodo mesmo try/catch/finally.
Uma coisa meio chata é que a classe deve implementar AutoCloseable, mas as classes do JDBC não implementam AutoCloseable apesar de o Darcy ter insistido bastante com o time do JDBC. Parece que a spec do JDBC para o Java 7 está fechada há tempos…

Aposto que vai confundir muita gente esse try com catch e finally opcionais!

Só não vi muita vantagem nisso… Não consegui entender o porque de eu não querer tratar a exception na hora de fechar o arquivo (ou coisa do tipo).

show de bola.

Vou copiar o exemplo do Thingol, e então postar o exemplo equivalente em Java 6.

        // Java 7
        // Exemplo um pouco mais complexo, que
        // envolve 2 arquivos
        try (BufferedOutputStream bos = new BufferedOutputStream (
                new FileOutputStream ("copy.of.file"));
             BufferedInputStream bis = new BufferedInputStream (
                new FileInputStream ("original.file"))) {
            byte[] buffer = new byte[10240];
            for (int nBytes = bis.read (buffer); nBytes > 0;
                    nBytes = bis.read (buffer)) {
                bos.write (buffer, 0, nBytes); 
            }
        } catch (final FileNotFoundException | IOException ex) {
            ex.printStackTrace();
        }
		// Java 6
		// Exemplo um pouco mais complexo, que
		// envolve 2 arquivos
		BufferedOutputStream bos = null;
		BufferedInputStream bis = null;
		try {
			BufferedOutputStream bos = new BufferedOutputStream (
				new FileOutputStream ("copy.of.file"));
			BufferedInputStream bis = new BufferedInputStream (
				new FileInputStream ("original.file"));
			byte[] buffer = new byte[10240];
			for (int nBytes = bis.read (buffer); nBytes > 0;
					nBytes = bis.read (buffer)) {
				bos.write (buffer, 0, nBytes); 
			}
		} catch (FileNotFoundException ex) {
			ex.printStackTrace();
		} catch (IOException ex) {
			ex.printStackTrace();
		} finally {
			if (bis != null) try { bis.close(); } catch (IOException ex) {}
			if (bos != null) try { bos.close(); } catch (IOException ex) {}
		}

[quote=Andre Brito]Aposto que vai confundir muita gente esse try com catch e finally opcionais!

Só não vi muita vantagem nisso… Não consegui entender o porque de eu não querer tratar a exception na hora de fechar o arquivo (ou coisa do tipo).
[/quote]

A idéia é que o io só existe dentro do scopo, ou seja dentro do try. Isso evita erros gerados quando não se desalocam recursos, como memory leaks.
Idéia muito boa essa interface AutoCloseable.

[quote=entanglement]Vou copiar o exemplo do Thingol, e então postar o exemplo equivalente em Java 6.

        // Java 7
        // Exemplo um pouco mais complexo, que
        // envolve 2 arquivos
        try (BufferedOutputStream bos = new BufferedOutputStream (
                new FileOutputStream ("copy.of.file"));
             BufferedInputStream bis = new BufferedInputStream (
                new FileInputStream ("original.file"))) {
            byte[] buffer = new byte[10240];
            for (int nBytes = bis.read (buffer); nBytes > 0;
                    nBytes = bis.read (buffer)) {
                bos.write (buffer, 0, nBytes); 
            }
        } catch (final FileNotFoundException | IOException ex) {
            ex.printStackTrace();
        }

// Java 6 // Exemplo um pouco mais complexo, que // envolve 2 arquivos BufferedOutputStream bos = null; BufferedInputStream bis = null; try { BufferedOutputStream bos = new BufferedOutputStream ( new FileOutputStream ("copy.of.file")); BufferedInputStream bis = new BufferedInputStream ( new FileInputStream ("original.file")); byte[] buffer = new byte[10240]; for (int nBytes = bis.read (buffer); nBytes > 0; nBytes = bis.read (buffer)) { bos.write (buffer, 0, nBytes); } } catch (FileNotFoundException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } finally { if (bis != null) try { bis.close(); } catch (IOException ex) {} if (bos != null) try { bos.close(); } catch (IOException ex) {} } [/quote]

Estou baixando a nova release.

Note várias coisas.

a) O try-with-resources sempre fecha direitinho os arquivos (não preciso usar esse truque de inicializar a variável com null, usar um finally, e então sufocar a eventual exceção gerada pelo close com esses try/catchs dentro do finally.
b) Diferentemente do C# (onde só posso fazer uma declaração dentro do using), posso pôr várias declarações dentro do try(). Elas indicarão também a ordem de encerramento ( se você abrir os arquivos em uma determinada ordem, eles serão fechados em ordem inversa).
c) Você pode tratar várias exceções em um mesmo catch, se elas forem compatíveis entre si. Basta usar “final” e “|” para separar os nomes das classes.

Como foi apontado pelo Thingol, infelizmente as classes e interfaces de java.sql.* não implementam AutoCloseable*, portanto não posso usar o mesmo truque com um ResultSet ou uma Connection. Provavelmente alguém irá criar um “wrapper” para essas interfaces que implemente AutoCloseable, só para poder usar o “try-with-resources”.

  • Provavelmente porque, devido ao fato de o JDBC envolver cooperação dos fornecedores de drivers, ficaria um pouco inviável, já que a spec foi fechada há tempos, reabrir a spec e forçar os fornecedores a atualizarem suas implementações. Isso é diferente, por exemplo, do java.io.*, cujo único fornecedor é a Sun/Oracle nesse caso.

Boa, mas a interface podia se chamar simplesmente Closable.

Isso tudo são syntax-sugars ou realmente ouve alguma mudança na estrutura dos programas em Java?

Achei muito legal, só não gostei da sintaxe.

[]´s

Não concordo. Pq o fechamento é automático e a interface tem que denotar exatamente, ao meu ver, o comportamento de quem a implementa.
Closable fica muito genérico. Afinal, as classes já são Closable.

[]´s

[quote=Marky.Vasconcelos]Boa, mas a interface podia se chamar simplesmente Closable.

Isso tudo são syntax-sugars ou realmente houve alguma mudança na estrutura dos programas em Java?[/quote]

Já existe uma interface Closeable:

http://download-llnw.oracle.com/javase/6/docs/api/java/io/Closeable.html

A Closeable, no Java 7, irá estender Autocloseable.

O que o Darcy não conseguiu é que as classes e interfaces do JDBC que tèm um método close() implementassem Autocloseable. (A propósito, elas também não implementam java.io.Closeable, principalmente porque a interface java.io.Closeable tem um método close que lança a exceção IOException, e o close do JDBC lança SQLException.

O close do Autocloseable irã lançar simplesmente Exception, o que é permitido pelo Java (você, ao estender uma interface, pode redefinir o método lançando uma exceção mais específica - por exemplo, se Autocloseable lança Exception, java.io.Closeable pode lançar IOException, e java.sql.Closeable (uma interface que não existe, aliás) poderia lançar SQLException, sem problemas.

Voltando à vaca fria: é claro que é um syntax sugar. Mas que economiza um bocado de esforço e ajuda a tornar seus programas mais corretos, ajuda.

PS: Thingol, seu blog é muito preto e cinza, é dificil ver as coisas. Foi pra economizar energia?

[quote=entanglement]Vou copiar o exemplo do Thingol, e então postar o exemplo equivalente em Java 6.

        // Java 7
        // Exemplo um pouco mais complexo, que
        // envolve 2 arquivos
        try (BufferedOutputStream bos = new BufferedOutputStream (
                new FileOutputStream ("copy.of.file"));
             BufferedInputStream bis = new BufferedInputStream (
                new FileInputStream ("original.file"))) {
            byte[] buffer = new byte[10240];
            for (int nBytes = bis.read (buffer); nBytes > 0;
                    nBytes = bis.read (buffer)) {
                bos.write (buffer, 0, nBytes); 
            }
        } catch (final FileNotFoundException | IOException ex) {
            ex.printStackTrace();
        }

// Java 6 // Exemplo um pouco mais complexo, que // envolve 2 arquivos BufferedOutputStream bos = null; BufferedInputStream bis = null; try { BufferedOutputStream bos = new BufferedOutputStream ( new FileOutputStream ("copy.of.file")); BufferedInputStream bis = new BufferedInputStream ( new FileInputStream ("original.file")); byte[] buffer = new byte[10240]; for (int nBytes = bis.read (buffer); nBytes > 0; nBytes = bis.read (buffer)) { bos.write (buffer, 0, nBytes); } } catch (FileNotFoundException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } finally { if (bis != null) try { bis.close(); } catch (IOException ex) {} if (bos != null) try { bos.close(); } catch (IOException ex) {} } [/quote]

O equivalente em Clojure:

(spit "copy.of.file" (slurp "original.file"))

A espera foi grande, mas parece que teremos uma boa gama de features novas, sem contar as closures.

[quote=Marky.Vasconcelos]Boa, mas a interface podia se chamar simplesmente Closable.

Isso tudo são syntax-sugars ou realmente ouve alguma mudança na estrutura dos programas em Java?[/quote]

Sim, apenas syntax-sugars…

[quote=mochuara][quote=entanglement]Vou copiar o exemplo do Thingol, e então postar o exemplo equivalente em Java 6.

        // Java 7
        // Exemplo um pouco mais complexo, que
        // envolve 2 arquivos
        try (BufferedOutputStream bos = new BufferedOutputStream (
                new FileOutputStream ("copy.of.file"));
             BufferedInputStream bis = new BufferedInputStream (
                new FileInputStream ("original.file"))) {
            byte[] buffer = new byte[10240];
            for (int nBytes = bis.read (buffer); nBytes > 0;
                    nBytes = bis.read (buffer)) {
                bos.write (buffer, 0, nBytes); 
            }
        } catch (final FileNotFoundException | IOException ex) {
            ex.printStackTrace();
        }

// Java 6 // Exemplo um pouco mais complexo, que // envolve 2 arquivos BufferedOutputStream bos = null; BufferedInputStream bis = null; try { BufferedOutputStream bos = new BufferedOutputStream ( new FileOutputStream ("copy.of.file")); BufferedInputStream bis = new BufferedInputStream ( new FileInputStream ("original.file")); byte[] buffer = new byte[10240]; for (int nBytes = bis.read (buffer); nBytes > 0; nBytes = bis.read (buffer)) { bos.write (buffer, 0, nBytes); } } catch (FileNotFoundException ex) { ex.printStackTrace(); } catch (IOException ex) { ex.printStackTrace(); } finally { if (bis != null) try { bis.close(); } catch (IOException ex) {} if (bos != null) try { bos.close(); } catch (IOException ex) {} } [/quote]

O equivalente em Clojure:

(spit "copy.of.file" (slurp "copy.of.file"))

O equivalente em Python

shutil.copyfile(copy.of.file, copy.of.file);

Vale lembrar que o commons-io tem muitas funções prontas pra esses tipos de operações que envolvem o filesystem. Mas são poucas pessoas que usam, de fato, as bibliotecas do commons =(

[quote=juliocbq][quote=Andre Brito]Aposto que vai confundir muita gente esse try com catch e finally opcionais!

Só não vi muita vantagem nisso… Não consegui entender o porque de eu não querer tratar a exception na hora de fechar o arquivo (ou coisa do tipo).
[/quote]

A idéia é que o io só existe dentro do scopo, ou seja dentro do try. Isso evita erros gerados quando não se desalocam recursos, como memory leaks.
Idéia muito boa essa interface AutoCloseable.[/quote]
Hum… É, o escopo fica bem mais ‘restrito’ ou ‘focado’, não sei qual o termo certo. Pensando dessa forma e das outras vantagens colocadas pelo entaglement, é bem interessante a funcionalidade.

Aliás, o blog do Thingol tem várias coisas a respeito do Java 7. Vale a pena dar uma conferida.

[quote=mochuara]
O equivalente em Clojure:

(spit "copy.of.file" (slurp "original.file"))

O equivalente em Java, usando um método copyFile:

copyFile("original.file", "copy.of.file");

Mas sério, não vou ficar brincando de a-minha-api-padrão-é-melhor-que-a-sua.

[quote=Leozin]
O equivalente em Python

shutil.copyfile(copy.of.file, copy.of.file);

Vale lembrar que o commons-io tem muitas funções prontas pra esses tipos de operações que envolvem o filesystem. Mas são poucas pessoas que usam, de fato, as bibliotecas do commons =([/quote]

Bem melhor ne? rs

Mas Clojure tem uma vantagem que é não precisar abrir mão do Java.