Domanda

Una delle cose che mi infastidisce sempre nell'uso dei lettori e dei flussi in Java è che il metodo close () può generare un'eccezione. Dal momento che è una buona idea mettere il metodo vicino in un blocco finalmente, ciò richiede un po 'di una situazione imbarazzante. Di solito uso questa costruzione:

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

Ma ho visto anche questa costruzione:

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
    }
}

Preferisco la prima costruzione perché c'è solo un blocco di cattura e sembra solo più elegante. C'è un motivo per preferire effettivamente la seconda o una costruzione alternativa?

AGGIORNAMENTO: Farebbe differenza se sottolineassi che sia read che close generano solo IOExceptions? Quindi mi sembra probabile che, se la lettura fallisce, la chiusura fallirà per lo stesso motivo.

È stato utile?

Soluzione

Vorrei sempre scegliere il primo esempio.

Se Close dovesse generare un'eccezione (in pratica ciò non accadrà mai per un FileReader), il modo standard di gestione che sarebbe quello di lanciare un'eccezione appropriato per il chiamante? La stretta eccezione quasi certamente supera qualsiasi problema che hai avuto usando la risorsa. Il secondo metodo è probabilmente più appropriato se la tua idea di gestione delle eccezioni è chiamare System.err.println.

Esiste un problema su quanto debbano essere lanciate le eccezioni. ThreadDeath dovrebbe sempre essere riproposto, ma qualsiasi eccezione all'interno lo fermerebbe. Allo stesso modo, l'errore dovrebbe andare oltre RuntimeException e RuntimeException oltre alle eccezioni verificate. Se lo desideri davvero, potresti scrivere il codice per seguire queste regole e quindi astrarlo con il comando "esegui intorno a" idioma.

Altri suggerimenti

Temo che ci sia un grosso problema con il primo esempio, ovvero che se si verifica un'eccezione durante o dopo la lettura, viene eseguito il blocco infine . Fin qui tutto bene. Ma cosa succede se fr.close () provoca un'altra eccezione? Questo sarà "briscola" la prima eccezione (un po 'come mettere return in un blocco finalmente ) e perderai tutte le informazioni su ciò che ha effettivamente causato il problema .

Il tuo blocco finally dovrebbe usare:

IOUtil.closeSilently(fr);

dove questo metodo di utilità fa proprio:

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

Preferisco il secondo. Perché? Se sia read () che close () generano eccezioni, una di queste potrebbe andare persa. Nella prima costruzione, l'eccezione da close () sostituisce l'eccezione da read () , mentre nella seconda, l'eccezione da close () è gestito separatamente.


A partire da Java 7, il try-with- la costruzione delle risorse rende tutto molto più semplice. Leggere senza preoccuparsi delle eccezioni:

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

Con gestione delle eccezioni:

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]);
    }
}

È necessario solo un tentativo di cattura e tutte le eccezioni possono essere gestite in modo sicuro. Puoi ancora aggiungere un blocco finalmente se vuoi, ma non è necessario chiudere le risorse.

Se sia leggi che chiudi generano un'eccezione, l'eccezione di leggi verrà nascosta nell'opzione 1. Quindi, la seconda opzione fa più gestione degli errori.

Tuttavia, nella maggior parte dei casi, la prima opzione sarà comunque preferita.

  1. In molti casi, non è possibile gestire le eccezioni nel metodo che vengono generati, ma è comunque necessario incapsulare la gestione del flusso all'interno di tale operazione.
  2. Prova ad aggiungere uno scrittore al codice e guarda quanto diventa dettagliato il secondo approccio.

Se devi superare tutte le eccezioni generate, può essere fatto .

La differenza, per quanto posso vedere, è che ci sono diverse eccezioni e cause in gioco a diversi livelli e il

catch (Exception e)

lo oscura. L'unico punto di più livelli è distinguere le tue eccezioni e cosa farai al riguardo:

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

Di solito faccio quanto segue. Innanzitutto, definisci una classe basata su un metodo modello per gestire il pasticcio 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);
                }
            }
        }
    }
}

Prendi nota di " in sospeso " eccezione: si occupa del caso in cui un'eccezione generata durante la chiusura mascherasse un'eccezione di cui potremmo davvero preoccuparci.

Finalmente tenta di chiudere prima dall'esterno di qualsiasi flusso decorato, quindi se hai avuto un BufferedWriter che avvolge un FileWriter, proviamo prima a chiudere BuffereredWriter e, se fallisce, proviamo comunque a chiudere il FileWriter stesso.

Puoi usare la classe sopra come segue:

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 questo approccio non devi mai preoccuparti di provare / catturare / finalmente per gestire nuovamente la chiusura dei file.

Se questo è troppo pesante per il tuo uso, almeno pensa a seguire il try / catch e il "in attesa" approccio variabile che utilizza.

La convenzione standard che uso è che non devi lasciare che le eccezioni sfuggano a un blocco infine.

Questo perché se un'eccezione si sta già propagando, l'eccezione lanciata dal blocco finally vincerà l'eccezione originale (e quindi andrà persa).

Nel 99% dei casi questo non è ciò che desideri poiché l'eccezione originale è probabilmente la fonte del tuo problema (eventuali eccezioni secondarie possono essere effetti collaterali dal primo ma oscurano la tua capacità di trovare la fonte dell'eccezione originale e quindi il vero problema).

Quindi il tuo codice di base dovrebbe apparire così:

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()
    {}
}

2o approccio.

Altrimenti, non ti vedo catturare l'eccezione dal costruttore FileReader

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

FileReader pubblico (String fileName)            genera FileNotFoundException

Quindi, di solito ho anche il costruttore all'interno del blocco try. il blocco finally verifica se il lettore NON è null prima di provare a chiudere.

Lo stesso schema vale per Datasource, Connection, Statement, ResultSet.

A volte il natch try-catch non è una preferenza, considera questo:

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{}

Questo è probabilmente un cattivo esempio, ma ci sono volte in cui avrai bisogno di try-cactch nidificato.

Mi piace l'approccio di @Chris Marshall, ma non mi piace mai vedere le eccezioni che vengono deglutite silenziosamente. Penso che sia meglio registrare le eccezioni, soprattutto se stai continuando a prescindere.

Uso sempre una classe di utilità per gestire questo tipo di eccezioni comuni, ma lo renderei molto diverso dalla sua risposta.

Userei sempre un logger (log4j per me) per registrare errori ecc.

IOUtil.close(fr);

Una leggera modifica al metodo di utilità:

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

In alcuni casi è inevitabile un Try-Catch nidificato. Ad esempio, quando il codice di ripristino dell'errore stesso può generare ed eccezioni. Ma per migliorare la leggibilità del codice è sempre possibile estrarre il blocco nidificato in un metodo a sé stante. Consulta questo post sul blog per ulteriori esempi su Try nidificato- Blocchi Catch-Infine.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top