Parte do Projeto Coin é a capacidade de lidar com o Gerenciamento Automático de Recursos ou, simplesmente, ARM (Automatic Resource Management). A proposta é torná-lo mais fácil de se trabalhar com recursos externos, que precisam ser eliminados ou fechados em caso de erros ou conclusão bem sucedida de um bloco de código. Considere a seguinte operação trivial de cópia de arquivo, a partir do tutorial Java Bytestream:
FileInputStream in = null; FileOutputStream out = null; try { in = new FileInputStream("entrada.txt"); out = new FileOutputStream("saida.txt"); int c; while ((c = in.read()) != -1) out.write(c); } finally { if (in != null) in.close(); if (out != null) out.close(); }
Nele, não há apenas códigos repetidos, mas a documentação para o método InputStream.close()
diz que este pode lançar uma IOException
. (é preferível uma exceção no OutputStream
, mas em todo caso, é preciso haver um catch externo ou uma propagation, para que este código possa ser compilado com sucesso).
O escopo do bloco try-catch-finally
também requere que as variáveis para o FileInputStream
(in) e para o FileOutputStream (out)
, sejam declaradas fora do bloco propriamente dito. (Se elas estivessem definidas dentro do bloco try
, então elas não estariam disponíveis dentro do bloco catch
ou finally
). Para eliminar este código clichê, e para restringir o escopo dos recursos utilizados dentro do bloco, uma nova adição foi feita ao bloco try na linguagem Java. Uma especificação inicial dos blocos try-with-resources (ou blocos ARM), foi disponibilizada através de uma implementação inicial, que posteriormente fez seu caminho para o build 105 do JDK 7.
A nova interface java.lang.AutoCloseable
foi adicionada à API proposta, que define um único método close()
, que pode lançar Exception
. Este tem sido retro-instalado como um pai do java.io.Closeable
, o que significa que todos os InputStream
e OutputStream
automaticamente tiram vantagem do seu comportamento. Adicionalmente, o FileLock
e o ImageInputStream
foram também equipados com a interface AutoCloseable
.
Isto permite que o exemplo acima possa ser escrito como:
try ( FileInputStream in = new FileInputStream("entrada.txt"); FileOutputStream out = new FileOutputStream("saida.txt") ) { int c; while((c=in.read()) != -1 ) out.write(); }
No final do bloco try, seja por conclusão normal ou não, ambos os recursos out
e in
terão o close()
chamado automaticamente. Além disso, ao contrário do nosso exemplo original, tanto o out.close()
quanto o in.close()
são garantidamente executados. (no exemplo original, caso o in.close()
lançasse uma exceção, o subsequente out.close()
não seria executado). Existem alguns aspectos sutis, que merecem ser citados:
- No seu estado atual, um ponto-e-vírgula à direita não é permitido depois do recurso final na seção de recursos.
- O bloco de recursos é separado com
()
ao invés do mais usual{}
, para separá-lo do bloco try existente. Se presente, ele deve conter uma ou mais definições de recursos. - Cada definição de recurso é da forma tipo var = expressão; você não pode ter declarações genéricas no bloco de recursos.
- Os recursos são implicitamente finais; ou seja, eles se comportam como se o modificador
final
estivesse presente. Qualquer tentativa de atribuição à variável de recursos é um erro em tempo de compilação. - Os recursos devem ser um subtipo de
AutoCloseable
; é um erro em tempo de compilação, se este não for o caso. - A ordem de encerramento é a ordem reversa na qual os recursos estão definidos. Em outras palavras, no exemplo refinado, o
out.close()
é chamado antes doin.close()
. Isso permite que streams aninhados sejam construídos e então fechados de fora para dentro, o que faz mais sentido do que em ordem (por exemplo, para liberar buffers antes do stream subjacente ser fechado). - Cada bloco pode gerar n+1 exceções, onde n é o número de recursos. Isto pode ocorrer se o método main lançar uma exceção, e cada fechamento de recurso também lançar uma exceção. Nesta situação, a exceção do método será lançada, mas as outras serão adicionadas à lista suprimida de exceções da exceção. Elas podem ser acessadas via
getSuppressedExceptions()
. - Rastreamentos de pilhas de exceção podem agora ter o código prefixado com o uso do
Suppressed:
nestes casos, o formato doThrowable
serializado agora é diferente também. (isto pode impactar os clientes Java 6 que estejam invocando serviços remotos Java 7 em tempo de execução, ou vice-versa). javax.swing
ejava.sql
não participam do ARM atualmente; as classes precisam do opt-in através de herança doAutoCloseable
, para serem usadas pelo ARM. Caso o JDBC 4.1 faça parte do JDK 7, ele irá suportar o ARM, mas não está claro quando isto irá acontecer.
A capacidade de remover códigos "clichê" do fluxo de trabalho dos desenvolvedores Java, é suscetível a ser um impulso de produtividade dos menores; mas por estar disponível no JDK 7, apenas algum tempo antes do código poder ser escrito é que poderemos tirar vantagem deste fato. Muitas bibliotecas precisarão ser compiladas para rodar contra o Java 6; e qualquer uso do gerenciador automático de recursos (ARM) apenas será aplicável para códigos compilados com o -target 7
(ou similar). Uma vez que o Java 6 está próximo do fim, a utilização do ARM se tornará uma forma automática de se trabalhar.