CodeGym /Cours /JAVA 25 SELF /Immutabilité — classes record immuables

Immutabilité — classes record immuables

JAVA 25 SELF
Niveau 22 , Leçon 1
Disponible

1. Classes record et immutabilité

L’immutabilité est une propriété d’un objet selon laquelle son état ne peut pas être modifié après sa création. En d’autres termes : une fois l’objet créé, vous ne pouvez pas changer ses données internes. Point final. Gravé dans la pierre.

Imaginez un billet de train. Dès qu’il est imprimé, vous ne pouvez pas modifier la date ou le lieu de départ (sauf à recourir à des bidouillages avec Photoshop). Le billet est un objet immuable. Si vous voulez un autre billet, vous en achetez un nouveau.

En programmation, on appelle de tels objets des objets immuables. Ils protègent le programme contre des modifications accidentelles et rendent le code plus sûr et plus prédictible.

Caractéristiques d’un objet immuable

  • Tous les champs de l’objet sont final (ils ne peuvent être affectés qu’une seule fois, généralement dans le constructeur).
  • Pas de setters (méthodes qui modifient les valeurs des champs).
  • Toutes les méthodes qui renvoient des données internes renvoient soit des copies, soit des données elles-mêmes immuables.

Les classes record en Java ont été conçues précisément pour créer des structures de données immuables de manière simple et sans douleur.

Pourquoi un record est-il immuable ?

  • Tous les composants d’un record sont final.
    Lorsque vous déclarez un record, le compilateur rend automatiquement tous ses champs private final. Cela signifie qu’après la création de l’objet vous ne pourrez pas modifier ses champs.
  • Pas de setters.
    Dans une classe record, vous ne pouvez pas ajouter une méthode setX(int x) — le compilateur ne vous permettra pas de changer la valeur d’un champ après la création de l’objet.
  • Le constructeur n’assigne les valeurs qu’une seule fois.
    Toutes les valeurs sont fixées au moment de la création de l’objet.

Exemple

public record Point(int x, int y) {}

Point p = new Point(5, 10);
// p.x = 7; // Erreur de compilation : le champ x est privé et final
// p.x(7);  // Erreur : aucun setter !
System.out.println(p.x()); // 5

Toute tentative de modifier un champ ou d’appeler un setter inexistant entraîne une erreur de compilation. Java veille strictement au contrat d’immutabilité.

2. Avantages des objets immuables

Sécurité en environnement multithread

Dans les programmes multithread (la majorité aujourd’hui !), les objets immuables sont comme un gilet pare-balles. Si l’objet ne peut pas être modifié, différents threads peuvent le lire tranquillement sans crainte qu’un autre change les données au même moment. Il n’est pas nécessaire de synchroniser l’accès ni de craindre des conditions de course.

Fait : de nombreuses classes de la bibliothèque standard Java, utilisées activement dans des scénarios multithread, sont soit immuables, soit spécialement protégées contre les modifications.

Compréhension du code facilitée

Si un objet est immuable, vous êtes toujours sûr que : vous le passez à une autre méthode ou classe — et il ne changera pas « dans votre dos ». Cela facilite grandement la lecture et le débogage du code. Pas besoin de se demander qui et où a pu changer un champ — personne ne le peut !

Pratiques comme clés dans les collections

Les objets immuables conviennent parfaitement comme clés dans des collections telles que HashMap ou HashSet. Pourquoi ? Parce que leurs equals et hashCode ne dépendent que de champs qui ne changent pas. L’objet ne se « perdra » donc pas dans la collection parce que son état aurait changé.

Moins de bogues sournois

Il est facile d’abîmer un objet mutable en transmettant par inadvertance sa référence au mauvais endroit. Un objet immuable, lui, est comme un livre imprimé : personne ne peut arracher ou réécrire une page.

3. Comparaison avec les classes ordinaires

Comparons le comportement d’une classe ordinaire et d’une classe record. Prenons comme exemple un simple modèle de point dans le plan.

Classe ordinaire (mutable)

public class PointClass {
    private int x;
    private int y;

    public PointClass(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() { 
        return x; 
    }
    public int getY() { 
        return y; 
    }

    public void setX(int x) { 
        this.x = x; 
    }
    public void setY(int y) { 
        this.y = y; 
    }
}

On peut créer un objet puis modifier son état autant qu’on le souhaite :

PointClass p = new PointClass(1, 2);
p.setX(10); // p.x vaut désormais 10

Classe record (immuable)

public record Point(int x, int y) {}

Vous créez l’objet — et c’est tout, il restera à jamais tel qu’il a été créé :

Point p = new Point(1, 2);
// p.x = 10;   // Erreur ! Pas d’accès au champ
// p.x(10);    // Erreur ! Pas de setter

Tableau : comparaison des comportements

Classe ordinaire Classe record
Champs n’importe lesquels uniquement private final
Setters peuvent être ajoutés ne peuvent pas être ajoutés
Mutabilité mutable immuable
Génération automatique non oui (equals, hashCode, toString)

4. Pratique : comment utiliser l’immutabilité des classes record

Appliquons un record dans un petit exemple. Supposons que nous ayons une application bancaire et que nous voulions stocker des informations sur une transaction :

public record Transaction(String fromAccount, String toAccount, double amount) {}

Créons un objet :

Transaction t = new Transaction("12345", "67890", 1500.0);
System.out.println(t);
// Transaction[fromAccount=12345, toAccount=67890, amount=1500.0]

Essayons de « transférer » l’argent vers un autre compte :

// t.toAccount = "11111"; // Erreur ! Champ final, pas d’accès
// t.toAccount("11111");  // Erreur ! Pas de setter

Si nous avons besoin d’une autre transaction — nous créons un nouvel objet :

Transaction t2 = new Transaction(t.fromAccount(), "11111", t.amount());

Important : immuable ne signifie pas « peu pratique ». C’est simplement un autre style de travail : si vous avez besoin d’un nouvel état — vous créez un nouvel objet.

5. Particularités de l’immutabilité : ce qu’il faut retenir

L’immutabilité n’est pas toujours absolue !

Une classe record garantit que ses champs ne changent pas. Mais si un champ est une référence vers un objet mutable (par exemple, un tableau ou une classe ordinaire), alors le contenu de cet objet peut être modifié.

Exemple avec un tableau

public record DataHolder(int[] data) {}

int[] arr = {1, 2, 3};
DataHolder holder = new DataHolder(arr);
arr[0] = 99;
System.out.println(holder.data()[0]); // 99 ! Le tableau a été modifié

Conclusion : si vous voulez une véritable immutabilité, utilisez uniquement des types immuables (String, Integer, d’autres records, etc.) ou faites des copies défensives des objets mutables dans le constructeur canonique de la classe record. Par exemple :

int[] copy = Arrays.copyOf(data, data.length);

6. Comment rendre une classe ordinaire immuable

Si vous voulez rendre une classe ordinaire immuable, vous devez le faire manuellement :

  • Marquer tous les champs comme private final,
  • Ne pas ajouter de setters,
  • Initialiser tous les champs uniquement via le constructeur,
  • Si un champ est un objet mutable, faire une copie défensive (defensive copy).

Exemple

public final class User {
    private final String name;
    private final int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String name() { 
        return name; 
    }
    public int age() { 
        return age; 
    }
}

Avouez que c’est plus simple et plus concis avec une classe record :

public record User(String name, int age) {}

7. Erreurs courantes lors de l’utilisation de classes record immuables

Erreur n°1 : tentative de modifier un champ après la création.
Les débutants essaient souvent d’écrire p.x = 42; ou p.x(42); pour un objet record. Mais le compilateur dira immédiatement : « Ce n’est pas possible ! Le champ est final, il n’y a pas de setter. »

Erreur n°2 : utilisation d’objets mutables comme composants d’un record.
Si vous ajoutez dans un record un champ de type List, Map, un tableau ou un autre objet mutable, le record lui-même ne vous protégera pas contre les modifications du contenu de cet objet. Par exemple, si vous avez un record User(List<String> hobbies), quelqu’un peut ajouter ou supprimer un élément de la liste, ce qui modifiera l’état de votre objet record. Pour éviter cela, utilisez des collections immuables (List.copyOf, Collections.unmodifiableList) ou faites des copies des collections dans le constructeur du record.

Erreur n°3 : mauvaise compréhension de l’immutabilité.
Certains pensent que si un objet est un record, il est protégé contre toute modification. En réalité, si les champs sont des références vers des objets mutables, leur contenu peut être changé, ce qui entraînera des bogues inattendus.

Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION