Recherche…


Remarques

Les objets immuables ont un état fixe (aucun paramètre), donc tout état doit être connu au moment de la création de l'objet.

Bien que ce ne soit pas techniquement requis, il est recommandé de rendre tous les champs final . Cela rendra la classe immuable sûre (cf. Concurrence Java en pratique, 3.4.1).

Les exemples montrent plusieurs modèles qui peuvent aider à atteindre cet objectif.

Création d'une version immuable d'un type utilisant la copie défensive.

Certains types et classes de base en Java sont fondamentalement mutables. Par exemple, tous les types de tableau sont modifiables, tout comme les classes java.util.Data . Cela peut être gênant dans les situations où un type immuable est obligatoire.

Une façon de gérer cela est de créer un wrapper immuable pour le type mutable. Voici un wrapper simple pour un tableau d'entiers

public class ImmutableIntArray {
    private final int[] array;
    
    public ImmutableIntArray(int[] array) {
        this.array = array.clone();
    }

    public int[] getValue() {
        return this.clone();
    }
}

Cette classe fonctionne en utilisant la copie défensive pour isoler l'état mutable (l' int[] ) de tout code qui pourrait le muter:

  • Le constructeur utilise clone() pour créer une copie distincte du tableau de paramètres. Si l'appelant du constructeur suivant modifiait le tableau de paramètres, cela n'affecterait pas l'état de la ImmutableIntArray .

  • La méthode getValue() utilise également clone() pour créer le tableau renvoyé. Si l'appelant changeait le tableau de résultats, cela n'affecterait pas l'état du tableau ImmutableIntArray .

Nous pourrions également ajouter des méthodes à ImmutableIntArray pour effectuer des opérations en lecture seule sur le tableau encapsulé; par exemple, obtenir sa longueur, obtenir la valeur à un index particulier, etc.

Notez qu'un type de wrapper immuable implémenté de cette manière n'est pas compatible avec le type d'origine. Vous ne pouvez pas simplement substituer le premier au second.

La recette d'une classe immuable

Un objet immuable est un objet dont l'état ne peut pas être modifié. Une classe immuable est une classe dont les instances sont immuables par conception et implémentation. La classe Java la plus communément présentée comme exemple d'immuabilité est java.lang.String .

Voici un exemple stéréotypé:

public final class Person {
    private final String name;
    private final String ssn;     // (SSN == social security number)

    public Person(String name, String ssn) {
        this.name = name;
        this.ssn = ssn;
    }

    public String getName() {
        return name;
    }
   
    public String getSSN() {
        return ssn;
    }
}

Une variante consiste à déclarer le constructeur comme private et à fournir une méthode de fabrique public static .


La recette standard pour une classe immuable est la suivante:

  • Toutes les propriétés doivent être définies dans les constructeurs ou les méthodes d'usine.
  • Il ne devrait y avoir aucun installateur.
  • S'il est nécessaire d'inclure des paramètres pour des raisons de compatibilité d'interface, ils doivent soit ne rien faire ou lancer une exception.
  • Toutes les propriétés doivent être déclarées comme private et final .
  • Pour toutes les propriétés qui sont des références à des types mutables:
    • la propriété doit être initialisée avec une copie profonde de la valeur transmise via le constructeur, et
    • le getter de la propriété doit retourner une copie profonde de la valeur de la propriété.
  • La classe doit être déclarée comme final pour empêcher quelqu'un de créer une sous-classe mutable d'une classe immuable.

Quelques autres choses à noter:

  • Immutabilité n'empêche pas l'objet d'être nullable; Par exemple, null peut être affecté à une variable String .
  • Si une propriété de classes immuables est déclarée comme final , les instances sont intrinsèquement sûres. Cela fait des classes immuables un bon bloc de construction pour l'implémentation d'applications multithread.

Des défauts de conception typiques qui empêchent une classe d'être immuable

En utilisant certains paramètres, sans définir toutes les propriétés nécessaires dans le ou les constructeurs

public final class Person { // example of a bad immutability
    private final String name;
    private final String surname;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
    public String getSurname() { return surname;}
    public void setSurname(String surname) { this.surname = surname); }
}

Il est facile de montrer que la classe Person n'est pas immuable:

Person person = new Person("Joe");
person.setSurname("Average"); // NOT OK, change surname field after creation

Pour résoudre ce problème, supprimez simplement setSurname() et setSurname() le constructeur comme suit:

public Person(String name, String surname) {
    this.name = name;
    this.surname = surname;
  }

Ne pas marquer les variables d'instance comme privées et finales

Jetez un oeil à la classe suivante:

public final class Person {
    public String name;
    public Person(String name) {
        this.name = name;
     }
    public String getName() {
        return name;
    }
    
}

L'extrait suivant montre que la classe ci-dessus n'est pas immuable:

Person person = new Person("Average Joe");
person.name = "Magic Mike"; // not OK, new name for person after creation

Pour y remédier, marquez simplement nom propriété comme private et final .


Exposer un objet mutable de la classe dans un getter

Jetez un oeil à la classe suivante:

import java.util.List;
import java.util.ArrayList;
public final class Names {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }
    public List<String> getNames() {
        return names;
    }
    public int size() {
        return names.size();
    }
}

Names classe des Names semble immuable à première vue, mais ce n’est pas ce que montre le code suivant:

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
Names names = new Names(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList = names.getNames();
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

Cela s'est produit car une modification apportée à la liste de références renvoyée par getNames() peut modifier la liste de Names .

Pour résoudre ce problème, évitez simplement de renvoyer des références qui font référence aux objets mutables de la classe, soit en créant des copies défensives, comme suit:

public List<String> getNames() {
   return new ArrayList<String>(this.names); // copies elements
}

ou en concevant des getters de manière à ne renvoyer que les autres objets et primitives immuables , comme suit:

public String getName(int index) {
    return names.get(index);
}
public int size() {
    return names.size();
}

Injecter un constructeur avec un ou des objets pouvant être modifiés en dehors de la classe immuable

Ceci est une variation du défaut précédent. Jetez un oeil à la classe suivante:

import java.util.List;
public final class NewNames {
    private final List<String> names;
    public Names(List<String> names) {
        this.names = names;
    }
    public String getName(int index) {
        return names.get(index);
    }
    public int size() {
        return names.size();
    }
}

Comme la classe Names avant, la classe NewNames semble également immuable à première vue, mais ce n’est pas le cas: l’extrait suivant prouve le contraire:

List<String> namesList = new ArrayList<String>();
namesList.add("Average Joe");
NewNames names = new NewNames(namesList);
System.out.println(names.size()); // 1, only containing "Average Joe"
namesList.add("Magic Mike");
System.out.println(names.size()); // 2, NOT OK, now names also contains "Magic Mike"

Pour corriger cela, comme dans la faille précédente, faites simplement des copies défensives de l'objet sans l'affecter directement à la classe immuable, c'est-à-dire que le constructeur peut être modifié comme suit:

    public Names(List<String> names) {
        this.names = new ArrayList<String>(names);
    }

Laisser les méthodes de la classe être surpassées

Jetez un oeil à la classe suivante:

public class Person {
    private final String name;
    public Person(String name) {
        this.name = name;
      }
    public String getName() { return name;}
}

Person classe des Person semble immuable à première vue, mais supposons qu'une nouvelle sous-classe de Person soit définie:

public class MutablePerson extends Person {
    private String newName;
    public MutablePerson(String name) {
        super(name);            
    }
    @Override
    public String getName() {
        return newName;
    }
    public void setName(String name) {
        newName = name;
    }
}

maintenant, la mutabilité de Person (im) peut être exploitée par le polymorphisme en utilisant la nouvelle sous-classe:

Person person = new MutablePerson("Average Joe");
System.out.println(person.getName()); prints Average Joe
person.setName("Magic Mike"); // NOT OK, person has now a new name!
System.out.println(person.getName()); // prints Magic Mike    

Pour résoudre ce problème, soit marquer la classe comme final ne peut donc pas être étendu ou déclarer l' ensemble de son constructeur (s) comme private .



Modified text is an extract of the original Stack Overflow Documentation
Sous licence CC BY-SA 3.0
Non affilié à Stack Overflow