Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 29

Gestion Automatique des Ressources avec `try-with-resources`

Recommended resources

Bonjour ! Dans la leçon précédente, nous avons appris à maîtriser les exceptions avec les blocs try, catch et finally. Nous avons particulièrement insisté sur le rôle crucial du bloc finally pour libérer des ressources externes (fichiers, connexions réseau, etc.), une tâche omniprésente en développement backend. Vous avez dû remarquer que cette gestion manuelle, bien que fonctionnelle, peut vite devenir verbeuse et sujette à erreurs, notamment avec la nécessité de vérifier si la ressource est nulle et d'imbriquer un autre try-catch pour l'opération de fermeture.

Aujourd'hui, nous allons découvrir une solution bien plus élégante et sûre. L'objectif de cette leçon est d'apprendre à utiliser le bloc try-with-resources. Introduite en Java 7, cette syntaxe automatise la gestion des ressources, rendant votre code plus lisible, plus concis et surtout plus robuste en prévenant les fuites de ressources. C'est la méthode standard que vous utiliserez au quotidien en tant que développeur Java professionnel.

1. Le problème du try-finally en pratique

Pour bien saisir l'avantage du try-with-resources, il est essentiel de se remémorer la complexité de l'approche traditionnelle avec finally. La vidéo suivante illustre parfaitement ce point en construisant, pas à pas, une méthode d'écriture dans un fichier avec try-catch-finally.

TUTO Java : Comparaison entre try/finally et le try-with-resources

La vidéo "TUTO Java : Comparaison entre try/finally et le try-with-resources" de Dominique Liard commence par un excellent exemple du code que nous cherchons à améliorer.

Regardez attentivement le segment Implémentation avec try-finally. Observez les différentes étapes nécessaires pour garantir la fermeture correcte du fichier : La déclaration de la variable de ressource (FileOutputStream) en dehors du bloc try et son initialisation à null. Le test if (fos != null) dans le bloc finally. L'ajout d'un bloc try-catch à l'intérieur du bloc finally pour gérer une éventuelle IOException lors de l'appel à fos.close(). Toute cette "plomberie" est une source courante de bugs et rend le code difficile à lire. 2. La solution : le try-with-resources Le try-with-resources a été conçu spécifiquement pour éliminer toute cette complexité. La syntaxe est simple : on déclare et initialise la ressource directement entre des parenthèses après le mot-clé try.

Toute cette "plomberie" est une source courante de bugs et rend le code difficile à lire.


2. La solution : le try-with-resources

Le try-with-resources a été conçu spécifiquement pour éliminer toute cette complexité. La syntaxe est simple : on déclare et initialise la ressource directement entre des parenthèses après le mot-clé try.

try (Ressource res = new Ressource()) {
    // Utilisation de la ressource 'res'
} catch (Exception e) {
    // Gestion des erreurs
}
// 'res.close()' est appelé automatiquement ici !

Le grand avantage est que la machine virtuelle Java (JVM) garantit que la méthode close() de la ressource sera appelée automatiquement à la fin du bloc try, que son exécution se termine normalement ou par une exception.

Reprenons la vidéo précédente pour voir la transformation.

TUTO Java : Comparaison entre try/finally et le try-with-resources

La même vidéo montre maintenant comment réécrire l'exemple précédent avec la nouvelle syntaxe.

Visionnez maintenant la partie Implémentation avec try-with-resources. Comparez la concision et la clarté de ce nouveau code par rapport au précédent. Le bloc finally a complètement disparu, et avec lui, toute la logique complexe de fermeture manuelle.

L'article ci-dessous, qui correspond à la vidéo, présente une comparaison directe sous forme de code côte à côte, ce qui est très parlant.

Comparaison entre try/finally et le try-with-resources - KooR.fr

Cet article du site Koor.fr (par le même auteur que la vidéo) met en évidence l'amélioration de la lisibilité.

Lisez la section "Comparaison entre try/finally et le try-with-resources". Portez une attention particulière aux deux blocs de code qui effectuent une copie de fichier. Le second, utilisant try-with-resources, est presque un tiers plus court et bien plus simple à comprendre. Notez aussi que la portée des variables de ressources (fis et fos) est maintenant limitée au bloc try, ce qui est une meilleure pratique.


3. Comment ça marche ? L'interface AutoCloseable

Cette fermeture automatique n'est pas magique. Elle repose sur un contrat défini par une interface : java.lang.AutoCloseable.

Pour qu'une classe puisse être utilisée dans un bloc try-with-resources, elle doit implémenter l'interface AutoCloseable. Cette interface ne contient qu'une seule méthode :
void close() throws Exception;

Ainsi, lorsque la JVM quitte le bloc try, elle sait qu'elle peut (et doit) appeler la méthode close() sur la ressource. La plupart des classes de ressources standards en Java (gestion de fichiers, connexions JDBC, sockets réseau) implémentent déjà cette interface.

Ce diagramme montre que des classes fondamentales comme `Writer` (et ses dérivés comme `BufferedWriter`) implémentent l'interface `Closeable`, qui elle-même étend `AutoCloseable`. C'est cette hiérarchie qui rend le `try-with-resources` si largement applicable dans l'écosystème Java.

Ce diagramme montre que des classes fondamentales comme Writer (et ses dérivés comme BufferedWriter) implémentent l'interface Closeable, qui elle-même étend AutoCloseable. C'est cette hiérarchie qui rend le try-with-resources si largement applicable dans l'écosystème Java.

On peut aussi créer nos propres classes "auto-fermables", ce qui est une pratique courante pour encapsuler la gestion de ressources spécifiques.

Cet exemple montre une classe `HBaseUtil` qui gère une connexion à une base de données HBase. En implémentant `AutoCloseable` et en plaçant la logique de fermeture dans la méthode `close()`, on peut ensuite l'utiliser de manière sûre et simple avec un `try-with-resources`, comme le montre la méthode `main`.

Cet exemple montre une classe HBaseUtil qui gère une connexion à une base de données HBase. En implémentant AutoCloseable et en plaçant la logique de fermeture dans la méthode close(), on peut ensuite l'utiliser de manière sûre et simple avec un try-with-resources, comme le montre la méthode main.

La vidéo suivante explique ce mécanisme en détail en créant une classe de démonstration.

TUTO Java : Comparaison entre try/finally et le try-with-resources

Retournons à la vidéo de Dominique Liard pour comprendre le mécanisme sous-jacent.

Regardez la section Explication de l'interface AutoCloseable. L'auteur y crée une classe Demo qui implémente AutoCloseable. Observez comment l'échec de la compilation, lorsque l'interface n'est pas implémentée, démontre bien que c'est un contrat strict imposé par le langage.


4. Gérer plusieurs ressources

Un autre avantage majeur du try-with-resources est la facilité avec laquelle on peut gérer plusieurs ressources. Il suffit de les déclarer à la suite, séparées par un point-virgule.

try (
    FileInputStream in = new FileInputStream("source.txt");
    FileOutputStream out = new FileOutputStream("destination.txt")
) {
    // Copier les données de 'in' vers 'out'
} catch (IOException e) {
    e.printStackTrace();
}
// 'out.close()' sera appelé, puis 'in.close()' sera appelé.

Java garantit que les ressources seront fermées dans l'ordre inverse de leur déclaration. C'est important car certaines ressources peuvent dépendre les unes des autres.

La vidéo aborde également les cas d'usage les plus courants pour un développeur backend, notamment l'accès aux bases de données avec JDBC, qui sera un sujet central de notre formation.

TUTO Java : Comparaison entre try/finally et le try-with-resources

La vidéo présente une liste de classes standards qui bénéficient de cette syntaxe, ainsi qu'un exemple avancé avec JDBC.

Regardez d'abord la liste des classes AutoCloseable courantes. Vous y retrouverez les entrées/sorties de fichiers (FileInputStream), les sockets réseau et, surtout, les objets JDBC (Connection, Statement, ResultSet). Ensuite, regardez l'exemple avancé avec JDBC. Ne vous inquiétez pas si vous ne comprenez pas toute la syntaxe JDBC, nous y reviendrons en détail. L'important est de voir comment les try-with-resources peuvent être imbriqués pour gérer proprement une connexion, une requête et un jeu de résultats, rendant le code à la fois lisible et sûr. 5. Exercice : Refactoriser vers try-with-resources Mettons cela en pratique. Voici une méthode qui lit la première ligne d'un fichier texte en utilisant l'approche try-catch-finally. Votre tâche est de la réécrire en utilisant try-with-resources pour la rendre plus propre et plus sûre. Code de départ :


5. Exercice : Refactoriser vers try-with-resources

Mettons cela en pratique. Voici une méthode qui lit la première ligne d'un fichier texte en utilisant l'approche try-catch-finally. Votre tâche est de la réécrire en utilisant try-with-resources pour la rendre plus propre et plus sûre.

Code de départ :

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderLegacy {

    public String readFirstLine(String path) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader(path));
            return br.readLine();
        } catch (IOException e) {
            System.err.println("Erreur lors de la lecture du fichier : " + e.getMessage());
            return null;
        } finally {
            try {
                if (br != null) {
                    br.close();
                    System.out.println("BufferedReader fermé.");
                }
            } catch (IOException ex) {
                System.err.println("Erreur lors de la fermeture du BufferedReader : " + ex.getMessage());
            }
        }
    }

    public static void main(String[] args) {
        // Créez un fichier "test.txt" à la racine de votre projet avec du contenu
        // pour tester la méthode.
        FileReaderLegacy reader = new FileReaderLegacy();
        String firstLine = reader.readFirstLine("test.txt");
        if (firstLine != null) {
            System.out.println("Première ligne : " + firstLine);
        }
    }
}

Votre objectif :

  1. Créez une nouvelle classe FileReaderModern.
  2. Copiez-y la méthode readFirstLine.
  3. Modifiez cette méthode pour utiliser un bloc try-with-resources afin de gérer le BufferedReader.
  4. Le bloc finally doit disparaître. La gestion de l'exception IOException dans le catch doit être conservée.
Cliquez ici pour voir la solution
import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderModern {

    public String readFirstLine(String path) {
        // Le BufferedReader est déclaré dans les parenthèses du try.
        try (BufferedReader br = new BufferedReader(new FileReader(path))) {
            // br.close() sera appelé automatiquement.
            return br.readLine();
        } catch (IOException e) {
            System.err.println("Erreur lors de la lecture ou fermeture du fichier : " + e.getMessage());
            return null;
        }
    }

    public static void main(String[] args) {
        FileReaderModern reader = new FileReaderModern();
        String firstLine = reader.readFirstLine("test.txt");
        if (firstLine != null) {
            System.out.println("Première ligne : " + firstLine);
        }
    }
}

Comparez les deux versions. La version moderne est non seulement plus courte, mais elle est aussi sémantiquement plus claire : l'intention de gérer une ressource est déclarée dès le début du bloc try.


Conclusion

Aujourd'hui, vous avez ajouté un outil puissant et essentiel à votre boîte à outils de développeur Java. Le try-with-resources est la norme pour la gestion des ressources.

Les points clés à retenir :

  • Le try-with-resources automatise la fermeture des ressources, ce qui prévient les fuites et simplifie le code.
  • Il fonctionne avec toute classe qui implémente l'interface java.lang.AutoCloseable.
  • Il remplace avantageusement le bloc finally dans la majorité des cas de gestion de ressources.
  • Il rend le code plus lisible en déclarant la ressource et son cycle de vie au même endroit.

Dans la leçon précédente, nous avons vu comment "attraper" les exceptions. Aujourd'hui, nous avons vu comment gérer proprement les ressources associées. Dans la prochaine leçon, nous allons aborder l'autre côté du miroir : comment créer et lancer nos propres exceptions avec throw, et comment les déclarer avec throws. C'est une compétence clé pour construire des couches de service et des API qui communiquent clairement les erreurs métier à leurs appelants.