Y a-t-il une préférence pour les blocs d’essais / catch nesteds?

Une des choses qui me gêne toujours à propos de l’utilisation des lecteurs et des stream en Java est que la méthode close() peut générer une exception. Puisque c’est une bonne idée de mettre 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 car il n’ya qu’un seul bloc de blocage et cela semble juste plus élégant. Y a-t-il une raison de préférer la seconde ou une autre construction?

MISE À JOUR: Est-ce que cela ferait une différence si je faisais remarquer que les deux read et close seulement jettent des IOExceptions? Il me semble donc probable que si la lecture échoue, la fermeture échouera pour la même raison.

J’irais toujours pour le premier exemple.

Si close devait lancer une exception (dans la pratique cela n’arrivera jamais pour un FileReader), la manière standard de gérer cela ne serait-elle pas de lancer une exception appropriée à l’appelant? L’exception de fermeture l’emporte presque certainement sur tout problème lié à l’utilisation de la ressource. La seconde méthode est probablement plus appropriée si votre idée de gestion des exceptions consiste à appeler System.err.println.

Il y a un problème de la distance à laquelle les exceptions doivent être levées. ThreadDeath devrait toujours être relancé, mais toute exception au final arrêterait cela. De même, une erreur doit être renvoyée plus loin que RuntimeException et RuntimeException plus que les exceptions vérifiées. Si vous le voulez vraiment, vous pouvez écrire du code pour suivre ces règles, puis le résumer avec le langage “execute around”.

J’ai peur qu’il y ait un gros problème avec le premier exemple, à savoir que si une exception survient à la lecture ou après, le bloc finally s’exécute. Jusqu’ici tout va bien. Mais que faire si le fr.close() provoque une autre exception? Cela va “l’emporter” sur la première exception (un peu comme mettre un return dans un bloc finally ) et vous perdrez toutes les informations sur ce qui a causé le problème .

Votre bloc final doit 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 read() et close() lancent des exceptions, l’une d’elles pourrait être perdue. Dans la première construction, l’exception de close() remplace l’exception de read() , tandis que dans le second, l’exception de close() est gérée séparément.


A partir de Java 7, la construction try-with-resources rend cela beaucoup plus simple. 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 resortingeved: Throwable[] suppressed = e.getSuppressed(); // can be an empty array for (Throwable t : suppressed) { log(suppressed[t]); } } 

Une seule prise d’essai est nécessaire et toutes les exceptions peuvent être traitées en toute sécurité. Vous pouvez toujours append un bloc finally si vous le souhaitez, mais vous n’avez pas besoin de fermer les ressources.

Si les deux lisent et ferment une exception, l’exception de read sera masquée dans l’option 1. Ainsi, la deuxième option fait plus de gestion 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, mais vous devez toujours encapsuler le traitement du stream au sein de cette opération.
  2. Essayez d’append un écrivain au code et de voir comment la deuxième approche est verbeuse.

Si vous devez passer toutes les exceptions générées, vous pouvez le faire .

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

capture (Exception e)

obscurcit cela. Le seul sharepoints multiples niveaux est de distinguer vos exceptions et ce que vous allez faire à leur sujet:

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

Je fais généralement les choses suivantes. Tout d’abord, définissez une classe basée sur un modèle de méthode pour gérer le gâchis 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 closeables_ = new LinkedList(); // 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 l’exception “en attente” – cela prend en compte le cas où une exception lancée pendant la fermeture masquerait une exception dont nous pourrions vraiment nous préoccuper.

Le système tente d’abord de fermer à l’extérieur de tout stream décoré, donc si vous avez un BufferedWriter enveloppant un FileWriter, nous essayons d’abord de fermer BuffereredWriter, et si cela échoue, essayez toujours 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 gérer à nouveau la fermeture des fichiers.

Si cela est trop lourd pour votre usage, pensez au moins à suivre la méthode try / catch et l’approche variable “en attente” qu’elle utilise.

La convention standard que j’utilise est que vous ne devez pas laisser les exceptions échapper à un bloc finally.

En effet, si une exception se propage déjà, l’exception renvoyée hors du bloc finally annulera l’exception originale (et sera donc perdue).

Dans 99% des cas, ce n’est pas ce que vous voulez, car l’exception d’origine est probablement à l’origine de votre problème (toute exception secondaire peut être un effet secondaire du premier coup, mais masquer votre capacité à trouver la source de l’exception originale et donc le réel). problème).

Donc, votre code de base devrait 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.Ssortingng)

public FileReader (Ssortingng fileName) lève FileNotFoundException

Donc, j’ai généralement le constructeur dans le bloc try. Enfin, le bloc vérifie si le lecteur n’est PAS null avant d’essayer de fermer.

Le même modèle s’applique à la source de données, à la connexion, à l’instruction et à ResultSet.

Parfois, le try-catch nested n’est pas une préférence, considérez ceci:

 try{ ssortingng 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(ssortingng line in s.split("\r\n")){ try{ total += int.Parse(line); } catch{} } catch{} 

Ceci est probablement un mauvais exemple, mais il y a des fois où vous aurez besoin de try-cactch nested.

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

J’utilise toujours une classe utilitaire pour gérer ce genre d’exceptions courantes, mais je voudrais que ce soit très différent de sa réponse.

J’utiliserais toujours un enregistreur (log4j pour moi) pour enregistrer les erreurs, etc.

 IOUtil.close(fr); 

Une légère modification de la méthode utilitaire:

 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 nested est inévitable. Par exemple, lorsque le code de récupération d’erreur lui-même peut lancer et exception. Mais pour améliorer la lisibilité du code, vous pouvez toujours extraire le bloc nested dans une méthode propre. Consultez cet article pour plus d’exemples sur les blocs Try-Catch-Finally nesteds.