Maneira correta de fechar streams e escritores nesteds em Java

Nota: Esta questão e a maioria de suas respostas datam antes do lançamento do Java 7. O Java 7 fornece a funcionalidade do Gerenciamento Automático de Recursos para fazer isso de maneira fácil. Se você estiver usando o Java 7 ou posterior, você deve avançar para a resposta de Ross Johnson .


Qual é a melhor e mais abrangente maneira de fechar streams nesteds em Java? Por exemplo, considere a configuração:

FileOutputStream fos = new FileOutputStream(...) BufferedOS bos = new BufferedOS(fos); ObjectOutputStream oos = new ObjectOutputStream(bos); 

Eu entendo que a operação de fechamento precisa ser segurada (provavelmente usando uma cláusula finally). O que eu me pergunto é, é necessário certificar-se explicitamente de que os streams nesteds estão fechados, ou é suficiente apenas para se certificar de fechar o stream externo (oos)?

Uma coisa que eu noto, pelo menos, lidar com este exemplo específico, é que os streams internos parecem apenas lançar FileNotFoundExceptions. O que parece implicar que não há necessidade técnica de se preocupar em fechá-los se eles falharem.

Veja o que um colega escreveu:


Tecnicamente, se fosse implementado corretamente, fechar o stream externo (oos) deveria ser suficiente. Mas a implementação parece falha.

Exemplo: BufferedOutputStream herda close () de FilterOutputStream, que define como:

  155 public void close() throws IOException { 156 try { 157 flush(); 158 } catch (IOException ignored) { 159 } 160 out.close(); 161 } 

No entanto, se flush () lançar uma exceção de tempo de execução por algum motivo, então out.close () nunca será chamado. Portanto, parece “mais seguro” (mas feio) preocupar-se principalmente com o fechamento do FOS, que mantém o arquivo aberto.


O que é considerado o melhor, quando você precisa ter certeza, abordagem para fechar streams nesteds?

E existem documentos oficiais sobre Java / Sun que lidam com isso em detalhes?

    Eu costumo fazer o seguinte. Primeiro, defina uma class baseada em modelo para lidar com a bagunça try / catch

     import java.io.Closeable; import java.io.IOException; import java.util.LinkedList; import java.util.List; public abstract class AutoFileCloser { // the core action code that the implementer wants to run protected abstract void doWork() throws Throwable; // track a list of closeable thingies to close when finished private List closeables_ = new LinkedList(); // give the implementer a way to track things to close // assumes this is called in order for nested closeables, // inner-most to outer-most protected final  T autoClose(T closeable) { closeables_.add(0, closeable); return closeable; } public AutoFileCloser() { // a variable to track a "meaningful" exception, in case // a close() throws an exception Throwable pending = null; try { doWork(); // do the real work } catch (Throwable throwable) { pending = throwable; } finally { // close the watched streams for (Closeable closeable : closeables_) { if (closeable != null) { try { closeable.close(); } catch (Throwable throwable) { if (pending == null) { pending = throwable; } } } } // if we had a pending exception, rethrow it // this is necessary b/c the close can throw an // exception, which would remove the pending // status of any exception thrown in the try block if (pending != null) { if (pending instanceof RuntimeException) { throw (RuntimeException) pending; } else { throw new RuntimeException(pending); } } } } } 

    Observe a exceção “pendente” – isso cuida do caso em que uma exceção lançada durante o fechamento iria mascarar uma exceção com a qual poderíamos realmente nos importar.

    O finalmente tenta fechar de fora de qualquer stream decorado primeiro, então se você tivesse um BufferedWriter envolvendo um FileWriter, nós tentamos fechar o BuffereredWriter primeiro, e se isso falhar, ainda tentamos fechar o próprio FileWriter. (Observe que a definição de chamadas Closeable para close () para ignorar a chamada, se o stream já está fechado)

    Você pode usar a class acima da seguinte maneira:

     try { // ... new AutoFileCloser() { @Override protected void doWork() throws Throwable { // declare variables for the readers and "watch" them FileReader fileReader = autoClose(fileReader = new FileReader("somefile")); BufferedReader bufferedReader = autoClose(bufferedReader = new BufferedReader(fileReader)); // ... do something with bufferedReader // if you need more than one reader or writer FileWriter fileWriter = autoClose(fileWriter = new FileWriter("someOtherFile")); BufferedWriter bufferedWriter = autoClose(bufferedWriter = new BufferedWriter(fileWriter)); // ... do something with bufferedWriter } }; // .. other logic, maybe more AutoFileClosers } catch (RuntimeException e) { // report or log the exception } 

    Usando essa abordagem você nunca precisa se preocupar com o try / catch / finally para lidar com o fechamento de arquivos novamente.

    Se isso for muito pesado para seu uso, pelo menos pense em seguir a abordagem de try / catch e a variável “pendente” que ele usa.

    Ao fechar streams encadeados, você só precisa fechar o stream mais externo. Quaisquer erros serão propagados na cadeia e serão capturados.

    Consulte Fluxos de E / S de Java para obter detalhes.

    Para resolver o problema

    No entanto, se flush () lançar uma exceção de tempo de execução por algum motivo, então out.close () nunca será chamado.

    Isso não está certo. Depois de capturar e ignorar essa exceção, a execução retornará após o bloco catch e a out.close() será executada.

    Seu colega faz uma boa observação sobre a Exceção de Tempo de Execução . Se você absolutamente precisa que o stream seja fechado, você pode sempre tentar fechar cada um individualmente, de fora para dentro, parando na primeira exceção.

    Na era do Java 7, tentar com resources é certamente o caminho a percorrer. Como mencionado em várias respostas anteriores, a solicitação de fechamento é propagada do stream mais externo para o stream mais interno. Então, um único fechamento é tudo o que é necessário.

     try (ObjectInputStream ois = new ObjectInputStream(new FileInputStream(f))) { // do something with ois } 

    No entanto, existe um problema com esse padrão. O try-with-resources não tem conhecimento do FileInputStream interno, portanto, se o construtor ObjectInputStream lançar uma exceção, o FileInputStream nunca será fechado (até que o coletor de lixo o atinja). A solução é…

     try (FileInputStream fis = new FileInputStream(f); ObjectInputStream ois = new ObjectInputStream(fis)) { // do something with ois } 

    Isso não é tão elegante, mas é mais robusto. Se isso é realmente um problema, vai depender de quais exceções podem ser lançadas durante a construção do (s) object (s) externo (s). ObjectInputStream pode lançar IOException que pode ser manipulado por um aplicativo sem finalizar. Muitas classs de stream apenas lançam exceções não verificadas, o que pode resultar no encerramento do aplicativo.

    É uma boa prática usar o Apache Commons para manipular objects relacionados a E / S.

    Na cláusula finally use IOUtils

    IOUtils.closeQuietly (bWriter); IOUtils.closeQuietly (oWritter);

    Snippet de código abaixo.

     BufferedWriter bWriter = null; OutputStreamWriter oWritter = null; try { oWritter = new OutputStreamWriter( httpConnection.getOutputStream(), "utf-8" ); bWriter = new BufferedWriter( oWritter ); bWriter.write( xml ); } finally { IOUtils.closeQuietly(bWriter); IOUtils.closeQuietly(oWritter); } 

    O colega levanta um ponto interessante, e há motivos para argumentar de qualquer maneira.

    Pessoalmente, eu iria ignorar o RuntimeException , porque uma exceção não verificada significa um bug no programa. Se o programa estiver incorreto, corrija-o. Você não pode “manipular” um programa incorreto em tempo de execução.

    Esta é uma pergunta surpreendentemente estranha. (Mesmo assumindo a acquire; try { use; } finally { release; } código está correto.)

    Se a construção do decorador falhar, você não estará fechando o stream subjacente. Portanto, você precisa fechar o stream subjacente explicitamente, seja no final após o uso ou, mais di fi culado após entregar o recurso com sucesso ao decorador).

    Se uma exceção causar falha na execução, você realmente deseja liberar?

    Alguns decoradores realmente têm resources próprios. A implementação atual do Sun do ZipInputStream por exemplo, tem memory heap não-Java alocada.

    Foi alegado que (IIRC) dois terços dos resources utilizados na biblioteca Java são implementados de maneira claramente incorreta.

    Enquanto BufferedOutputStream fecha até mesmo em um IOException do flush , BufferedWriter fecha corretamente.

    Meu conselho: Feche os resources o mais diretamente possível e não os deixe contaminar outro código. OTOH, você pode gastar muito tempo com este problema – se o OutOfMemoryError for lançado, é bom se comportar bem, mas outros aspectos do seu programa são provavelmente uma prioridade mais alta e o código da biblioteca provavelmente está quebrado nesta situação. Mas eu sempre escrevo:

     final FileOutputStream rawOut = new FileOutputStream(file); try { OutputStream out = new BufferedOutputStream(rawOut); ... write stuff out ... out.flush(); } finally { rawOut.close(); } 

    (Olha: não pega!)

    E talvez use o idioma Execute Around.

    O try-with-resources do Java SE 7 não parece ser mencionado. Ele elimina a necessidade de fazer explicitamente um fechamento completamente, e eu gosto muito da idéia.

    Infelizmente, para o desenvolvimento do Android, esse doce só fica disponível usando o Android Studio (eu acho) e direcionando o Kitkat e acima .

    Além disso, você não precisa fechar todos os streams nesteds

    veja este http://ckarthik17.blogspot.com/2011/02/closing-nested-streams.html

    Eu uso para fechar streams como este, sem aninhar try-catch em finalmente blocos

     public class StreamTest { public static void main(String[] args) { FileOutputStream fos = null; BufferedOutputStream bos = null; ObjectOutputStream oos = null; try { fos = new FileOutputStream(new File("...")); bos = new BufferedOutputStream(fos); oos = new ObjectOutputStream(bos); } catch (Exception e) { } finally { Stream.close(oos,bos,fos); } } } class Stream { public static void close(AutoCloseable... array) { for (AutoCloseable c : array) { try {c.close();} catch (IOException e) {} catch (Exception e) {} } } } 

    Os JavaDocs da Sun incluem RuntimeException s em sua documentação, conforme mostrado pelo método read (byte [], int, int) do InputStream; documentado como lançando NullPointerException e IndexOutOfBoundsException.

    O flush () de FilterOutputStream é documentado apenas como lançando IOException, portanto, ele realmente não lança nenhum RuntimeException s. Qualquer coisa que pudesse ser lançada provavelmente seria envolvida em uma IIOException .

    Poderia ainda lançar um Error , mas não há muito o que fazer sobre isso; A Sun recomenda que você não tente pegá-los.