web-dev-qa-db-fra.com

Java try / catch / finalement les meilleures pratiques lors de l'acquisition / fermeture des ressources

En travaillant sur un projet d'école, 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() jettent un 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 le IOException juste là.

Ma solution de genou 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 voudrais simplement profiter d'un bloc using, donc je ne sais pas quelle est la "bonne" façon de gérer cela.

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

40
Austin Hyde

Si vous essayez de détecter 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:

  • Le standard Java wrapper propagent tous close et flush vers leurs flux encapsulés, etc. Il vous suffit donc de fermer ou de vider le plus externe emballage.
  • Le but du vidage explicite à la fin du bloc try est pour que le (vrai) gestionnaire de IOException puisse voir les échecs d'écriture1.
  • Lorsque vous effectuez une fermeture ou un vidage sur un flux de sortie, il y a une chance "une fois dans une lune bleue" qu'une exception soit levée en raison d'erreurs de disque ou d'un système de fichiers plein. Vous ne devez pas écraser cette exception! .

Si vous devez souvent "fermer un flux éventuellement nul en ignorant les IOExceptions", alors vous pouvez vous écrire une méthode d'aide comme celle-ci:

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

alors vous pouvez remplacer le bloc finally précédent 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'ajouter 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 veillez à n'utiliser closeQuietly que sur les flux où IO exceptions vraiment ne sont pas pertinents.

1 - Cela n'est pas nécessaire lors de l'utilisation de try-with-resources.


Sur la question de flush() versus close() que les gens demandent:

  • Les flux de sortie et les écrivains standard "filtre" et "tamponné" ont un contrat d'API qui stipule que close() provoque le vidage de toutes les sorties tamponnées. Vous devriez constater que toutes les autres classes de sortie (standard) qui mettent en mémoire tampon de sortie se comporteront de la même manière. Ainsi, pour une classe standard, il est redondant d'appeler flush() juste avant close().
  • Pour les classes personnalisées et tierces, vous devez enquêter (par exemple lire le javadoc, regarder le code), mais toute méthode close() qui ne vide pas les données tamponnées est sans doute cassé .
  • Enfin, il y a la question de ce que flush() fait réellement. Voici ce que dit le javadoc (pour OutputStream ...)

    Si la destination prévue de ce flux est une abstraction fournie par le système d'exploitation sous-jacent, par exemple un fichier, alors le vidage du flux garantit uniquement que les octets précédemment écrits dans le flux sont transmis au système d'exploitation pour l'écriture; cela 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 la persistance de vos données, vous vous trompez! (Si vous avez besoin de faire ce genre de chose, regardez le FileChannel.force Méthode ...)


D'un autre côté, si vous pouvez utiliser Java 7 ou version ultérieure, le "nouveau" try-with-resources tel que décrit dans la réponse de @Mike Clark est la meilleure solution.

Si vous n'utilisez pas Java 7 ou version ultérieure pour votre nouveau code, vous êtes probablement dans un trou profond et vous creusez plus profondément.

53
Stephen C

La meilleure pratique actuelle pour essayer/attraper/impliquer finalement des objets pouvant être fermés (par exemple, des fichiers) est d'utiliser Java 7 l'instruction try-with-resource de Java, 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 finally explicite. Voici quelques exemples:

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

La description officielle Java est à:

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

25
Phil

Java 7 ajoutera des blocs Automatic Resource Management . Ils sont très similaires aux using de C #.

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

Voici un exemple du code d'Asker, traduit en 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
}
13
Mike Clark

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

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

Et les gars? Pas de chèque nul, 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
}
3
RAY

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

1

Tu le fais bien. Cela me dérange aussi. Vous devez initialiser ces flux à null explicitement - c'est une convention courante. Tout ce que vous pouvez faire est de rejoindre le club et de vouloir using.

1
Mike

Ce n'est pas une réponse directe à votre argument mais c'est un fait malheureux parce que finally et catch sont tous les deux associés à try les gens pensent qu'ils appartiennent ensemble. La meilleure conception pour les blocs try est d'avoir un 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 E/S de fichiers, nous plaignons-nous de quoi que ce soit à l'utilisateur. Nous pourrions être en train de courir en profondeur sur un serveur quelque part avec aucun utilisateur en vue.

Ainsi, le code que vous présentez ci-dessus devrait avoir un finally pour échouer gracieusement lorsque les choses tournent mal. Cependant, il n'a pas la capacité de traiter intelligemment les erreurs, donc votre catch appartient quelque part plus haut dans la chaîne d'appel.

0
CurtainDog