Une des choses qui me dérange toujours dans l'utilisation de Readers et Streams en Java est que la méthode close()
peut lever 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 car il n'y a qu'un seul bloc captif et cela me semble plus élégant. Y a-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 jettent que 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 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’abstraire avec l’idiome "exécuter autour".
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
s'exécute. 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 "écraser" la première exception (un peu comme mettre return
dans un bloc finally
) et vous perdrez toute information sur ce qui a réellement causé le problème pour commencer.
Votre bloc final 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 read()
et close()
lèvent des exceptions, l'une d'entre elles peut être perdue. Dans la première construction, l'exception de close()
remplace l'exception de read()
, tandis que dans la seconde, l'exception de close()
est traitée séparément.
A partir de Java 7, la construction { try-with-resources) simplifie beaucoup cette opération. 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 traitement exceptionnel:
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. La deuxième option gère donc davantage les erreurs.
Cependant, dans la plupart des cas, la première option sera toujours préférée.
Si vous avez besoin de passer toutes les exceptions générées, cela peut être fait .
La différence, à ce que je sache, est qu’il existe différentes exceptions et causes à différents niveaux, et
catch (Exception e)
obscurcit cela. Le seul point 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);
}
}
}
}
}
Remarquez l'exception "en attente" - cela prend en charge le cas où une exception levée lors de la fermeture masquerait une exception dont nous pourrions vraiment nous soucier.
Enfin, il essaie d’abord de fermer tout flux décoré. Par conséquent, si vous avez un BufferedWriter enveloppant un FileWriter, nous essayons de fermer d’abord le 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 traiter à nouveau avec la fermeture de fichiers.
Si cela est trop lourd pour votre utilisation, pensez au moins à suivre l'approche try/catch et la variable "en attente" qu'elle utilise.
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. Découvrez this blog post pour plus d'exemples sur les blocs imbriqués Try-Catch-Finally.
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.
Je voudrais toujours utiliser un enregistreur (log4j pour moi) pour enregistrer 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);
}
}
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, ce n'est pas 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 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 vois pas que vous attrapez 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
Donc, d'habitude, j'ai aussi le constructeur à l'intérieur du bloc try. Le bloc finally 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 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é.