Table of Contents
Introduction
La gestion des ressources et la suppression d’exceptions sont deux concepts essentiels pour les developpeurs Java. Avant Java 7, la fermeture correcte des ressources (fichiers, connexions reseau, flux de donnees) etait une source frequente de bugs et de fuites de memoire. Le code etait verbeux, complexe et prone aux erreurs.
Avec l’introduction du constructeur try-with-resources dans Java 7, la gestion des ressources est devenue beaucoup plus simple et sure. Mais un aspect souvent meconnu de ce mecanisme est la suppression d’exceptions (exception suppression) : que se passe-t-il lorsque plusieurs exceptions sont levees simultanement ?
Dans ce guide complet, nous allons explorer :
- Le mecanisme
try-with-resourceset son fonctionnement interne - Le concept de suppression d’exceptions et comment y acceder
- La comparaison avec l’approche classique
try-catch-finally - Les bonnes pratiques et les pieges a eviter
Que vous soyez un developpeur Java debutant ou experimente, comprendre ces mecanismes vous aidera a ecrire du code plus robuste et maintenable.
Le Constructeur try-with-resources
Le constructeur try-with-resources a ete introduit dans Java 7 pour simplifier la gestion des ressources. Il permet aux developpeurs de definir plusieurs ressources dans une seule structure try, ce qui est particulierement utile lorsqu’il faut gerer plusieurs ressources au cours d’une operation.
Syntaxe de base
Voici un exemple simple :
try (Writer w = new BufferedWriter(new FileWriter(someFilename))) {
w.write("Hello, World!");
// La ressource sera automatiquement fermee
}
Dans cet exemple, le constructeur try-with-resources assure que la ressource w est fermee automatiquement, meme si une exception est levee.
Gestion de plusieurs ressources
Vous pouvez declarer plusieurs ressources separees par des points-virgules :
try (FileInputStream fis = new FileInputStream("input.txt");
FileOutputStream fos = new FileOutputStream("output.txt");
BufferedReader reader = new BufferedReader(new InputStreamReader(fis))) {
String line;
while ((line = reader.readLine()) != null) {
fos.write(line.getBytes());
fos.write('\n');
}
}
// Toutes les ressources sont fermees dans l'ordre inverse de leur declaration
L’interface AutoCloseable
Pour qu’une ressource puisse etre utilisee avec try-with-resources, elle doit implementer l’interface AutoCloseable :
public class MaRessource implements AutoCloseable {
private String nom;
public MaRessource(String nom) {
this.nom = nom;
System.out.println("Ressource " + nom + " ouverte");
}
public void utiliser() {
System.out.println("Utilisation de " + nom);
}
@Override
public void close() throws Exception {
System.out.println("Ressource " + nom + " fermee");
}
}
// Utilisation
try (MaRessource r1 = new MaRessource("R1");
MaRessource r2 = new MaRessource("R2")) {
r1.utiliser();
r2.utiliser();
}
// Sortie :
// Ressource R1 ouverte
// Ressource R2 ouverte
// Utilisation de R1
// Utilisation de R2
// Ressource R2 fermee
// Ressource R1 fermee
Le Concept de Suppression d’Exceptions
Lorsqu’une exception est levee pendant l’execution du bloc try, le constructeur try-with-resources assure que toutes les ressources sont fermees avant de lever l’exception. Mais que se passe-t-il si une seconde exception est levee pendant la fermeture des ressources ?
C’est la que le mecanisme de suppression d’exceptions entre en jeu. L’exception levee lors de la fermeture sera “supprimee” et attachee a l’exception principale.
Exemple concret
public class RessourceDefaillante implements AutoCloseable {
private String nom;
public RessourceDefaillante(String nom) {
this.nom = nom;
}
public void operation() {
throw new RuntimeException("Erreur dans operation() de " + nom);
}
@Override
public void close() {
throw new RuntimeException("Erreur dans close() de " + nom);
}
}
// Test du mecanisme de suppression
try (RessourceDefaillante r = new RessourceDefaillante("R1")) {
r.operation(); // Leve une exception
} catch (RuntimeException e) {
System.out.println("Exception principale : " + e.getMessage());
// Recuperer les exceptions supprimees
Throwable[] supprimees = e.getSuppressed();
for (Throwable t : supprimees) {
System.out.println("Exception supprimee : " + t.getMessage());
}
}
// Sortie :
// Exception principale : Erreur dans operation() de R1
// Exception supprimee : Erreur dans close() de R1
Acces aux exceptions supprimees
La methode getSuppressed() de la classe Throwable permet de recuperer toutes les exceptions supprimees :
try {
try (RessourceDefaillante r1 = new RessourceDefaillante("R1");
RessourceDefaillante r2 = new RessourceDefaillante("R2")) {
r1.operation();
}
} catch (Exception e) {
System.out.println("Exception principale : " + e.getMessage());
Throwable[] supprimees = e.getSuppressed();
System.out.println("Nombre d'exceptions supprimees : " + supprimees.length);
for (int i = 0; i < supprimees.length; i++) {
System.out.println(" [" + i + "] " + supprimees[i].getMessage());
}
}
Ajout manuel d’exceptions supprimees
Vous pouvez egalement ajouter manuellement des exceptions supprimees avec addSuppressed() :
Exception principale = new Exception("Erreur principale");
principale.addSuppressed(new Exception("Erreur secondaire 1"));
principale.addSuppressed(new Exception("Erreur secondaire 2"));
throw principale;
Comparaison : try-with-resources vs try-catch-finally
L’approche classique avec try-catch-finally
Avant Java 7, la gestion des ressources se faisait avec le bloc finally :
BufferedReader reader = null;
try {
reader = new BufferedReader(new FileReader("fichier.txt"));
String ligne = reader.readLine();
// Traitement...
} catch (IOException e) {
System.err.println("Erreur de lecture : " + e.getMessage());
} finally {
if (reader != null) {
try {
reader.close();
} catch (IOException e) {
// Que faire ici ? L'exception de fermeture est perdue !
System.err.println("Erreur de fermeture ignoree");
}
}
}
Le probleme de la perte d’exceptions
Avec l’approche classique, si une exception est levee dans le bloc try ET une autre dans le bloc finally, l’exception du finally ecrase celle du try :
// PROBLEME : l'exception originale est perdue !
try {
throw new Exception("Exception importante du try");
} finally {
throw new Exception("Exception du finally"); // Celle-ci est propagee
}
Solution avec try-with-resources
Le mecanisme de suppression resout ce probleme :
try (MaRessource r = new MaRessource()) {
throw new Exception("Exception importante du try");
} // Si close() leve une exception, elle sera supprimee, pas perdue
Tableau comparatif
| Aspect | try-catch-finally | try-with-resources |
|---|---|---|
| Fermeture automatique | Non | Oui |
| Ordre de fermeture | Manuel | Inverse de declaration |
| Exceptions multiples | Perte possible | Suppression |
| Lisibilite | Verbeux | Concis |
| Null-safety | Verification manuelle | Automatique |
Bonnes Pratiques
1. Toujours preferer try-with-resources
// BON : try-with-resources
try (Connection conn = dataSource.getConnection();
PreparedStatement stmt = conn.prepareStatement(sql);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
// Traitement
}
}
// MAUVAIS : try-catch-finally verbeux et error-prone
Connection conn = null;
PreparedStatement stmt = null;
ResultSet rs = null;
try {
conn = dataSource.getConnection();
stmt = conn.prepareStatement(sql);
rs = stmt.executeQuery();
// ...
} finally {
// Fermeture manuelle penible
}
2. Logger les exceptions supprimees
try (MaRessource r = new MaRessource()) {
r.operation();
} catch (Exception e) {
logger.error("Erreur principale : {}", e.getMessage(), e);
// Ne pas oublier les exceptions supprimees !
for (Throwable suppressed : e.getSuppressed()) {
logger.warn("Exception supprimee : {}", suppressed.getMessage(), suppressed);
}
}
3. Implementer close() de maniere idempotente
public class RessourceSure implements AutoCloseable {
private boolean fermee = false;
@Override
public void close() {
if (!fermee) {
fermee = true;
// Logique de fermeture
}
// Appels multiples a close() sont sans effet
}
}
4. Utiliser les ressources Java 9+ deja declarees
A partir de Java 9, vous pouvez utiliser des variables effectivement finales :
BufferedReader reader = new BufferedReader(new FileReader("fichier.txt"));
try (reader) { // Java 9+
// Utilisation
}
Pieges Courants
1. Oublier de verifier getSuppressed()
// PIEGE : Les exceptions supprimees sont ignorees
try (RessourceDefaillante r = new RessourceDefaillante()) {
r.operation();
} catch (Exception e) {
// MAUVAIS : On ne voit que l'exception principale
System.err.println("Erreur : " + e.getMessage());
// SOLUTION : Toujours verifier les exceptions supprimees
if (e.getSuppressed().length > 0) {
System.err.println("Attention : " + e.getSuppressed().length +
" exception(s) supprimee(s)");
}
}
2. Ordre de fermeture inattendu
// Les ressources sont fermees dans l'ORDRE INVERSE
try (Ressource r1 = new Ressource("Premier"); // Ferme en dernier
Ressource r2 = new Ressource("Deuxieme"); // Ferme en deuxieme
Ressource r3 = new Ressource("Troisieme")) { // Ferme en premier
// ...
}
// Ordre de fermeture : r3 -> r2 -> r1
3. Ressources dependantes mal ordonnees
// PIEGE : Si r2 depend de r1, r1 sera ferme apres r2
try (Connection conn = getConnection();
Statement stmt = conn.createStatement()) { // OK : stmt depend de conn
// stmt sera ferme avant conn
}
// MAUVAIS : Ordre inverse
try (Statement stmt = conn.createStatement(); // ERREUR potentielle
Connection conn = getConnection()) {
// conn sera ferme avant stmt !
}
4. Exception dans le constructeur de ressource
try (Ressource r1 = new Ressource(); // OK
Ressource r2 = new RessourceQuiEchoue()) { // Leve exception
// Ce bloc n'est jamais execute
} // r1 sera quand meme fermee correctement !
Conclusion
La gestion des ressources et la suppression d’exceptions sont des mecanismes essentiels pour ecrire du code Java robuste et maintenable.
Points cles a retenir :
- Utilisez toujours try-with-resources pour gerer les ressources qui implementent
AutoCloseable - Comprenez le mecanisme de suppression : l’exception principale est preservee, les exceptions de fermeture sont ajoutees via
addSuppressed() - Verifiez systematiquement
getSuppressed()lors du traitement des exceptions pour ne pas perdre d’informations de diagnostic - L’ordre de declaration compte : les ressources sont fermees dans l’ordre inverse de leur declaration
- Implementez
close()de maniere idempotente pour eviter les problemes lors d’appels multiples
En adoptant ces bonnes pratiques, vous eviterez les fuites de ressources et les bugs difficiles a diagnostiquer lies a la perte d’exceptions.
Ressources supplementaires
In-Article Ad
Dev Mode
Tags
Mahmoud DEVO
Senior Full-Stack Developer
I'm a passionate full-stack developer with 10+ years of experience building scalable web applications. I write about Vue.js, Node.js, PostgreSQL, and modern DevOps practices.
Enjoyed this article?
Subscribe to get more tech content delivered to your inbox.
Related Articles
Gestion des Exceptions en Java : Try-With-Resources et Exceptions Personnalisees
Maitrisez la gestion des exceptions Java : try-with-resources, exceptions personnalisees et InterruptedException. Guide complet avec exemples de code et bonnes pratiques pour un code robuste et maintenable.
Gestion des Exceptions en Java : Evitez les Pieges Courants et les Blocages
Guide complet sur la gestion des exceptions en Java. Apprenez a eviter les deadlocks, gerer les interruptions de threads, utiliser correctement les stacktraces et maitriser les bonnes pratiques de programmation defensive.
Manipuler les classes Java avec ASM et Javassist : bytecode, instrumentation et fichiers JAR
Apprenez a manipuler les classes Java avec ASM et Javassist : chargement, modification du bytecode, instrumentation et creation de fichiers JAR.