Comment rendre une classe Java immuable, quel est le besoin d'immuabilité et y a-t-il un avantage à l'utiliser?
@Immutable
annotation de jcabi-aspects
Comment rendre une classe Java immuable, quel est le besoin d'immuabilité et y a-t-il un avantage à l'utiliser?
@Immutable
annotation de jcabi-aspects
Réponses:
Qu'est-ce qu'un objet immuable?
Un objet immuable est celui qui ne changera pas d'état après avoir été instancié.
Comment rendre un objet immuable?
En général, un objet immuable peut être créé en définissant une classe dont aucun de ses membres n'est exposé et qui n'a pas de setters.
La classe suivante créera un objet immuable:
class ImmutableInt {
private final int value;
public ImmutableInt(int i) {
value = i;
}
public int getValue() {
return value;
}
}
Comme on peut le voir dans l'exemple ci-dessus, la valeur de ImmutableInt
ne peut être définie que lorsque l'objet est instancié, et en n'ayant qu'un getter ( getValue
), l'état de l'objet ne peut pas être modifié après l'instanciation.
Cependant, il faut veiller à ce que tous les objets référencés par l'objet soient également immuables, sinon il pourrait être possible de changer l'état de l'objet.
Par exemple, autoriser une référence à un tableau ou ArrayList
être obtenue via un getter permettra à l'état interne de changer en modifiant le tableau ou la collection:
class NotQuiteImmutableList<T> {
private final List<T> list;
public NotQuiteImmutableList(List<T> list) {
// creates a new ArrayList and keeps a reference to it.
this.list = new ArrayList(list);
}
public List<T> getList() {
return list;
}
}
Le problème avec le code ci-dessus est que le ArrayList
peut être obtenu à travers getList
et être manipulé, ce qui conduit à modifier l'état de l'objet lui-même, donc pas immuable.
// notQuiteImmutableList contains "a", "b", "c"
List<String> notQuiteImmutableList= new NotQuiteImmutableList(Arrays.asList("a", "b", "c"));
// now the list contains "a", "b", "c", "d" -- this list is mutable.
notQuiteImmutableList.getList().add("d");
Une façon de contourner ce problème consiste à renvoyer une copie d'un tableau ou d'une collection lorsqu'elle est appelée à partir d'un getter:
public List<T> getList() {
// return a copy of the list so the internal state cannot be altered
return new ArrayList(list);
}
Quel est l'avantage de l'immuabilité?
L'avantage de l'immuabilité vient avec la concurrence. Il est difficile de maintenir l'exactitude des objets mutables, car plusieurs threads pourraient essayer de changer l'état du même objet, ce qui conduit certains threads à voir un état différent du même objet, en fonction de la synchronisation des lectures et des écritures sur ledit objet. objet.
En ayant un objet immuable, on peut s'assurer que tous les threads qui regardent l'objet verront le même état, car l'état d'un objet immuable ne changera pas.
return Collections.unmodifiableList(list);
renvoyer une vue en lecture seule sur la liste.
final
aussi. Sinon, il peut être étendu avec une méthode setter (ou d'autres types de méthodes de mutation et de champs mutables).
final
si la classe ne contient que des private
champs car ils ne sont pas accessibles depuis les sous-classes.
En plus des réponses déjà données, je recommanderais de lire sur l'immuabilité dans Effective Java, 2e éd., Car certains détails sont faciles à manquer (par exemple, des copies défensives). De plus, Effective Java 2nd Ed. est une lecture incontournable pour chaque développeur Java.
Vous rendez une classe immuable comme ceci:
public final class Immutable
{
private final String name;
public Immutable(String name)
{
this.name = name;
}
public String getName() { return this.name; }
// No setter;
}
Voici les conditions requises pour rendre une classe Java immuable:
final
(pour que les classes enfants ne puissent pas être créées)final
(pour que nous ne puissions pas en changer la valeur après la création de l'objet)Les classes immuables sont utiles car
- Elles sont thread-safe.
- Ils expriment également quelque chose de profond à propos de votre conception: "Je ne peux pas changer cela."
L'immuabilité peut être obtenue principalement de deux manières:
final
attributs d'instance pour éviter la réaffectationLes avantages de l'immuabilité sont les hypothèses que vous pouvez faire sur ces objets:
Les classes immuables ne peuvent pas réaffecter les valeurs après leur instanciation. Le constructeur affecte des valeurs à ses variables privées. Jusqu'à ce que l'objet devienne nul, les valeurs ne peuvent pas être modifiées en raison de l'indisponibilité des méthodes de définition.
être immuable doit satisfaire la suite,
/**
* Strong immutability - by making class final
*/
public final class TestImmutablity {
// make the variables private
private String Name;
//assign value when the object created
public TestImmutablity(String name) {
this.Name = name;
}
//provide getters to access values
public String getName() {
return this.Name;
}
}
Avantages: les objets immuables contiennent leurs valeurs initialisées jusqu'à leur mort.
Les classes immuables sont celles dont les objets ne peuvent pas être modifiés après la création.
Les classes immuables sont utiles pour
Exemple
Classe de chaîne
Exemple de code
public final class Student {
private final String name;
private final String rollNumber;
public Student(String name, String rollNumber) {
this.name = name;
this.rollNumber = rollNumber;
}
public String getName() {
return this.name;
}
public String getRollNumber() {
return this.rollNumber;
}
}
Comment rendre une classe Java immuable?
À partir du JDK 14+ qui a JEP 359 , nous pouvons utiliser " records
". C'est le moyen le plus simple et le plus gratuit de créer une classe Immuable.
Une classe d'enregistrement est un support transparent peu profond pour un ensemble fixe de champs connu sous le nom d'enregistrementcomponents
qui fournit une state
description de l'enregistrement. Chacun component
donne lieu à un final
champ qui contient la valeur fournie et une accessor
méthode pour récupérer la valeur. Le nom du champ et le nom de l'accesseur correspondent au nom du composant.
Prenons l'exemple de la création d'un rectangle immuable
record Rectangle(double length, double width) {}
Pas besoin de déclarer un constructeur, pas besoin d'implémenter les méthodes equals & hashCode. Tous les enregistrements ont besoin d'un nom et d'une description d'état.
var rectangle = new Rectangle(7.1, 8.9);
System.out.print(rectangle.length()); // prints 7.1
Si vous souhaitez valider la valeur lors de la création de l'objet, nous devons déclarer explicitement le constructeur.
public Rectangle {
if (length <= 0.0) {
throw new IllegalArgumentException();
}
}
Le corps de l'enregistrement peut déclarer des méthodes statiques, des champs statiques, des initialiseurs statiques, des constructeurs, des méthodes d'instance et des types imbriqués.
Méthodes d'instance
record Rectangle(double length, double width) {
public double area() {
return this.length * this.width;
}
}
champs statiques, méthodes
Étant donné que l'état doit faire partie des composants, nous ne pouvons pas ajouter de champs d'instance aux enregistrements. Mais, nous pouvons ajouter des champs et des méthodes statiques:
record Rectangle(double length, double width) {
static double aStaticField;
static void aStaticMethod() {
System.out.println("Hello Static");
}
}
quel est le besoin d'immuabilité et y a-t-il un avantage à l'utiliser?
Les réponses précédemment publiées sont suffisamment bonnes pour justifier le besoin d'immuabilité et ce sont des avantages
Une autre façon de rendre un objet immuable consiste à utiliser la bibliothèque Immutables.org :
En supposant que les dépendances requises ont été ajoutées, créez une classe abstraite avec des méthodes d'accès abstraites. Vous pouvez faire de même en annotant avec des interfaces ou même des annotations (@interface):
package info.sample;
import java.util.List;
import java.util.Set;
import org.immutables.value.Value;
@Value.Immutable
public abstract class FoobarValue {
public abstract int foo();
public abstract String bar();
public abstract List<Integer> buz();
public abstract Set<Long> crux();
}
Il est maintenant possible de générer puis d'utiliser l'implémentation immuable générée:
package info.sample;
import java.util.List;
public class FoobarValueMain {
public static void main(String... args) {
FoobarValue value = ImmutableFoobarValue.builder()
.foo(2)
.bar("Bar")
.addBuz(1, 3, 4)
.build(); // FoobarValue{foo=2, bar=Bar, buz=[1, 3, 4], crux={}}
int foo = value.foo(); // 2
List<Integer> buz = value.buz(); // ImmutableList.of(1, 3, 4)
}
}
Une classe immuable est simplement une classe dont les instances ne peuvent pas être modifiées.
Toutes les informations contenues dans chaque instance sont fixes pour la durée de vie de l'objet, de sorte qu'aucun changement ne peut jamais être observé.
Les classes immuables sont plus faciles à concevoir, à implémenter et à utiliser que les classes mutables.
Pour rendre une classe immuable, suivez ces cinq règles:
Ne pas fournir de méthodes qui modifient l'état de l'objet
Assurez-vous que la classe ne peut pas être étendue.
Rendez tous les champs définitifs.
Rendre tous les champs privés.
Garantissez un accès exclusif à tous les composants mutables.
Les objets immuables sont intrinsèquement thread-safe; ils ne nécessitent aucune synchronisation.
Les objets immuables peuvent être partagés librement.
Les objets immuables constituent d'excellents blocs de construction pour d'autres objets
@Jack, Avoir les champs finaux et les setters en classe ne rendra pas la classe immuable. Le mot clé final garantit uniquement qu'une variable n'est jamais réaffectée. Vous devez renvoyer une copie complète de tous les champs dans les méthodes getter. Cela garantira qu'après avoir obtenu un objet de la méthode getter, l'état interne de l'objet n'est pas perturbé.
En tant que non-anglophone, je n'aime pas l'interprétation courante de «classe immuable» étant «les objets de classe construits sont immuables»; plutôt, je me penche moi-même pour interpréter cela comme «l'objet de classe lui-même est immuable».
Cela dit, la "classe immuable" est une sorte d'objet immuable. La différence réside dans la réponse à l'avantage. À ma connaissance / interprétation, la classe immuable empêche ses objets de modifier le comportement à l'exécution.
La plupart des réponses ici sont bonnes, et certaines ont mentionné les règles, mais je pense qu'il est bon de mettre par écrit pourquoi et quand nous devons suivre ces règles. Alors je donne l'explication ci-dessous
Et bien sûr, si nous essayons d'utiliser Setters pour ces variables finales, le compilateur renvoie une erreur.
public class ImmutableClassExplored {
public final int a;
public final int b;
/* OR
Generally we declare all properties as private, but declaring them as public
will not cause any issues in our scenario if we make them final
public final int a = 109;
public final int b = 189;
*/
ImmutableClassExplored(){
this. a = 111;
this.b = 222;
}
ImmutableClassExplored(int a, int b){
this.a = a;
this.b= b;
}
}
Avons-nous besoin de déclarer la classe comme «finale»?
1. N'ayant que des membres primitifs: nous n'avons pas de problème Si la classe n'a que des membres primitifs, alors nous n'avons pas besoin de déclarer la classe comme finale.
2. Avoir des objets comme variables membres: Si nous avons des objets comme variables membres, nous devons rendre les membres de ces objets également finaux. Cela signifie que nous devons traverser profondément dans l'arbre et rendre tous les objets / primitives comme finaux, ce qui peut ne pas être possible tout le temps. La solution de contournement consiste donc à rendre la classe définitive, ce qui empêche l'héritage. Il n'est donc pas question de sous-classe remplacer les méthodes getter.
L'annotation @Value de Lombok peut être utilisée pour générer des classes immuables. C'est aussi simple que le code ci-dessous.
@Value
public class LombokImmutable {
int id;
String name;
}
Selon la documentation sur le site de Lombok:
@Value est la variante immuable de @Data; tous les champs sont rendus privés et définitifs par défaut et les setters ne sont pas générés. La classe elle-même est également rendue définitive par défaut, car l'immuabilité n'est pas quelque chose qui peut être forcé sur une sous-classe. Comme @Data, les méthodes utiles toString (), equals () et hashCode () sont également générées, chaque champ obtient une méthode getter et un constructeur qui couvre chaque argument (à l'exception des champs finaux qui sont initialisés dans la déclaration de champ) est également généré .
Un exemple pleinement fonctionnel peut être trouvé ici.
record
peut être utilisé?