Bloc finally en Java
Exécutez du code de nettoyage en Java avec les blocs finally qui s'exécutent toujours, qu'une exception ait été levée ou non.
Un bloc finally s'exécute quelle que soit la façon dont le try se termine — fin normale, exception interceptée, exception non interceptée, ou même un return anticipé. Cette garantie est sa raison d'être : c'est là que vous placez le code de nettoyage qui doit s'exécuter, absolument. Fermer un descripteur de fichier, libérer un verrou, restaurer l'état d'un thread — tout ce dont l'absence laisserait le programme dans un état pire que celui dans lequel il se trouvait.
La structure
try {
// risky code
} catch (SomeException e) {
// optional — zero or more catches
} finally {
// always runs after the try (and any matching catch)
}Vous pouvez associer finally à catch, sans aucun catch (try { ... } finally { ... }), ou avec plusieurs catches. Les éléments se combinent librement.
Ce que signifie « s'exécute toujours »
Un bloc finally s'exécute quand le contrôle quitte le try, quelle qu'en soit la raison :
- Fin normale du bloc
try—finallys'exécute après la dernière instruction. - Une exception levée depuis
try—finallys'exécute après la fin ducatchcorrespondant (ou, si aucun catch ne correspond, juste avant que l'exception se propage). returndanstryoucatch—finallys'exécute avant que le retour prenne réellement effet.breakoucontinuequi ferait sortir dutry—finallys'exécute avant le saut.
Les seules façons de contourner finally sont : la JVM elle-même meurt (System.exit, une coupure de courant, Runtime.halt), une boucle infinie ou un deadlock dans le try, ou Thread.stop (déprécié pour cette raison précise). Pour tout ce que vous écrivez dans du code applicatif normal, finally est une garantie absolue.
try {
return computeAnswer(); // even though there's a return here,
} finally {
cleanup(); // this runs before the method actually returns
}À quoi sert finally
La réponse honnête est : le nettoyage des ressources, presque toujours. Avant que Java 7 n'introduise try-with-resources, la forme canonique était :
InputStream in = null;
try {
in = new FileInputStream(path);
// read from in...
} catch (IOException e) {
// handle
} finally {
if (in != null) {
try { in.close(); } catch (IOException ignored) { /* */ }
}
}Ce try/catch imbriqué autour de close() dans le finally est le type de bruit que try-with-resources a été conçu pour éliminer. Nous le verrons dans le prochain chapitre. Mais comprendre à quoi finally sert permet de mieux comprendre la nouvelle construction.
Au-delà des ressources, finally est utile pour :
- Restaurer l'état partagé que vous avez modifié le temps du
try— incrémenter un compteur de profondeur, basculer un indicateur, échanger unThreadLocal. - Libérer des verrous acquis manuellement (
Lock.lock()→try { ... } finally { lock.unlock(); }). - Arrêter des minuteries ou fermer des transactions qui n'implémentent pas
AutoCloseable.
Ce à quoi finally ne sert pas
N'écrivez pas de logique produisant des résultats dans finally. Le bloc s'exécute quelle que soit l'issue — il ne sait pas si le try a réussi. Si vous mettez commit() dans finally, vous validerez même en cas d'échec.
Et ne faites pas de return depuis finally. C'est l'un des coins véritablement dangereux du langage :
try {
return 1;
} finally {
return 2; // wins — the method returns 2 and the original return is lost
}Le return (ou throw) dans finally écrase tout retour ou exception provenant du try. L'exception qui allait se propager est silencieusement ignorée. La plupart des outils d'analyse statique signalent cela comme une erreur pour cette raison. La règle : finally fait du nettoyage ; finally ne produit pas de valeurs.
Ordre d'exécution
Quand un catch et un finally sont tous deux présents :
try { ... }
catch { ... }
finally { ... }L'ordre est exactement celui que vous imaginez : try s'exécute, si une exception est levée et qu'un catch correspond ce catch s'exécute, et finally s'exécute après celui des deux qui s'est produit. Si finally lève ensuite une exception, ce nouveau throw remplace ce qui se propageait depuis le try ou le catch — une autre raison de garder finally discret.
Un exemple concret
Nous instrumentons une petite « transaction » avec try/catch/finally et l'appelons de trois façons différentes : succès normal, échec récupérable, et échec irrécupérable. Le finally s'exécute dans les trois cas, ce qui est tout l'intérêt.
Dans le troisième appel, doWork lève une RuntimeException que le catch local ne correspond pas. Le finally s'exécute quand même et affiche « release lock » avant que l'exception continue de remonter jusqu'à main. C'est la propriété que vous souhaitez d'un code de nettoyage — il ne dépend pas du succès ou de l'échec du traitement.
Et ensuite
La forme « ouvrir une ressource, travailler avec, la fermer dans finally » est si courante que Java a créé une instruction dédiée. Continuez vers Java try-with-resources.