Java essayer / attraper / enfin les meilleures pratiques lors de l’acquisition / fermeture des ressources

En travaillant sur un projet scolaire, j’ai écrit le code suivant:

FileOutputStream fos; ObjectOutputStream oos; try { fos = new FileOutputStream(file); oos = new ObjectOutputStream(fos); oos.writeObject(shapes); } catch (FileNotFoundException ex) { // complain to user } catch (IOException ex) { // notify user } finally { if (oos != null) oos.close(); if (fos != null) fos.close(); } 

Le problème est que Netbeans me dit que les lignes resource.close() lancent une IOException et doivent donc être capturées ou déclarées. Il se plaint également que oos et fos ne soient pas encore initialisés (malgré les vérifications nulles).

Cela semble un peu étrange, vu que le but est d’arrêter l’ IOException .

Mon correctif est de faire ceci:

 } finally { try { if (oos != null) oos.close(); if (fos != null) fos.close(); } catch (IOException ex) { } } 

Mais au fond, cela me dérange et me sent sale.

Je viens d’un arrière-plan C #, où je profite simplement d’un bloc d’ using , donc je ne suis pas sûr de la manière “correcte” de gérer cela.

Quelle est la bonne façon de gérer ce problème?

Si vous essayez d’attraper et de signaler toutes les exceptions à la source, une meilleure solution est la suivante:

 ObjectOutputStream oos = null; try { oos = new ObjectOutputStream(new FileOutputStream(file)); oos.writeObject(shapes); oos.flush(); } catch (FileNotFoundException ex) { // complain to user } catch (IOException ex) { // notify user } finally { if (oos != null) { try { oos.close(); } catch (IOException ex) { // ignore ... any significant errors should already have been // reported via an IOException from the final flush. } } } 

Remarques:

  • Les encapsuleurs Java standard, les lecteurs et les graveurs se propagent tous à close de leurs stream enveloppés, etc. Il suffit donc de fermer ou de vider le wrapper le plus à l’extérieur.
  • Le vidage explicite à la fin du bloc try a pour but de permettre au gestionnaire (réel) pour IOException de voir les échecs d’écriture 1 .
  • Lorsque vous effectuez une fermeture ou un vidage sur un stream de sortie, il existe une chance “qu’une fois dans une lune bleue” qu’une exception soit levée en raison d’erreurs de disque ou de saturation du système de fichiers. Vous ne devriez pas écraser cette exception! .

Si vous devez souvent “fermer un stream éventuellement nul en ignorant les exceptions IOEx”, vous pouvez alors écrire vous-même une méthode d’assistance comme ceci:

 public void closeQuietly(Closeable closeable) { if (closeable != null) { try { closeable.close(); } catch (IOException ex) { // ignore } } } 

alors vous pouvez remplacer le bloc last enfin par:

 } finally { closeQuietly(oos); } 

(Une autre réponse souligne qu’une méthode closeQuietly est déjà disponible dans une bibliothèque Apache Commons … si cela ne vous dérange pas d’append une dépendance à votre projet pour une méthode de 10 lignes. UPDATE : notez que ces méthodes sont obsolètes dans la version 2.6 de l’API.)

Mais faites attention à utiliser uniquement closeQuietly sur les stream où les exceptions IO ne sont vraiment pas pertinentes.

1 – Cela n’est pas nécessaire lorsque vous utilisez try-with-resources.


Sur la question de flush() versus close() , les gens s’interrogent sur:

  • Les stream de sortie et les graveurs standard “filter” et “buffered” ont un contrat API qui indique que close() provoque le vidage de toutes les sorties en mémoire tampon. Vous devriez constater que toutes les autres classes de sortie (standard) effectuant une mise en mémoire tampon de sortie se comporteront de la même manière. Donc, pour une classe standard, il est redondant d’appeler flush() immédiatement avant close() .
  • Pour les classes personnalisées et tierces, vous devez étudier (par exemple, lire le javadoc, regarder le code), mais toute méthode close() qui ne vide pas les données mises en mémoire tampon est sans doute cassée .
  • Enfin, il y a la question de ce que fait réellement flush() . OutputStream ce que dit le javadoc (pour OutputStream …)

    Si la destination de ce stream est une abstraction fournie par le système d’exploitation sous-jacent, par exemple un fichier, alors le vidage du stream garantit que seuls les octets précédemment écrits dans le stream sont transmis au système d’exploitation pour écriture; il ne garantit pas qu’ils sont réellement écrits sur un périphérique physique tel qu’un lecteur de disque.

    Donc … si vous espérez / imaginez que l’appel de flush() garantit que vos données vont persister, vous avez tort! (Si vous avez besoin de faire ce genre de chose, regardez la méthode FileChannel.force …)


D’un autre côté, si vous pouvez utiliser Java 7 ou une version ultérieure, la «nouvelle» tentative de ressources décrite dans la réponse de @Mike Clark constitue la meilleure solution.

Si vous n’utilisez pas Java 7 ou une version ultérieure pour votre nouveau code, vous êtes probablement dans une impasse et vous devez creuser plus profondément.

La meilleure pratique actuelle pour les objects try / catch / finally impliquant une fermeture (par exemple, fichiers) consiste à utiliser l’instruction try-with-resource de Java 7, par exemple:

 try (FileReader reader = new FileReader("ex.txt")) { System.out.println((char)reader.read()); } catch (IOException ioe) { ioe.printStackTrace(); } 

Dans ce cas, le FileReader est automatiquement fermé à la fin de l’instruction try, sans qu’il soit nécessaire de le fermer dans un bloc explicit explicite. Il y a quelques exemples ici:

http://ppkwok.blogspot.com/2012/11/java-cafe-2-try-with-resources.html

La description officielle de Java est à:

http://docs.oracle.com/javase/7/docs/technotes/guides/language/try-with-resources.html

Java 7 appenda des blocs de gestion automatique des ressources . Ils sont très similaires à l’utilisation de C #.

Josh Bloch a rédigé la proposition technique , que je recommande fortement de lire. Pas seulement parce que cela vous donnera une longueur d’avance sur une future fonctionnalité de langage Java 7, mais parce que la spécification motive le besoin d’une telle construction et illustre ainsi comment écrire du code correct même en l’absence d’ARM.

Voici un exemple de code Asker, traduit en forme ARM:

 try (FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos)) { oos.writeObject(shapes); } catch (FileNotFoundException ex) { // handle the file not being found } catch (IOException ex) { // handle some I/O problem } 

J’ai généralement une petite classe IOUtil avec une méthode telle que:

 public static void close(Closeable c) { if (c != null) { try { c.close(); } catch (IOException e) { // ignore or log } } } 

Qu’en est-il de ces gars? Pas de vérification nulle, pas de surprise. Tout est nettoyé à la sortie.

 try { final FileOutputStream fos = new FileOutputStream(file); try { final ObjectOutputStream oos = new ObjectOutputStream(fos); try { oos.writeObject(shapes); oos.flush(); } catch(IOException ioe) { // notify user of important exception } finally { oos.close(); } } finally { fos.close(); } } catch (FileNotFoundException ex) { // complain to user } catch (IOException ex) { // notify user } 

Malheureusement, il n’y a pas de support de niveau de langue. Mais il y a beaucoup de bibliothèques qui rendent cela simple. Vérifiez la bibliothèque commun-io. Ou google-guava moderne @ http://guava-libraries.googlecode.com/svn/trunk/javadoc/index.html

Tu le fais bien. Cela me dérange aussi. Vous devez initialiser ces stream pour qu’ils soient explicitement nuls – c’est une convention courante. Tout ce que vous pouvez faire, c’est rejoindre le club et vouloir l’ using .

Ce n’est pas une réponse directe à votre question, mais il est regrettable que, finally , catch et catch deux soient associés à des try gens pensent qu’ils appartiennent ensemble. La meilleure conception pour les blocs d’ try est d’avoir une catch ou un finally mais pas les deux.

Dans ce cas, vos commentaires suggèrent que quelque chose ne va pas. Pourquoi, dans une méthode traitant des fichiers IO, nous nous plaignons de tout pour l’utilisateur. Nous sums peut-être en train de courir sur un serveur quelque part sans un utilisateur en vue.

Ainsi, le code que vous présentez ci-dessus devrait finally échouer de manière gracieuse lorsque les choses tournent mal. Cependant, il lui manque la capacité de traiter intelligemment les erreurs, de sorte que votre catch appartient quelque part plus haut sur la chaîne d’appels.