Pergunta

Uma das coisas que sempre me incomoda sobre o uso de leitores e Streams em Java é que o método close() pode lançar uma exceção. Desde que é uma boa idéia colocar o método close em um bloco, finalmente, que necessita de um pouco de uma situação embaraçosa. Eu costumo usar esta construção:

FileReader fr = new FileReader("SomeFile.txt");
try {
    try {
        fr.read();
    } finally {
        fr.close();
    }
} catch(Exception e) {
    // Do exception handling
}

Mas eu também vi esta construção:

FileReader fr = new FileReader("SomeFile.txt");
try {
    fr.read() 
} catch (Exception e) {
    // Do exception handling
} finally {
    try {
        fr.close();
    } catch (Exception e) {
        // Do exception handling
    }
}

Eu prefiro a primeira construção porque há apenas um bloco catch e parece apenas mais elegante. Existe uma razão para realmente prefiro a segunda ou uma construção alternativa?

UPDATE: Faria alguma diferença se eu apontou que tanto read e close única jogue IOExceptions? Assim, parece provável para mim que, se leitura falhar, perto falhará pela mesma razão.

Foi útil?

Solução

Eu sempre ir para o primeiro exemplo.

Se perto estavam para lançar uma exceção (na prática isso nunca vai acontecer para um FileReader), não seria a maneira padrão de lidar que ser para lançar uma apropriada exceção para o chamador? A exceção próximo quase certamente supera qualquer problema que você teve usando o recurso. O segundo método é provavelmente mais apropriado se a sua ideia de manipulação de exceção é chamar System.err.println.

Não é uma questão de quão longe exceções devem ser lançada. ThreadDeath deve sempre ser relançada, mas qualquer exceção dentro finalmente iria parar com isso. Da mesma forma erro deve lançar mais longe do que RuntimeException e RuntimeException mais longe do que exceções verificadas. Se você realmente quiser você pode escrever código para seguir estas regras, e, em seguida, abstract-lo com o "executar em torno de" idioma.

Outras dicas

Eu tenho medo há um grande problema com o primeiro exemplo, o que é que se uma exceção acontece em ou após a leitura, as executa o bloco finally. Por enquanto, tudo bem. Mas e se o fr.close() em seguida, faz outra exceção seja lançada? Isto irá "trunfo" a primeira exceção (um pouco como colocar return em um bloco finally) e você vai perder todas as informações sobre o que realmente causou o problema para começar.

Seu último bloco deve usar:

IOUtil.closeSilently(fr);

onde este método de utilidade só faz:

public static void closeSilently(Closeable c) {
    try { c.close(); } catch (Exception e) {} 
} 

Eu prefiro a segunda. Por quê? Se ambos os read() e close() lance exceções, um deles poderia ser perdido. Na primeira construção, à excepção de close() substitui a excepção de read(), enquanto na segunda, a excepção de close() é manuseado separadamente.


A partir do Java 7, a tentar-com- recursos construir torna isso muito mais simples Para ler sem se preocupar com exceções:.

try (FileReader fr = new FileReader("SomeFile.txt")) {
    fr.read();
    // no need to close since the try-with-resources statement closes it automatically
}

Com o tratamento de exceção:

try (FileReader fr = new FileReader("SomeFile.txt")) {
    fr.read();
    // no need to close since the try-with-resources statement closes it automatically
} catch (IOException e) {
    // Do exception handling
    log(e);
    // If this catch block is run, the FileReader has already been closed.
    // The exception could have come from either read() or close();
    // if both threw exceptions (or if multiple resources were used and had to be closed)
    // then only one exception is thrown and the others are suppressed
    // but can still be retrieved:
    Throwable[] suppressed = e.getSuppressed(); // can be an empty array
    for (Throwable t : suppressed) {
        log(suppressed[t]);
    }
}

Apenas um try-catch é necessária e todas as exceções podem ser tratadas de forma segura. Você ainda pode adicionar um bloco finally se quiser, mas não há nenhuma necessidade de fechar os recursos.

Se ambos ler e próximo lançar uma exceção, a exceção de Leia serão escondidos na opção 1. Assim, a segunda opção faz mais tratamento de erros.

No entanto, na maioria dos casos, a primeira opção ainda será preferido.

  1. Em muitos casos, você não pode lidar com exceções no método que eles são gerados, mas você ainda deve encapsular o fluxo de manipulação dentro dessa operação.
  2. Tente adicionar um escritor para o código e ver como detalhado a segunda abordagem fica.

Se você precisa passar todas as exceções geradas, pode ser feito .

A diferença, tanto quanto eu posso ver, é que existem diferentes exceções e as causas em jogo em diferentes níveis, eo

captura (exceção e)

obscurece isso. O único ponto dos múltiplos níveis é distinguir suas exceções, eo que você vai fazer com eles:

try
{
  try{
   ...
  }
   catch(IOException e)
  {
  ..
  }
}
catch(Exception e)
{
  // we could read, but now something else is broken 
  ...
}

Eu costumo fazer o seguinte. Primeiro, definir um método-modelo de classe base 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 {
    private static final Closeable NEW_FILE = new Closeable() {
        public void close() throws IOException {
            // do nothing
        }
    };

    // 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<Closeable> closeables_ = new LinkedList<Closeable>();

    // mark a new file
    protected void newFile() {
        closeables_.add(0, NEW_FILE);
    }

    // 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 void watch(Closeable closeable) {
        closeables_.add(0, 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
            boolean skip = false;
            for (Closeable closeable : closeables_) {
                if (closeable == NEW_FILE) {
                    skip = false;
                } else  if (!skip && closeable != null) {
                    try {
                        closeable.close();
                        // don't try to re-close nested closeables
                        skip = true;
                    } 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 o "pendente" exceção -. Este cuida do caso em que uma exceção lançada durante o próximo iria mascarar uma exceção que pode realmente se preocupam

O finalmente tenta fechar do lado de fora de qualquer fluxo decorado em primeiro lugar, por isso, se você tivesse um BufferedWriter envolvendo um FileWriter, tentamos fechar a BuffereredWriter primeiro lugar, e se isso falhar, ainda tentar fechar a si FileWriter.

Você pode usar a classe acima da seguinte forma:

try {
    // ...

    new AutoFileCloser() {
        @Override protected void doWork() throws Throwable {
            // declare variables for the readers and "watch" them
            FileReader fileReader = null;
            BufferedReader bufferedReader = null;
            watch(fileReader = new FileReader("somefile"));
            watch(bufferedReader = new BufferedReader(fileReader));

            // ... do something with bufferedReader

            // if you need more than one reader or writer
            newFile(); // puts a flag in the 
            FileWriter fileWriter = null;
            BufferedWriter bufferedWriter = null;
            watch(fileWriter = new FileWriter("someOtherFile"));
            watch(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 tem que se preocupar com o try / catch / finally para lidar com fechamento de arquivos novamente.

Se este for demasiado pesado para o seu uso, pelo menos, pensar sobre seguindo o try / catch e o "pendente" abordagem variável que utiliza.

O uso padrão convenção I é que você não deve deixar escapar exceções um bloco finally.

Isso porque, se uma exceção já está propagando a exceção lançada para fora do trunfo finalmente bloco vontade a exceção original (e, portanto, ser perdidos).

Em 99% dos casos, isso não é o que você quer como a exceção original é provavelmente a fonte do problema (as exceções secundárias podem ser efeitos colaterais da primeira, mas vai obscurecer a sua capacidade para encontrar a fonte da exceção original e assim o problema real).

Portanto, o seu código básico deve ficar assim:

try
{
    // Code
}
// Exception handling
finally
{
    // Exception handling that is garanteed not to throw.
    try
    {
         // Exception handling that may throw.
    }
    // Optional Exception handling that should not throw
    finally()
    {}
}

2ª abordagem.

Caso contrário, eu não vejo você captura a exceção do FileReader construtor

http://java.sun.com/j2se/1.5.0/docs/api/java/io/FileReader.html#FileReader (java.lang.String)

FileReader pública (String fileName) lança FileNotFoundException

Então, eu geralmente têm o construtor dentro do bloco try bem. o bloco finally verifica para ver se o leitor não é nulo antes de tentar fazer um fim.

O mesmo padrão vale para Datasource, Connection, Statement, ResultSet.

Às vezes aninhada try-catch não é uma preferência, considere o seguinte:

try{
 string s = File.Open("myfile").ReadToEnd(); // my file has a bunch of numbers
 // I want to get a total of the numbers 
 int total = 0;
 foreach(string line in s.split("\r\n")){
   try{ 
     total += int.Parse(line); 
   } catch{}
 }
catch{}

Este é provavelmente um mau exemplo, mas há momentos em que você vai precisar aninhada try-cactch.

Eu gosto da abordagem por @ Chris Marshall, mas eu nunca gostaria de ver exceções sendo engolido em silêncio. Eu acho que é melhor para exceções de log, especialmente se você está contiuing independentemente.

Eu sempre uso uma classe de utilitário para lidar com este tipo de exceções comuns, mas gostaria de fazer esta pequena diferente para a sua resposta.

Eu sempre usar um logger (log4j para mim) a erros etc log.

IOUtil.close(fr);

Uma ligeira modificação ao método de utilidade:

public static void close(Closeable c) {
    try {
      c.close();
    } catch (Exception e) {
      logger.error("An error occurred while closing. Continuing regardless", e); 
    } 
}

Em alguns casos, uma aninhada try-catch é inevitável. Por exemplo, quando o próprio código de recuperação de erro pode lançar e exceção. Mas, a fim de melhorar a legibilidade do código que você sempre pode extrair o bloco aninhado em um método próprio. Confira este post blog para mais exemplos sobre tentar- aninhada catch-finally blocos.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top