Wie man in Java eine unveränderliche Klasse erstellt

Einführung

Dieser Artikel gibt einen Überblick darüber, wie man in der Java-Programmierung eine unveränderliche Klasse erstellt.

Ein Objekt ist unveränderlich, wenn sich sein Zustand nach der Initialisierung nicht mehr ändert. Zum Beispiel ist die String-Klasse unveränderlich, und einmal instanziiert, ändert sich der Wert eines String-Objekts nie. Erfahren Sie mehr darüber, warum die String-Klasse in Java unveränderlich ist.

Weil ein unveränderliches Objekt nicht aktualisiert werden kann, müssen Programme für jede Zustandsänderung ein neues Objekt erstellen. Unveränderliche Objekte haben jedoch auch folgende Vorteile:

  • Eine unveränderliche Klasse eignet sich gut für Caching-Zwecke, da Sie sich keine Gedanken über Wertänderungen machen müssen.
  • Eine unveränderliche Klasse ist von Natur aus threadsicher, sodass Sie sich in Mehrfadenumgebungen keine Gedanken über Threadsicherheit machen müssen.

Erfahren Sie mehr über Multithreading in Java und durchsuchen Sie die Java-Multithreading-Interviewfragen.

Erstellen einer unveränderlichen Klasse in Java

Um eine unveränderliche Klasse in Java zu erstellen, müssen Sie diesen allgemeinen Grundsätzen folgen:

  1. Deklarieren Sie die Klasse als final, damit sie nicht erweitert werden kann.
  2. Machen Sie alle Felder private, damit ein direkter Zugriff nicht erlaubt ist.
  3. Stellen Sie keine Settermethoden für Variablen bereit.
  4. Machen Sie alle veränderlichen Felder final, damit der Wert eines Feldes nur einmal zugewiesen werden kann.
  5. Initialisieren Sie alle Felder mithilfe einer Konstruktor-Methode, die eine Deep-Copy durchführt.
  6. Führen Sie eine Klonung der Objekte in den Getter-Methoden durch, um eine Kopie zurückzugeben, anstatt die tatsächliche Objektreferenz zurückzugeben.

Die folgende Klasse ist ein Beispiel, das die Grundlagen der Unveränderlichkeit veranschaulicht. Die Klasse FinalClassExample definiert die Felder und stellt die Konstruktor-Methode bereit, die Deep-Copy verwendet, um das Objekt zu initialisieren. Der Code in der main-Methode der Datei FinalClassExample.java testet die Unveränderlichkeit des Objekts.

Erstellen Sie eine neue Datei mit dem Namen FinalClassExample.java und kopieren Sie den folgenden Code hinein:

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

public final class FinalClassExample {

	// Felder der Klasse 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;
	}

	// Getter-Funktion für veränderbare Objekte

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

	// Konstruktor-Methode führt eine Deep Copy durch
	
	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Deep Copy for Object initialization");

		// "this" Schlüsselwort bezieht sich auf das aktuelle Objekt
		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;
	}

	// Testen der unveränderlichen Klasse

	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);
		
		// Drucken der Werte von ce
		System.out.println("ce id: "+ce.getId());
		System.out.println("ce name: "+ce.getName());
		System.out.println("ce testMap: "+ce.getTestMap());
		// Ändern der Werte der lokalen Variablen
		i=20;
		s="modified";
		h1.put("3", "third");
		// erneutes Drucken der Werte
		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());

	}

}

Das Programm kompilieren und ausführen:

  1. javac FinalClassExample.java
  2. java FinalClassExample

Hinweis: Beim Kompilieren der Datei erhalten Sie möglicherweise folgende Meldung: Note: FinalClassExample.java verwendet nicht überprüfbare oder unsichere Operationen, da die Getter-Methode einen nicht überprüften Cast von HashMap<String,String> zu Object verwendet. Sie können die Compiler-Warnung für dieses Beispiel ignorieren.

Sie erhalten die folgende Ausgabe:

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}

Die Ausgabe zeigt, dass sich die Werte der HashMap nicht geändert haben, da der Konstruktor eine Deep Copy verwendet und die Getter-Funktion einen Klon des Originalobjekts zurückgibt.

Was passiert, wenn Sie keine Tiefenkopie und Klonen verwenden?

Sie können Änderungen an der Datei FinalClassExample.java vornehmen, um zu zeigen, was passiert, wenn Sie anstelle einer Tiefenkopie eine Flachkopie verwenden und das Objekt anstelle einer Kopie zurückgeben. Das Objekt ist nicht mehr unveränderlich und kann geändert werden. Führen Sie die folgenden Änderungen an der Beispieldatei durch (oder kopieren Sie sie aus dem Codebeispiel):

  • Löschen Sie die Konstruktor-Methode, die eine Tiefenkopie bereitstellt, und fügen Sie die Konstruktor-Methode ein, die eine Flachkopie bereitstellt und die im folgenden Beispiel hervorgehoben ist.
  • Im Getter-Funktion löschen Sie return (HashMap<String, String>) testMap.clone(); und fügen Sie return testMap; hinzu.

Die Beispieldatei sollte jetzt so aussehen:

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

public final class FinalClassExample {

	// Felder der Klasse 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;
	}

	// Getter-Funktion für veränderliche Objekte

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

	// Konstruktor-Methode, die eine Flachkopie durchführt

	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;
	}

	// Testen Sie die unveränderliche Klasse

	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);
		
		// geben Sie die Werte von ce aus
		System.out.println("ce id: "+ce.getId());
		System.out.println("ce name: "+ce.getName());
		System.out.println("ce testMap: "+ce.getTestMap());
		// ändern Sie die Werte der lokalen Variable
		i=20;
		s="modified";
		h1.put("3", "third");
		// geben Sie die Werte erneut aus
		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());

	}

}

Kompilieren und führen Sie das Programm aus:

  1. javac FinalClassExample.java
  2. java FinalClassExample

Du erhältst folgende Ausgabe:

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}

Die Ausgabe zeigt, dass sich die HashMap-Werte geändert haben, weil der Konstruktor eine Flachkopie verwendet und es daher eine direkte Referenz zum Originalobjekt in der Getter-Funktion gibt.

Schlussfolgerung

Du hast einige allgemeine Grundsätze kennengelernt, die du beachten solltest, wenn du in Java unveränderliche Klassen erstellst, einschließlich der Bedeutung der Tiefenkopie. Setze dein Lernen mit weiteren Java-Tutorials fort.

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