Chaînes Immuables en Programmation Java

JavaBeginner
Pratiquer maintenant

Introduction

Dans ce laboratoire, vous découvrirez les avantages de l'utilisation de chaînes de caractères immuables (immutable Strings) en programmation Java. Vous comprendrez pourquoi les chaînes de caractères en Java sont immuables par défaut et comment cette caractéristique peut améliorer les performances globales et la sécurité de vos applications. À la fin de ce laboratoire, vous serez capable de créer et d'utiliser efficacement des chaînes de caractères immuables dans vos programmes Java.

Créer un Fichier de Projet Java

Dans cette étape, vous allez créer un nouveau fichier Java nommé ImmutableStringDemo.java dans votre répertoire ~/project. Ce fichier contiendra tout le code Java pour ce laboratoire.

Tout d'abord, assurez-vous d'être dans le répertoire ~/project. Vous pouvez vérifier votre répertoire actuel en utilisant la commande pwd :

pwd

Vous devriez voir une sortie similaire à celle-ci :

/home/labex/project

Maintenant, créez le fichier ImmutableStringDemo.java en utilisant la commande touch :

touch ImmutableStringDemo.java

Après avoir créé le fichier, vous pouvez l'ouvrir dans l'éditeur WebIDE pour commencer à écrire votre code Java.

Comprendre l'Immuabilité des Chaînes et le String Pool en Java

En Java, les objets String sont immuables, ce qui signifie que leur valeur ne peut pas être modifiée après leur création. Ce choix de conception offre plusieurs avantages, notamment la sécurité, la sûreté des threads (thread safety) et des optimisations de performance grâce au String pool.

Le String pool est une zone mémoire spéciale au sein du tas (heap) qui stocke les littéraux de chaînes de caractères (String literals). Lorsque vous créez un littéral de chaîne, Java vérifie d'abord si une chaîne identique existe déjà dans le pool. Si c'est le cas, la référence de la chaîne existante est retournée, ce qui économise de la mémoire. Sinon, une nouvelle chaîne est créée dans le pool et sa référence est retournée.

Ajoutons la structure de base d'une classe Java et démontrons la création de chaînes de caractères. Ouvrez ImmutableStringDemo.java dans le WebIDE et ajoutez le code suivant :

// ~/project/ImmutableStringDemo.java
public class ImmutableStringDemo {

    public static void main(String[] args) {
        // Création d'un littéral de chaîne
        String name = "LabexUser";
        System.out.println("Nom initial : " + name);

        // Création de chaînes qui peuvent utiliser le String pool
        String str1 = "Hello";
        String str2 = "Hello"; // Ceci fera probablement référence au même objet que str1 dans le String pool

        System.out.println("str1 : " + str1);
        System.out.println("str2 : " + str2);
        System.out.println("str1 == str2 : " + (str1 == str2)); // Vérifie s'ils font référence au même objet

        // Création d'une chaîne en utilisant le mot-clé 'new'
        // Ceci crée toujours un nouvel objet dans le tas, même si le littéral existe dans le pool
        String str3 = new String("Hello");
        System.out.println("str3 : " + str3);
        System.out.println("str1 == str3 : " + (str1 == str3)); // Vérifie s'ils font référence au même objet
    }
}

Enregistrez le fichier après avoir ajouté le code.

Compilez et exécutez maintenant le programme Java pour voir la sortie. Dans le terminal, exécutez les commandes suivantes :

javac ImmutableStringDemo.java
java ImmutableStringDemo

Vous devriez voir une sortie similaire à celle-ci :

Nom initial : LabexUser
str1 : Hello
str2 : Hello
str1 == str2 : true
str3 : Hello
str1 == str3 : false

Cette sortie démontre que str1 et str2 font référence au même objet dans le String pool, tandis que str3 (créé avec new String()) est un objet distinct dans le tas, même si son contenu est identique.

Garantir l'Immuabilité pour la Sécurité et la Sécurité des Threads

L'immuabilité est cruciale pour la sécurité et la sûreté des threads (thread safety) en Java. Lorsqu'une chaîne de caractères est immuable, sa valeur ne peut être modifiée par aucune partie du programme après sa création. Cela empêche le code malveillant de modifier des données sensibles stockées dans des chaînes, telles que des mots de passe ou des URL.

De plus, les objets immuables sont intrinsèquement sûrs pour les threads. Plusieurs threads peuvent accéder à une chaîne immuable simultanément sans nécessiter de synchronisation externe, car il n'y a aucun risque qu'un thread modifie la chaîne pendant qu'un autre la lit.

Démontrons cela en ajoutant le mot-clé final à une chaîne de caractères et en créant un exemple simple de sûreté des threads.

Modifiez votre fichier ImmutableStringDemo.java comme suit. Nous allons déclarer une chaîne final au niveau de la classe pour la rendre accessible à notre classe ThreadSafeTask.

// ~/project/ImmutableStringDemo.java
import java.util.HashMap;

public class ImmutableStringDemo {

    // Déclarer une chaîne final pour la sécurité et la sûreté des threads
    private static final String SECURE_PASSWORD = "MySecurePassword123";

    // Classe interne pour démontrer la sûreté des threads
    static class ThreadSafeTask implements Runnable {
        private final String password; // Le mot de passe est passé comme une chaîne immuable

        public ThreadSafeTask(String password) {
            this.password = password;
        }

        @Override
        public void run() {
            System.out.println("Thread " + Thread.currentThread().getName() + " accède au mot de passe : " + password);
            // Simuler un travail
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        // ... (le code précédent pour name, str1, str2, str3 reste ici) ...
        String name = "LabexUser";
        System.out.println("Nom initial : " + name);

        String str1 = "Hello";
        String str2 = "Hello";
        System.out.println("str1 : " + str1);
        System.out.println("str2 : " + str2);
        System.out.println("str1 == str2 : " + (str1 == str2));

        String str3 = new String("Hello");
        System.out.println("str3 : " + str3);
        System.out.println("str1 == str3 : " + (str1 == str3));

        System.out.println("\n--- Démonstration de la sécurité et de la sûreté des threads ---");

        // Utilisation de la chaîne final
        System.out.println("Mot de passe sécurisé : " + SECURE_PASSWORD);

        // Démontrer la sûreté des threads avec le mot de passe immuable
        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(new ThreadSafeTask(SECURE_PASSWORD), "Thread-" + (i + 1));
            thread.start();
        }

        // Attendre la fin des threads (optionnel, pour une sortie plus propre)
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}

Note : Le mot-clé final garantit que la variable SECURE_PASSWORD ne peut être assignée qu'une seule fois. Comme String lui-même est immuable, cela rend le contenu de SECURE_PASSWORD immuable. La classe ThreadSafeTask accède en toute sécurité à cette chaîne immuable à partir de plusieurs threads.

Enregistrez le fichier. Ensuite, compilez et exécutez à nouveau le programme :

javac ImmutableStringDemo.java
java ImmutableStringDemo

Vous devriez voir une sortie similaire à celle-ci, avec les messages d'accès aux threads apparaissant dans un ordre non déterministe :

Nom initial : LabexUser
str1 : Hello
str2 : Hello
str1 == str2 : true
str3 : Hello
str1 == str3 : false

--- Démonstration de la sécurité et de la sûreté des threads ---
Mot de passe sécurisé : MySecurePassword123
Thread Thread-1 accède au mot de passe : MySecurePassword123
Thread Thread-2 accède au mot de passe : MySecurePassword123
Thread Thread-3 accède au mot de passe : MySecurePassword123

Utiliser l'Immuabilité pour le Cache de Hashcode dans les Collections

L'immuabilité des chaînes de caractères les rend idéales pour être utilisées comme clés dans des collections basées sur le hachage (hash-based collections) comme HashMap et HashSet. Comme la valeur d'une chaîne ne change jamais, son hashCode() peut être calculé une fois et mis en cache. Les appels ultérieurs à hashCode() retourneront la valeur mise en cache, entraînant des améliorations significatives de performance, surtout lorsque les chaînes sont fréquemment utilisées comme clés.

Si les chaînes étaient mutables, leur code de hachage pourrait changer après avoir été inséré dans un HashMap, rendant impossible la récupération de l'objet en utilisant le code de hachage d'origine.

Ajoutons du code à ImmutableStringDemo.java pour démontrer l'utilisation des chaînes dans un HashMap.

Ajoutez le code suivant à la méthode main de votre fichier ImmutableStringDemo.java, après la démonstration de la sûreté des threads :

// ~/project/ImmutableStringDemo.java
import java.util.HashMap; // Assurez-vous que cet import est en haut de votre fichier

public class ImmutableStringDemo {

    private static final String SECURE_PASSWORD = "MySecurePassword123";

    static class ThreadSafeTask implements Runnable {
        private final String password;

        public ThreadSafeTask(String password) {
            this.password = password;
        }

        @Override
        public void run() {
            System.out.println("Thread " + Thread.currentThread().getName() + " accède au mot de passe : " + password);
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
    }

    public static void main(String[] args) {
        // ... (le code précédent pour name, str1, str2, str3, et la sûreté des threads reste ici) ...
        String name = "LabexUser";
        System.out.println("Nom initial : " + name);

        String str1 = "Hello";
        String str2 = "Hello";
        System.out.println("str1 : " + str1);
        System.out.println("str2 : " + str2);
        System.out.println("str1 == str2 : " + (str1 == str2));

        String str3 = new String("Hello");
        System.out.println("str3 : " + str3);
        System.out.println("str1 == str3 : " + (str1 == str3));

        System.out.println("\n--- Démonstration de la sécurité et de la sûreté des threads ---");
        System.out.println("Mot de passe sécurisé : " + SECURE_PASSWORD);

        for (int i = 0; i < 3; i++) {
            Thread thread = new Thread(new ThreadSafeTask(SECURE_PASSWORD), "Thread-" + (i + 1));
            thread.start();
        }
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }

        System.out.println("\n--- Démonstration de la mise en cache du hashCode ---");

        HashMap<String, Integer> studentScores = new HashMap<>();

        String studentName1 = "Alice";
        String studentName2 = "Bob";
        String studentName3 = "Alice"; // Ceci fera référence au même "Alice" dans le String pool

        studentScores.put(studentName1, 95);
        studentScores.put(studentName2, 88);
        studentScores.put(studentName3, 92); // Ceci mettra à jour la valeur pour "Alice"

        System.out.println("Scores des étudiants : " + studentScores);
        System.out.println("Score d'Alice : " + studentScores.get("Alice"));
        System.out.println("Score de Bob : " + studentScores.get("Bob"));

        // Démontrer que même si nous essayons de "modifier" une chaîne, une nouvelle est créée
        String originalString = "Java";
        System.out.println("Chaîne originale : " + originalString + ", HashCode : " + originalString.hashCode());

        String modifiedString = originalString.concat(" Programming"); // Crée une nouvelle chaîne
        System.out.println("Chaîne modifiée : " + modifiedString + ", HashCode : " + modifiedString.hashCode());
        System.out.println("Chaîne originale (après concat) : " + originalString + ", HashCode : " + originalString.hashCode());
    }
}

Enregistrez le fichier. Ensuite, compilez et exécutez à nouveau le programme :

javac ImmutableStringDemo.java
java ImmutableStringDemo

Vous devriez voir une sortie similaire à celle-ci, incluant les démonstrations de HashMap et de hashCode :

Nom initial : LabexUser
str1 : Hello
str2 : Hello
str1 == str2 : true
str3 : Hello
str1 == str3 : false

--- Démonstration de la sécurité et de la sûreté des threads ---
Mot de passe sécurisé : MySecurePassword123
Thread Thread-1 accède au mot de passe : MySecurePassword123
Thread Thread-2 accède au mot de passe : MySecurePassword123
Thread Thread-3 accède au mot de passe : MySecurePassword123

--- Démonstration de la mise en cache du hashCode ---
Scores des étudiants : {Bob=88, Alice=92}
Score d'Alice : 92
Score de Bob : 88
Chaîne originale : Java, HashCode : 2301506
Chaîne modifiée : Java Programming, HashCode : -1479700901
Chaîne originale (après concat) : Java, HashCode : 2301506

Notez comment le code de hachage de originalString reste le même même après l'appel de concat, car concat retourne un nouvel objet String, laissant l'original intact.

Résumé

Dans ce laboratoire, vous avez découvert le concept fondamental des chaînes immuables (immutable Strings) en programmation Java. Vous avez exploré pourquoi les chaînes sont immuables par défaut et comment ce choix de conception contribue à l'optimisation de la mémoire grâce au pool de chaînes (String pool). Vous avez également compris les avantages critiques de l'immuabilité, notamment une sécurité renforcée pour les données sensibles, une sûreté des threads intrinsèque pour les opérations concurrentes, et une amélioration des performances dans les collections basées sur le hachage grâce à la mise en cache du code de hachage (hashcode caching). En réalisant les exercices pratiques, vous avez acquis une expérience concrète dans la création et l'utilisation efficaces des chaînes immuables dans vos applications Java.