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;
}
}
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:
address
final et privé.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".
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());
}
Si vous souhaitez encapsuler un objet mutable dans un objet immuable , vous devez:
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;
}
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.
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.