web-dev-qa-db-fra.com

Comment maintenir l'immuabilité d'une classe avec une référence mutable

Je connais toutes les règles de base pour rendre notre classe immuable, mais je suis un peu confus s’il existe une autre référence de classe. Je sais que s’il existe une collection au lieu de Address, nous pouvons utiliser la fonction Collections.unmodifiableList(new ArrayList<>(modifiable)); pour rendre notre classe immuable. Mais dans les cas suivants, je suis toujours incapable de comprendre le concept.

public final class Employee{
    private final int id;
    private Address address;
    public Employee(int id, Address address)
    {
        this.id = id;
        this.address=address;
    }
    public int getId(){
        return id;
    }
    public Address getAddress(){
        return address;
    }
}

public class Address{
    private String street;
    public String getStreet(){
        return street;
    }
    public void setStreet(String street){
        this.street = street;
    }
}
8
user1111880

Eh bien, le concept consiste à lire le JLS et à le comprendre. Dans ce cas, le JLS dit: 

les champs finaux permettent également aux programmeurs d'implémenter des objets immuables sécurisés pour les threads sans synchronisation. Un objet immuable thread-safe est considéré comme immuable par tous les threads, même si une course de données est utilisée pour transmettre des références à l'objet immuable entre les threads. Cela peut fournir des garanties de sécurité contre l'utilisation abusive d'une classe immuable par un code incorrect ou malveillant. Les champs finaux doivent être utilisés correctement pour fournir une garantie d'immutabilité.

Le modèle d'utilisation des champs finaux est simple: définissez les champs finaux d'un objet dans le constructeur de cet objet; et n'écrivez pas de référence à l'objet en cours de construction à un endroit où un autre thread peut le voir avant que le constructeur de l'objet ne soit terminé. Si ceci est suivi, alors quand l'objet est vu par un autre thread, celui-ci verra toujours la version correctement construite de ses champs finaux. Il verra également les versions de tout objet ou tableau référencé par ces champs finaux qui sont au moins aussi à jour que les champs finaux.

Donc vous devez:

  1. Faites address final et privé.
  2. Pour tout objet mutable, vous devez empêcher que la référence à cet objet soit vue de l'extérieur.

Dans ce cas, # 2 signifie probablement que vous ne pouvez pas renvoyer une référence à Address comme vous l'avez fait avec getAddress(). Et vous devez faire une copie défensive dans le constructeur. C'est-à-dire, faire une copie de tout paramètre mutable et la stocker dans Employee. Si vous ne pouvez pas faire une copie défensive, il n'y a vraiment aucun moyen de rendre l'employé immuable.

public final class Employee{
    private final int id;
    private final Address address;
    public Employee(int id, Address address)
    {
        this.id = id;
        this.address=new Address();  // defensive copy
        this.address.setStreet( address.getStreet() );
    }
    pulbic int getId(){
        return id;
    }
    public Address getAddress() {
        Address nuAdd = new Address(); // must copy here too
        nuAdd.setStreet( address.getStreet() );
        return nuAdd;
}

Implémenter clone() ou quelque chose de similaire (un copieur) faciliterait la création d'objets défensifs pour des classes compliquées. Cependant, la meilleure recommandation que je pense serait de rendre Address immuable. Une fois que vous avez fait cela, vous pouvez librement passer sa référence sans aucun problème de sécurité des threads.

Dans cet exemple, remarquez que jePASdois copier la valeur de street. Street est une chaîne et les chaînes sont immuables. Si street est constitué de champs mutables (numéro de rue entier par exemple), alors je voudrais dois également faire une copie de street, et ainsi de suite. C'est pourquoi les objets immuables sont si précieux qu'ils rompent la chaîne de "copies infinies".

10
markspace

Eh bien, il existe des étapes fournies par Java Docs

Une stratégie pour définir les objets immuables

Les règles suivantes définissent une stratégie simple pour créer des objets Immuables. Toutes les classes documentées comme "immuables" ne suivent pas ces règles. Cela ne signifie pas nécessairement que les créateurs de ces classes étaient Négligés - ils peuvent avoir de bonnes raisons de croire que des exemples de leurs classes ne changent jamais après la construction. Cependant, de telles stratégies Nécessitent une analyse sophistiquée et ne sont pas destinées aux débutants.

  • Ne fournissez pas de méthodes "setter" - méthodes qui modifient des champs ou des objets Référés par des champs. 
  • Rendre tous les champs finaux et privés. 
  • Ne laissez pas les sous-classes Remplacer les méthodes. Le moyen le plus simple de procéder consiste à Déclarer la classe finale. Une approche plus sophistiquée consiste à rendre Le constructeur privé et à construire des instances dans les méthodes d'usine. 
  • Si Les champs d'instance incluent des références à des objets mutables, n'autorisez pas À modifier ces objets:
    • Ne fournissez pas de méthodes qui modifient les objets mutables . 
    • Ne partagez pas les références aux objets mutables. Jamais Ne stocke de références à des objets externes modifiables passés au constructeur ; si nécessaire, créez des copies et stockez les références aux copies . De même, créez des copies de vos objets mutables internes lorsque Est nécessaire pour éviter de renvoyer les originaux dans vos méthodes.

La classe d'adresse est modifiable parce que vous pouvez la modifier avec la méthode setStreet. Ainsi, une autre classe peut modifier cette classe.

Nous pouvons nous défendre contre cela en prenant une copie de l'instance d'adresse lorsqu'elle est passée au lieu de faire confiance à la référence à l'instance qui nous est donnée.

Rendre un objet adresse final

private final Address address;

Deuxièmement, 

this.address = new Address(address.getStreet());

Créez un constructeur dans la classe Address qui définit la méthode de définition de Street.Remove pour street.

Et finalement au lieu de 

public Address getAddress(){
    return address;
} 

Utilisation

public Address getAddress(){
    return new Address(address.getStreet());
}
7
Naruto

Si vous souhaitez encapsuler un objet mutable dans un objet immuable , vous devez:

  1. Créez une copie de l'objet mutable (c'est-à-dire via le constructeur de copie, le clonage, la sérialisation/désérialisation, etc.); Ne stocke jamais la référence à l'objet mutable d'origine.
  2. Ne retournez jamais l'objet mutable. Si vous devez le faire, alors retournez une copie de l'objet.
  3. Évitez les méthodes qui peuvent changer l'objet mutable. 

employé public (int id, adresse adresse) {

        this.id = id;
        this.address=new Address();  
        this.address.setStreet( address.getStreet() );
    }



public Address getAddress() {
        Address nuAdd = new Address(); // must copy here too
        nuAdd.setStreet( address.getStreet() );
        return nuAdd;
}
1
Neeraj Gahlawat

Vous pouvez également utiliser une copie superficielle à l'aide du clonage

public final class Employee{
    private final int id;
    private Address address;
    public Employee(int id, Address address)
    {
        this.id = id;
        this.address=address.clone();
    }
    public int getId(){
        return id;
    }
    public Address getAddress(){
        return address.clone();
    }
}

L'utilisation de cela crée un objet distinct de Address dans la classe Employee. Ainsi, dans ce cas, toute modification apportée à l'objet Address transmis en tant qu'argument dans le constructeur Employee ne modifiera pas l'objet de variable membre Address de la classe Employee.

La méthode getAddress () renvoie également un objet clone afin que les modifications apportées à l'objet récupéré par cette méthode n'affectent pas l'objet adresse de la classe Employee.

Remarque: Utiliser cette option pour rendre la classe d'adresse clonable.

0
sanjeev51

Ainsi, dans votre exemple, la classe Employee est immuable, car une fois créée, vous ne pouvez pas changer son état, car elle ne comporte que des méthodes getter.

Address class est mutable car vous pouvez le modifier avec la méthode setStreet

Donc, si vous avez une autre classe qui utilise l'objet Address, vous êtes sûr que cette classe ne peut pas modifier l'état des objets.

0
jonasnas