Comment créer une classe immuable en Java

Introduction

Cet article donne un aperçu de la création d’une classe immuable en programmation Java.

Un objet est immuable lorsqu’il ne change pas d’état après son initialisation. Par exemple, String est une classe immuable et, une fois instancié, la valeur d’un objet String ne change jamais. Apprenez-en davantage sur pourquoi la classe String est immuable en Java.

Étant donné qu’un objet immuable ne peut pas être mis à jour, les programmes doivent créer un nouvel objet pour chaque changement d’état. Cependant, les objets immuables présentent également les avantages suivants :

  • Une classe immuable est bonne à des fins de mise en cache car vous n’avez pas à vous soucier des changements de valeur.
  • Une classe immuable est intrinsèquement sûre pour le thread, donc vous n’avez pas à vous soucier de la sécurité des threads dans des environnements multi-thread.

Apprenez-en davantage sur le multi-threading en Java et parcourez les questions d’entretien sur le multi-threading en Java.

Création d’une classe immuable en Java

Pour créer une classe immuable en Java, suivez ces principes généraux:

  1. Déclarez la classe comme final pour qu’elle ne puisse pas être étendue.
  2. Rendez tous les champs private afin que l’accès direct ne soit pas autorisé.
  3. Ne fournissez pas de méthodes setter pour les variables.
  4. Rendez tous les champs mutables final afin qu’une valeur de champ ne puisse être assignée qu’une seule fois.
  5. Initialisez tous les champs à l’aide d’une méthode constructeur effectuant une copie profonde.
  6. Effectuez une copie des objets dans les méthodes getter pour renvoyer une copie plutôt que la référence réelle de l’objet.

La classe suivante est un exemple qui illustre les bases de l’immuabilité. La classe FinalClassExample définit les champs et fournit la méthode constructeur qui utilise une copie profonde pour initialiser l’objet. Le code dans la méthode main du fichier FinalClassExample.java teste l’immuabilité de l’objet.

Créez un nouveau fichier appelé FinalClassExample.java et copiez le code suivant :

FinalClassExample.java
import java.util.HashMap;
import java.util.Iterator;

public final class FinalClassExample {

	// champs de la classe FinalClassExample
	private final int id;
	
	private final String name;
	
	private final HashMap<String,String> testMap;

	
	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	// Fonction Getter pour les objets mutables

	public HashMap<String, String> getTestMap() {
		return (HashMap<String, String>) testMap.clone();
	}

	// Méthode du constructeur effectuant une copie profonde
	
	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Deep Copy for Object initialization");

		// Le mot-clé "this" fait référence à l'objet actuel
		this.id=i;
		this.name=n;

		HashMap<String,String> tempMap=new HashMap<String,String>();
		String key;
		Iterator<String> it = hm.keySet().iterator();
		while(it.hasNext()){
			key=it.next();
			tempMap.put(key, hm.get(key));
		}
		this.testMap=tempMap;
	}

	// Tester la classe immuable

	public static void main(String[] args) {
		HashMap<String, String> h1 = new HashMap<String,String>();
		h1.put("1", "first");
		h1.put("2", "second");
		
		String s = "original";
		
		int i=10;
		
		FinalClassExample ce = new FinalClassExample(i,s,h1);
		
		// imprimer les valeurs de ce
		System.out.println("ce id: "+ce.getId());
		System.out.println("ce name: "+ce.getName());
		System.out.println("ce testMap: "+ce.getTestMap());
		// changer les valeurs de la variable locale
		i=20;
		s="modified";
		h1.put("3", "third");
		// imprimer à nouveau les valeurs
		System.out.println("ce id after local variable change: "+ce.getId());
		System.out.println("ce name after local variable change: "+ce.getName());
		System.out.println("ce testMap after local variable change: "+ce.getTestMap());
		
		HashMap<String, String> hmTest = ce.getTestMap();
		hmTest.put("4", "new");
		
		System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());

	}

}

Compiler et exécuter le programme :

  1. javac FinalClassExample.java
  2. java FinalClassExample

Remarque : Vous pourriez obtenir le message suivant lors de la compilation du fichier : Remarque : FinalClassExample.java utilise des opérations non vérifiées ou non sécurisées car la méthode Getter utilise une conversion non vérifiée de HashMap<String,String> en Object. Vous pouvez ignorer l’avertissement du compilateur à des fins d’exemple.

Vous obtenez la sortie suivante :

Output
Performing Deep Copy for Object initialization ce id: 10 ce name: original ce testMap: {1=first, 2=second} ce id after local variable change: 10 ce name after local variable change: original ce testMap after local variable change: {1=first, 2=second} ce testMap after changing variable from getter methods: {1=first, 2=second}

La sortie montre que les valeurs de HashMap n’ont pas changé car le constructeur utilise une copie profonde et la fonction Getter renvoie un clone de l’objet d’origine.

Que se passe-t-il lorsque vous n’utilisez pas de copie profonde et de clonage

Vous pouvez apporter des modifications au fichier FinalClassExample.java pour montrer ce qui se passe lorsque vous utilisez une copie superficielle au lieu d’une copie profonde et que vous retournez l’objet au lieu d’une copie. L’objet n’est plus immuable et peut être modifié. Effectuez les modifications suivantes dans le fichier exemple (ou copiez et collez à partir de l’exemple de code):

  • Supprimez la méthode constructeur fournissant une copie profonde et ajoutez la méthode constructeur fournissant une copie superficielle qui est surlignée dans l’exemple suivant.
  • Dans la fonction getter, supprimez return (HashMap<String, String>) testMap.clone(); et ajoutez return testMap;.

Le fichier exemple devrait maintenant ressembler à ceci:

FinalClassExample.java
import java.util.HashMap;
import java.util.Iterator;

public final class FinalClassExample {

	// champs de la classe FinalClassExample
	private final int id;
	
	private final String name;
	
	private final HashMap<String,String> testMap;

	
	public int getId() {
		return id;
	}

	public String getName() {
		return name;
	}

	// Fonction getter pour les objets mutables

	public HashMap<String, String> getTestMap() {
		return testMap;
	}

	// Méthode constructeur effectuant une copie superficielle

	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Shallow Copy for Object initialization");
		this.id=i;
		this.name=n;
		this.testMap=hm;
	}

	// Tester la classe immuable

	public static void main(String[] args) {
		HashMap<String, String> h1 = new HashMap<String,String>();
		h1.put("1", "first");
		h1.put("2", "second");
		
		String s = "original";
		
		int i=10;
		
		FinalClassExample ce = new FinalClassExample(i,s,h1);
		
		// imprimer les valeurs de ce
		System.out.println("ce id: "+ce.getId());
		System.out.println("ce name: "+ce.getName());
		System.out.println("ce testMap: "+ce.getTestMap());
		// modifier les valeurs des variables locales
		i=20;
		s="modified";
		h1.put("3", "third");
		// imprimer à nouveau les valeurs
		System.out.println("ce id after local variable change: "+ce.getId());
		System.out.println("ce name after local variable change: "+ce.getName());
		System.out.println("ce testMap after local variable change: "+ce.getTestMap());
		
		HashMap<String, String> hmTest = ce.getTestMap();
		hmTest.put("4", "new");
		
		System.out.println("ce testMap after changing variable from getter methods: "+ce.getTestMap());

	}

}

Compilez et exécutez le programme:

  1. javac FinalClassExample.java
  2. java FinalClassExample

Vous obtenez la sortie suivante :

Output
Performing Shallow Copy for Object initialization ce id: 10 ce name: original ce testMap: {1=first, 2=second} ce id after local variable change: 10 ce name after local variable change: original ce testMap after local variable change: {1=first, 2=second, 3=third} ce testMap after changing variable from getter methods: {1=first, 2=second, 3=third, 4=new}

La sortie montre que les valeurs de la HashMap ont été modifiées car la méthode du constructeur utilise une copie superficielle, il y a une référence directe à l’objet d’origine dans la fonction getter.

Conclusion

Vous avez appris certains des principes généraux à suivre lorsque vous créez des classes immuables en Java, notamment l’importance de la copie profonde. Continuez votre apprentissage avec d’autres tutoriels Java.

Source:
https://www.digitalocean.com/community/tutorials/how-to-create-immutable-class-in-java