[JAVA] Qu'est-ce qu'un objet immuable? [Explication comment faire]

J'ai entendu dire que les objets immuables sont sûrs, ou essayez de créer autant que possible des objets immuables, mais qu'est-ce que c'est? Je pense qu'il y a beaucoup de monde

Ici, je vais expliquer ce qu'est un objet immuable et comment faire un objet immuable. Les objets immuables peuvent être créés quelle que soit la langue, mais je pense que je vais l'expliquer en Java.

L'explication est pour les débutants ici, mais si vous voulez lire des explications plus détaillées, veuillez acheter les livres suivants. Effective Java 3rd Edition

Qu'est-ce qu'un objet immuable?

Pour le dire simplement, c'est un objet qui a les mêmes données.

Je ne sais pas ce que cela signifie, alors je vais l'expliquer en utilisant Java String et StringBuilder. Regardez d'abord le code ci-dessous


String str = "A";

for(int i = 0; i < 2 ; i++){
  str += "A";
}

System.out.println(str);

Le résultat de sortie est le suivant


AAA

Ce code ajoute simplement A à str deux fois Dans le code ci-dessus, str est déclaré en tant que type String, mais déclarer str en tant que type StringBuilder ne modifie pas le résultat de sortie.

Cependant, la méthode d'instanciation est différente entre String et StringBuilder. Voyons quel genre d'instance restera à la fin

** Pour StringBuilder ** StringBuilder.png Étant donné que seule la même instance que le résultat de sortie est générée, l'instance restante finale n'est qu'un AAA.

** Pour chaîne ** String.png Dans le cas de String, une autre instance est créée chaque fois que A est ajouté. Par conséquent, les instances A, AA, AAA restent Les instances A et AA sont hors de référence et sont soumises à GC

Différence entre String et StringBuilder

Comme vous pouvez le voir dans l'exemple ci-dessus, StringBuilder a la valeur d'une instance réécrite, tandis que String a une autre instance sans réécrire la valeur. Génération de </ font> couleur </ font>

StringBuilder ne crée qu'une seule instance, donc c'est moins cher Puisque String a créé des instances plusieurs fois, la charge sera plus élevée en conséquence.

En regardant cela, il ne sert à rien d'utiliser String! Vous pouvez penser que ** c'est une erreur **

Objets immuables et variables

StringBuilder est appelé un objet mutable car il réécrit la valeur La chaîne est appelée un objet immuable car elle n'a pas réécrit sa valeur

Comme mentionné ci-dessus, les avantages de StringBuilder conduisent à des performances améliorées. Dans cet exemple, A n'est combiné que deux fois, mais si c'est 10 000 fois, il ne sera pas possible de se moquer du coût de création de l'instance.

Cependant, même s'il s'agit de la même instance, la valeur sera réécrite en fonction de la synchronisation, telle que A, AA, AAAAAAA. C'est un gros problème, et une fois que vous faites une modification, cela affecte également les objets qui y font référence. 可変オブジェクト.png

Cela signifie que les objets variables ** auront des effets secondaires à chaque fois qu'ils seront modifiés ** </ font color>

Et pour String? La création de plusieurs instances coûte de l'argent, mais un objet a toujours la même valeur. Si vous voulez une instance avec une valeur différente, créez une autre instance Par conséquent, cela n'affecte pas les autres objets. 不変オブジェクト.png Autrement dit, ** Les objets immuables peuvent être utilisés pour garantir que leurs valeurs ne changent pas ** </ font color>

Les objets immuables sont également utiles pour la programmation multithread Les objets immuables ne changent pas de valeur, vous n'avez donc pas à synchroniser avec synchronized ... C'est un objet variable qui compte dans un environnement multithread.

** Les objets immuables sont essentiellement thread-safe ** </ font color>

Même si vous créez un objet variable, cela permet d'éviter les accidents en réduisant autant que possible les parties variables.

Comment créer un objet immuable

J'espère que vous pouvez voir à quel point les objets immuables sont utiles par l'explication jusqu'à présent. À partir de là, j'expliquerai comment créer réellement un objet immuable.

Empêcher l'héritage de classe

Si vous autorisez l'héritage de la classe, l'encapsulation sera interrompue, comme l'héritage et l'ajout de méthodes étranges. Déclarez la classe afin qu'elle ne puisse pas être héritée car nous ne voulons pas causer de problèmes tels que des objets immuables ou des objets variables en fonction de la façon dont ils sont créés même s'ils sont du même type.


final class TestClass{}

S'il s'agit d'une classe d'un module encapsulé, il n'est pas visible de l'extérieur, il est donc pratiquement impossible d'hériter sans ajouter final, mais si vous ne comptez pas en hériter, ajoutez final pour hériter Il est courant d'interdire (la même chose est vraie pour les objets variables)

Rendre tous les champs constants

Les objets immuables ne veulent pas que leurs valeurs soient modifiées, alors faites-en toutes les constantes Définissez également la visibilité afin que les variables ne soient pas directement référencées.


private final int num;
private final StringBuilder str;

Il n'y a aucun problème si vous déclarez de la même manière quel que soit le type de données de base et le type de données de référence. Dans le cas du type de données de référence, il est nécessaire de modifier un peu le traitement, mais il sera décrit plus loin.

Ne fais pas un mutateur

Un mutateur est une opération qui réécrit des valeurs telles que les setters. Bien sûr, vous n'avez pas besoin d'une méthode pour le faire car vous ne voulez pas réécrire la valeur

Attention aux getters

Regardez d'abord le code ci-dessous


public int getNum(){
  return num;
}

public StringBuilder getStr(){
  return str;
}

La valeur de retour de la méthode getNum est le type de données de base et la valeur de retour de la méthode getStr est le type de données de référence. Pour le type de données de base, une copie de la valeur de num est renvoyée, il n'y a donc pas de problème. Toutefois, dans le cas du type de données référence, un problème se produit car la référence est renvoyée telle quelle.

Puisque la référence de l'instance peut être obtenue du côté qui a appelé la méthode getStr, la valeur du contenu de l'instance str peut être réécrite.

Pour éviter cela, j'ai essayé d'améliorer le code précédent


public String getStr(){
  String result = str.toString;
  return result;
}

La classe StringBuilder avait une méthode appelée méthode toString qui change un objet variable en un objet immuable, j'ai donc essayé de l'utiliser. Cependant, cette partie de conversion de type n'est pas très importante dans la description "Attention aux getters". L'important est que les valeurs de soient copiées et renvoyées </ font color>

Créer une autre instance de copie et renvoyer une référence à l'instance de copie En faisant cela, l'appelant de la méthode getStr peut utiliser n'importe quel moyen pour falsifier l'instance de copie, mais cela n'interférera pas avec l'instance d'origine.

Cette technique s'appelle ** copie défensive ** En utilisant une copie défensive, même une classe qui gère des objets variables tels que StringBuilder peut faire de la classe un objet immuable.

Il y a quelques mises en garde lors de l'utilisation de la copie défensive ① Les performances peuvent se détériorer Dans le cas d'un tableau, d'une liste, etc., les performances peuvent être dégradées car tout le contenu doit être copié. Mais gardez à l'esprit que les objets immuables valent le prix. ② N'utilisez pas la méthode de clonage La méthode de clonage java a quelques problèmes, veuillez donc ne pas l'utiliser lors de la création d'une copie défensive

Exemple de code

Je vais mettre l'exemple de code de l'objet immuable expliqué jusqu'à présent (La partie constructeur a été ajoutée)


final class TestClass{

  private final int num;
  private final StringBuilder str;

  public TestClass(int num,StringBuilder str){
    this.num = num;
    //Faites également une copie défensive ici
    this.str = new StringBuilder(str);
  }

  public int getNum(){
    return num;
  }

  public String getStr(){
    String result = str.toString;
    return result;
  }

}
  • Ajouté le 1er juillet 2020 (Merci à @ saka1029 pour l'avoir signalé) Vous devez également faire une copie défensive lors de la réception d'un objet mutable comme argument dans le constructeur Si vous ne le faites pas, l'appelant et TestClass auront la même référence et vous pourrez réécrire la valeur. ** Utilisez une copie défensive lors de la réception et de la transmission d'objets variables ** </ font color>

prime

Dans l'exemple de code ci-dessus, le constructeur est public, mais Effective Java recommande une fabrique statique. Cela s'applique également au développement actuel de l'API Réécrivons l'exemple de code plus tôt


final class TestClass{

  private final int num;
  private final StringBuilder str;

  //Interdire l'utilisation de constructeurs de l'extérieur
  //Le code suivant ne peut pas être utilisé côté appelant
  // TestClass tc = new TestClass(10,"AAA");
  private TestClass(int num,StringBuilder str){
    this.num = num;
    this.str = new StringBuilder(str);
  }


  //static factory
  public static final TestClass newInstance(int num,StringBuilder str){
    return new TestClass(num,str);
  }

  public int getNum(){
    return num;
  }

  public String getStr(){
    String result = str.toString;
    return result;
  }

}

Le code d'appel ressemble à ceci


TestClass tc = TestClass.newInstance(10,"AAA");

Dans le cas du code ci-dessus, ce ne sera pas un singleton car une nouvelle instance de type TestClass sera créée chaque fois que la méthode newInstance, qui est une fabrique statique, est appelée. Mais si vous ne voulez pas être singleton, vous pouvez utiliser cette technique

Si vous voulez le rendre singleton, n'utilisez pas new dans la méthode de fabrique statique, créez simplement une instance à l'avance et retournez

Avec l'avènement des conteneurs DI, les chances d'appeler directement une usine statique peuvent être réduites, mais cette technique est toujours active.

Résumé

J'ai présenté ce qu'est un objet immuable et comment le fabriquer.

Il n'est pas exagéré de dire qu'il n'y a pas de système qui ne nécessite des objets immuables dans aucun système, alors n'oubliez pas et utilisez-le.

Recommended Posts