Question

Une des choses qui me dérange toujours dans l'utilisation de Readers and Streams en Java est que la méthode close () peut générer une exception. Comme il est judicieux de placer la méthode close dans un bloc finally, cela nécessite une situation un peu délicate. J'utilise habituellement cette construction:

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

Mais j'ai aussi vu cette construction:

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

Je préfère la première construction parce qu’il n’ya qu’un seul bloc captif et que cela semble plus élégant. Existe-t-il une raison de préférer la seconde construction ou une construction alternative?

UPDATE: Cela ferait-il une différence si je soulignais que read et close ne lève que des IOExceptions? Il me semble donc probable que si la lecture échoue, la fermeture échouera pour la même raison.

Était-ce utile?

La solution

Je choisirais toujours le premier exemple.

Si close lève une exception (dans la pratique, cela ne se produira jamais pour un FileReader), la manière habituelle de le gérer consiste-t-elle à lancer une exception appropriée à l'appelant? L'exception close remplace presque certainement tous les problèmes que vous avez rencontrés lors de l'utilisation de la ressource. La deuxième méthode est probablement plus appropriée si votre idée de la gestion des exceptions consiste à appeler System.err.println.

Il y a une question de savoir jusqu'où des exceptions devraient être levées. ThreadDeath devrait toujours être relancé, mais toute exception à l'intérieur arrêterait finalement cela. De la même façon, Error devrait lancer plus loin que les exceptions vérifiées, à l'exception de RuntimeException et RuntimeException Si vous le vouliez vraiment, vous pourriez écrire du code pour suivre ces règles, puis l’abstrait avec le mot de passe "execute around". idiome.

Autres conseils

Je crains que le premier exemple ne pose un gros problème, à savoir que si une exception se produit après ou après la lecture, le bloc finally est exécuté. Jusqu'ici tout va bien. Mais que se passe-t-il si fr.close () provoque alors la génération d’une autre exception? Cela va " atout " la première exception (un peu comme mettre return dans un bloc finally ) et , vous perdrez toutes les informations sur la cause réelle du problème .

Votre bloc finally devrait utiliser:

IOUtil.closeSilently(fr);

où cette méthode utilitaire ne fait que:

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

Je préfère le second. Pourquoi? Si les deux exceptions read () et close () , l'une d'elles est perdue. Dans la première construction, l’exception de close () annule l’exception de read () , tandis que dans la deuxième, l’exception de close () est traité séparément.

À partir de Java 7, le try-with- la construction de ressources simplifie grandement les choses. Pour lire sans se soucier des exceptions:

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

Avec gestion des exceptions:

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

Un seul try-catch est nécessaire et toutes les exceptions peuvent être traitées en toute sécurité. Vous pouvez toujours ajouter un bloc finally si vous le souhaitez, mais il n'est pas nécessaire de fermer les ressources.

Si read et close lisent une exception, l'exception de read sera masquée dans l'option 1. Ainsi, la deuxième option plus de traitement des erreurs.

Cependant, dans la plupart des cas, la première option sera toujours préférée.

  1. Dans de nombreux cas, vous ne pouvez pas gérer les exceptions dans la méthode générée, mais vous devez néanmoins encapsuler la gestion du flux au sein de cette opération.
  2. Essayez d’ajouter un rédacteur au code et voyez comment la seconde approche devient prolixe.

Si vous devez passer toutes les exceptions générées, cela peut être fait .

Autant que je sache, la différence est qu’il existe différentes exceptions et causes en jeu à différents niveaux, et le

catch (exception e)

obscurcit cela. Le seul intérêt des multiples niveaux est de distinguer vos exceptions et ce que vous allez faire à leur propos:

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

Je fais habituellement ce qui suit. Commencez par définir une classe basée sur un modèle pour gérer le désordre 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);
                }
            }
        }
    }
}

Notez le " en attente " exception - cela prend en charge le cas où une exception levée lors d'une fermeture masquerait une exception qui pourrait vraiment nous intéresser.

Finalement, il essaie d’abord de fermer tout flux décoré. Par conséquent, si vous avez un BufferedWriter encapsulant un FileWriter, nous essayons d’abord de fermer BuffereredWriter. Si cela échoue, essayez quand même de fermer le FileWriter lui-même.

Vous pouvez utiliser la classe ci-dessus comme suit:

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
}

En utilisant cette approche, vous n'avez jamais à vous soucier de try / catch / finally pour traiter à nouveau la fermeture de fichiers.

Si cela est trop lourd pour votre utilisation, pensez au moins à suivre les instructions try / catch et "en attente". approche variable qu'il utilise.

La convention standard que j'utilise est que vous ne devez pas laisser les exceptions échapper à un blocage final.

En effet, si une exception est déjà en train de se propager, l'exception renvoyée du bloc finally sera prioritaire sur l'exception d'origine (et sera donc perdue).

Dans 99% des cas, il ne s'agit pas de ce que vous souhaitez, car l'exception d'origine est probablement la source de votre problème (toute exception secondaire peut être un effet secondaire du premier, mais occultera votre capacité à rechercher la source de l'exception d'origine et donc le vrai problème).

Votre code de base devrait donc ressembler à ceci:

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ème approche.

Sinon, je ne vous vois pas intercepter l'exception du constructeur FileReader

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

public FileReader (String NomFichier)            lève une exception FileNotFoundException

Ainsi, le constructeur se trouve généralement dans le bloc try également. Le bloc finally vérifie si le lecteur n'est pas null avant d'essayer de le fermer.

Le même modèle s'applique à la source de données, à la connexion, à l'instruction et au jeu de résultats.

Parfois, try-catch imbriqué n'est pas une préférence, considérez ceci:

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

C’est probablement un mauvais exemple, mais vous aurez parfois besoin de try-cactch imbriqué.

J'aime l'approche de Chris Marshall, mais je n'aime jamais voir les exceptions se faire avaler en silence. Je pense qu'il est préférable de consigner les exceptions, surtout si vous continuez malgré tout.

J'utilise toujours une classe d'utilitaires pour gérer ce type d'exceptions communes, mais je voudrais que ce soit très différent de sa réponse.

J'utiliserais toujours un enregistreur (log4j pour moi) pour consigner les erreurs, etc.

IOUtil.close(fr);

Une légère modification de la méthode d’utilité:

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

Dans certains cas, un Try-Catch imbriqué est inévitable. Par exemple, lorsque le code de récupération d'erreur lui-même peut générer une exception. Mais pour améliorer la lisibilité du code, vous pouvez toujours extraire le bloc imbriqué dans une méthode qui lui est propre. Consultez le ce billet de blog pour plus d'exemples sur le test imbriqué Try- Catch-Finally bloque.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top