jette une exception dans les blocs finalement


100

Existe-t-il une manière élégante de gérer les exceptions qui sont lancées en finallybloc?

Par exemple:

try {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}
finally {
   try{
     resource.close();
   }
   catch( Exception ex ) {
     // Could not close the resource?
   }
}

Comment éviter le try/ catchdans le finallybloc?

Réponses:


72

Je le fais généralement comme ceci:

try {
  // Use the resource.
} catch( Exception ex ) {
  // Problem with the resource.
} finally {
  // Put away the resource.
  closeQuietly( resource );
}

Autre part:

protected void closeQuietly( Resource resource ) {
  try {
    if (resource != null) {
      resource.close();
    }
  } catch( Exception ex ) {
    log( "Exception during Resource.close()", ex );
  }
}

4
Oui, j'utilise un langage très similaire. Mais je ne crée pas de fonction pour ça.
OscarRyz

9
Une fonction est pratique si vous devez utiliser l'idiome à quelques endroits dans la même classe.
Darron le

Le contrôle de null est redondant. Si la ressource était nulle, la méthode d'appel est interrompue doit être corrigée. De plus, si la ressource est nulle, cela devrait probablement être consigné. Sinon, une exception potentielle est ignorée en silence.
Dave Jarvis

14
Le contrôle de null n'est pas toujours redondant. Pensez à "resource = new FileInputStream (" file.txt ")" comme première ligne de l'essai. De plus, cette question ne concernait pas une programmation orientée aspect que beaucoup de gens n'utilisent pas. Cependant, le concept selon lequel l'exception ne doit pas être simplement ignorée a été traité de manière plus compacte en affichant une instruction de journal.
Darron

1
Resource=> Closeable?
Dmitry Ginzburg

25

J'utilise généralement l'une des closeQuietlyméthodes suivantes org.apache.commons.io.IOUtils:

public static void closeQuietly(OutputStream output) {
    try {
        if (output != null) {
            output.close();
        }
    } catch (IOException ioe) {
        // ignore
    }
}

3
Vous pouvez rendre cette méthode plus générale avec Closeable public static void closeQuietly (Closeable closeable) {
Peter Lawrey

6
Oui, Closeable est agréable. C'est dommage que beaucoup de choses (comme les ressources JDBC) ne l'implémentent pas.
Darron le

22

Si vous utilisez Java 7 et resourceimplémentez AutoClosable, vous pouvez le faire (en utilisant InputStream comme exemple):

try (InputStream resource = getInputStream()) {
  // Use the resource.
}
catch( Exception ex ) {
  // Problem with the resource.
}

8

Sans doute un peu exagéré, mais peut-être utile si vous laissez les exceptions bouillonner et que vous ne pouvez rien enregistrer à partir de votre méthode (par exemple, parce que c'est une bibliothèque et que vous préférez laisser le code appelant gérer les exceptions et la journalisation):

Resource resource = null;
boolean isSuccess = false;
try {
    resource = Resource.create();
    resource.use();
    // Following line will only run if nothing above threw an exception.
    isSuccess = true;
} finally {
    if (resource != null) {
        if (isSuccess) {
            // let close throw the exception so it isn't swallowed.
            resource.close();
        } else {
            try {
                resource.close();
            } catch (ResourceException ignore) {
                // Just swallow this one because you don't want it 
                // to replace the one that came first (thrown above).
            }
        }
    }
}

MISE À JOUR: J'ai examiné cela un peu plus et j'ai trouvé un excellent article de blog de quelqu'un qui a clairement pensé à cela plus que moi: http://illegalargumentexception.blogspot.com/2008/10/java-how-not-to-make -mess-of-stream.html Il va encore plus loin et combine les deux exceptions en une seule, ce que je pourrais voir utile dans certains cas.


1
+1 pour le lien du blog. De plus, je voudrais au moins enregistrer l' ignoreexception
Denis Kniazhev

6

À partir de Java 7, vous n'avez plus besoin de fermer explicitement les ressources dans un bloc finally à la place, vous pouvez utiliser try la syntaxe -with-resources. L'instruction try-with-resources est une instruction try qui déclare une ou plusieurs ressources. Une ressource est un objet qui doit être fermé une fois que le programme en a terminé avec elle. L'instruction try-with-resources garantit que chaque ressource est fermée à la fin de l'instruction. Tout objet qui implémente java.lang.AutoCloseable, qui inclut tous les objets qui implémentent java.io.Closeable, peut être utilisé comme ressource.

Supposons le code suivant:

try( Connection con = null;
     Statement stmt = con.createStatement();
     Result rs= stmt.executeQuery(QUERY);)
{  
     count = rs.getInt(1);
}

Si une exception se produit, la clôture méthode sera appelée sur chacune de ces trois ressources dans l'ordre inverse dans lequel elles ont été créées. Cela signifie que la méthode close serait appelée en premier pour ResultSetm puis pour Statement et à la fin pour l'objet Connection.

Il est également important de savoir que toutes les exceptions qui se produisent lorsque les méthodes close sont automatiquement appelées sont supprimées. Ces exceptions supprimées peuvent être récupérées par la méthode getsuppressed () définie dans la classe Throwable .

Source: https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html


Il semble incomplet que cette réponse ne mentionne pas la différence de comportement entre cette approche et le fonctionnement de l'exemple de code publié par l'OP.
Nathan Hughes

2
l'utilisation de try-with-resources lève une exception à la fermeture si la partie du bloc try se termine normalement mais pas la méthode close, contrairement à ce que fait le code OP. le recommander en remplacement sans reconnaître le changement de comportement semble potentiellement trompeur.
Nathan Hughes

Il ne lève pas d'exception, la méthode close est automatiquement appelée et supprimée.
Soroosh

2
essayez le cas que j'ai décrit. try block se termine normalement, close lance quelque chose. et relisez la page sur laquelle vous avez publié le lien, la suppression ne s'applique que lorsque le bloc try lance quelque chose.
Nathan Hughes

3

Ignorer les exceptions qui se produisent dans un bloc «finalement» est généralement une mauvaise idée à moins que l'on ne sache quelles seront ces exceptions et quelles conditions elles représenteront. Dans le try/finallymodèle d'utilisation normal , le trybloc place les choses dans un état auquel le code extérieur ne s'attendra pas, et lefinally bloc restaure l'état de ces choses à ce que le code extérieur attend. Le code extérieur qui intercepte une exception s'attendra généralement à ce que, malgré l'exception, tout ait été restaurénormalEtat. Par exemple, supposons que du code démarre une transaction et tente ensuite d'ajouter deux enregistrements; le bloc "finalement" effectue une opération "rollback si non validée". Un appelant peut être préparé à ce qu'une exception se produise lors de l'exécution de la deuxième opération "ajouter" et peut s'attendre à ce que s'il détecte une telle exception, la base de données sera dans l'état où elle était avant que l'une ou l'autre des opérations ne soit tentée. Si, cependant, une deuxième exception se produit pendant la restauration, de mauvaises choses peuvent se produire si l'appelant émet des hypothèses sur l'état de la base de données. L'échec de la restauration représente une crise majeure - une crise qui ne devrait pas être interceptée par le code en s'attendant à une simple exception «Échec d'ajout d'enregistrement».

Mon inclination personnelle serait d'avoir une méthode enfin pour attraper les exceptions qui se produisent et les envelopper dans une "CleanupFailedException", en reconnaissant qu'un tel échec représente un problème majeur et qu'une telle exception ne doit pas être prise à la légère.


2

Une solution, si les deux exceptions sont deux classes différentes

try {
    ...
    }
catch(package1.Exception err)
   {
    ...
   }
catch(package2.Exception err)
   {
   ...
   }
finally
  {
  }

Mais parfois, vous ne pouvez pas éviter cette deuxième tentative. par exemple pour fermer un flux

InputStream in=null;
try
 {
 in= new FileInputStream("File.txt");
 (..)// do something that might throw an exception during the analysis of the file, e.g. a SQL error
 }
catch(SQLException err)
 {
 //handle exception
 }
finally
 {
 //at the end, we close the file
 if(in!=null) try { in.close();} catch(IOException err) { /* ignore */ }
 }

Dans votre cas, si vous avez utilisé une instruction "using", cela devrait nettoyer la ressource.
Chuck Conway

Mon mauvais, je suppose que c'est C #.
Chuck Conway

1

Pourquoi voulez-vous éviter le blocage supplémentaire? Puisque le bloc finally contient des opérations "normales" qui peuvent lever une exception ET que vous voulez que le bloc finally s'exécute complètement, vous DEVEZ intercepter les exceptions.

Si vous ne vous attendez pas à ce que le bloc finally lève une exception et que vous ne savez pas comment gérer l'exception de toute façon (vous ne feriez que vider la trace de la pile), laissez l'exception remonter la pile des appels (supprimez le try-catch du bloquer).

Si vous voulez réduire le typage, vous pouvez implémenter un bloc try-catch externe "global", qui capturera toutes les exceptions levées dans les blocs finally:

try {
    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }

    try {
        ...
    } catch (Exception ex) {
        ...
    } finally {
        ...
    }
} catch (Exception ex) {
    ...
}

2
-1 Pour celui-ci aussi. Que faire si vous essayez de fermer plusieurs ressources dans un seul bloc finally? Si la fermeture de la première ressource échoue, les autres resteront ouvertes une fois l'exception levée.
Programmeur hors

C'est pourquoi j'ai dit à Paul que vous DEVEZ attraper les exceptions si vous voulez vous assurer que le bloc finally se termine. Veuillez lire la réponse ENTIÈRE!
Eduard Wirch

1

Après beaucoup de réflexion, je trouve le code suivant le meilleur:

MyResource resource = null;
try {
    resource = new MyResource();
    resource.doSomethingFancy();
    resource.close(); 
    resource = null;  
} finally {
    closeQuietly(resource)
}

void closeQuietly(MyResource a) {
    if (a!=null)
        try {
             a.close();
        } catch (Exception e) {
             //ignore
        }
}

Ce code garantit ce qui suit:

  1. La ressource est libérée lorsque le code est terminé
  2. Les exceptions levées lors de la fermeture de la ressource ne sont pas consommées sans leur traitement.
  3. Le code n'essaye pas de fermer la ressource deux fois, aucune exception inutile ne sera créée.

Vous pouvez également éviter d'appeler resource.close (); resource = null dans le bloc try, c'est à cela que servent les blocs finally. Notez également que vous ne gérez aucune exception lancée en "faisant quelque chose de fantaisie", ce qui en fait, je pense que je préfère mieux, pour gérer les exceptions d'infrastructure à un niveau d'application plus élevé dans la pile.
Paul

Le resource.close () peut également lancer une exception, c'est-à-dire lorsque le vidage de la mémoire tampon échoue. Cette exception ne doit jamais être utilisée. Cependant, si vous fermez le flux suite à une exception précédemment déclenchée, la ressource doit être fermée silencieusement en ignorant l'exception et en préservant la cause première.
Grogi

0

Si vous le pouvez, vous devez tester pour éviter la condition d'erreur pour commencer.

try{...}
catch(NullArgumentException nae){...}
finally
{
  //or if resource had some useful function that tells you its open use that
  if (resource != null) 
  {
      resource.Close();
      resource = null;//just to be explicit about it was closed
  }
}

De plus, vous ne devriez probablement attraper que des exceptions dont vous pouvez récupérer, si vous ne pouvez pas récupérer, laissez-le se propager au niveau supérieur de votre programme. Si vous ne pouvez pas tester une condition d'erreur, vous devrez entourer votre code d'un bloc try catch comme vous l'avez déjà fait (même si je recommanderais toujours de détecter les erreurs spécifiques attendues).


Tester les conditions d'erreur est en général une bonne pratique, simplement parce que les exceptions coûtent cher.
Dirk Vollmar le

La «programmation défensive» est un paradigme dépassé. Le code gonflé qui résulte du test de toutes les conditions d'erreur cause finalement plus de problèmes qu'il n'en résout. TDD et la gestion des exceptions est cette approche moderne IMHO
Joe Soul-bringer le

@Joe - Je ne suis pas en désaccord avec vous sur le test de toutes les conditions d'erreur, mais parfois cela a du sens, surtout à la lumière de la différence (normalement) de coût d'une simple vérification pour éviter l'exception par rapport à l'exception elle-même.
Ken Henderson

1
-1 Ici, resource.Close () peut lever une exception. Si vous avez besoin de fermer des ressources supplémentaires, l'exception entraînera le retour de la fonction et elles resteront ouvertes. C'est le but du deuxième essai / capture dans l'OP.
Programmeur hors

@Outlaw - vous manquez mon point si Close lève une exception, et la ressource est ouverte alors en capturant et en supprimant l'exception comment puis-je corriger le problème? D'où pourquoi je l'ai laissé se propager (c'est assez rare que je puisse le récupérer avec encore ouvert).
Ken Henderson

0

Vous pouvez refactoriser cela dans une autre méthode ...

public void RealDoSuff()
{
   try
   { DoStuff(); }
   catch
   { // resource.close failed or something really weird is going on 
     // like an OutOfMemoryException 
   }
}

private void DoStuff() 
{
  try 
  {}
  catch
  {
  }
  finally 
  {
    if (resource != null) 
    {
      resource.close(); 
    }
  }
}

0

Je fais généralement ceci:

MyResource r = null;
try { 
   // use resource
} finally {   
    if( r != null ) try { 
        r.close(); 
    } catch( ThatSpecificExceptionOnClose teoc ){}
}

Justification: Si j'en ai fini avec la ressource et que le seul problème que j'ai est de la fermer, je ne peux pas y faire grand-chose. Cela n'a pas de sens non plus de tuer tout le fil si j'en ai fini avec la ressource de toute façon.

C'est l'un des cas où, du moins pour moi, il est prudent d'ignorer cette exception vérifiée.

À ce jour, je n'ai eu aucun problème à utiliser cet idiome.


Je l'enregistrerais, juste au cas où vous trouveriez des fuites dans le futur. De cette façon, vous saurez d'où ils pourraient (ne pas) venir
Egwor

@Egwor. Je suis d'accord avec toi. C'était juste un petit smippet. Je l'enregistre aussi et probablement utiliser une capture est quelque chose qui pourrait être fait à l'exception :)
OscarRyz

0
try {
    final Resource resource = acquire();
    try {
        use(resource);
    } finally {
        resource.release();
    }
} catch (ResourceException exx) {
    ... sensible code ...
}

Travail accompli. Aucun test nul. Prise unique, comprend les exceptions d'acquisition et de libération. Bien sûr, vous pouvez utiliser l'idiome Execute Around et ne devez l'écrire qu'une seule fois pour chaque type de ressource.


5
Que faire si use (resource) lève l'exception A, puis resource.release () lève l'exception B? L'exception A est perdue ...
Darron

0

Passer Resourcede la meilleure réponse àCloseable

Streams implémente CloseableAinsi, vous pouvez réutiliser la méthode pour tous les flux

protected void closeQuietly(Closeable resource) {
    if (resource == null) 
        return;
    try {
        resource.close();
    } catch (IOException e) {
        //log the exception
    }
}

0

J'ai rencontré une situation similaire où je ne pouvais pas utiliser try avec des ressources, mais je voulais également gérer l'exception provenant de la fermeture, pas seulement la journaliser et l'ignorer comme le fait le mécanisme closeQuietly. dans mon cas, je n'ai pas vraiment affaire à un flux de sortie, donc l'échec à la fermeture est plus intéressant qu'un simple flux.

IOException ioException = null;
try {
  outputStream.write("Something");
  outputStream.flush();
} catch (IOException e) {
  throw new ExportException("Unable to write to response stream", e);
}
finally {
  try {
    outputStream.close();
  } catch (IOException e) {
    ioException = e;
  }
}
if (ioException != null) {
  throw new ExportException("Unable to close outputstream", ioException);
}
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.