Hoe maak je een onveranderlijke klasse in Java

Inleiding

Dit artikel geeft een overzicht van hoe je een onveranderlijke klasse kunt maken in Java-programmering.

Een object is onveranderlijk wanneer de toestand ervan niet verandert nadat het is geïnitialiseerd. Bijvoorbeeld, String is een onveranderlijke klasse en, eenmaal geïnstantieerd, verandert de waarde van een String-object nooit. Leer meer over waarom de klasse String onveranderlijk is in Java.

Omdat een onveranderlijk object niet kan worden bijgewerkt, moeten programma’s voor elke verandering van toestand een nieuw object maken. Echter, onveranderlijke objecten hebben ook de volgende voordelen:

  • Een onveranderlijke klasse is goed voor cachingdoeleinden omdat je je geen zorgen hoeft te maken over waardeveranderingen.
  • Een onveranderlijke klasse is inherent thread-safe, dus je hoeft je geen zorgen te maken over threadveiligheid in multi-threaded omgevingen.

Leer meer over multi-threading in Java en bekijk de Java Multi-Threaded Interview Questions.

Het creëren van een Onveranderlijke Klasse in Java

Om een onveranderlijke klasse in Java te creëren, moet je deze algemene principes volgen:

  1. Declareren van de klasse als final zodat deze niet uitgebreid kan worden.
  2. Maak alle velden private zodat directe toegang niet is toegestaan.
  3. Bied geen setter-methoden voor variabelen.
  4. Maak alle mutabele velden final zodat de waarde van een veld slechts één keer kan worden toegewezen.
  5. Initialiseer alle velden met behulp van een constructor-methode die diepe kopieën uitvoert.
  6. Voer kloonbewerkingen uit van objecten in de getter-methoden om een kopie terug te geven in plaats van de werkelijke objectreferentie.

De volgende klasse is een voorbeeld dat de basisprincipes van onveranderlijkheid illustreert. De klasse FinalClassExample definieert de velden en biedt de constructor-methode die diepe kopieën gebruikt om het object te initialiseren. De code in de main-methode van het bestand FinalClassExample.java test de onveranderlijkheid van het object.

Maak een nieuw bestand genaamd FinalClassExample.java aan en kopieer de volgende code:

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

public final class FinalClassExample {

	// velden van de FinalClassExample-klasse
	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-functie voor wijzigbare objecten

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

	// Constructor-methode voor diepe kopie
	
	public FinalClassExample(int i, String n, HashMap<String,String> hm){
		System.out.println("Performing Deep Copy for Object initialization");

		// Het "this"-woord verwijst naar het huidige object
		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;
	}

	// Test de onveranderlijke 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);
		
		// print de ce-waarden
		System.out.println("ce id: "+ce.getId());
		System.out.println("ce name: "+ce.getName());
		System.out.println("ce testMap: "+ce.getTestMap());
		// wijzig de waarden van de lokale variabele
		i=20;
		s="modified";
		h1.put("3", "third");
		// print de waarden opnieuw
		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());

	}

}

Compileer en voer het programma uit:

  1. javac FinalClassExample.java
  2. java FinalClassExample

Opmerking: U kunt mogelijk het volgende bericht krijgen bij het compileren van het bestand: Let op: FinalClassExample.java maakt gebruik van ongecontroleerde of onveilige bewerkingen omdat de getter-methode een ongecontroleerde cast gebruikt van HashMap<String,String> naar Object. U kunt de compilerwaarschuwing negeren voor dit voorbeeld.

U krijgt de volgende uitvoer:

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}

De uitvoer toont aan dat de waarden van de HashMap niet zijn gewijzigd omdat de constructor een diepe kopie gebruikt en de getter-functie een kloon van het originele object retourneert.

Wat gebeurt er wanneer je geen diepe kopie en klonen gebruikt?

Je kunt wijzigingen aanbrengen in het bestand FinalClassExample.java om te laten zien wat er gebeurt wanneer je in plaats van een diepe kopie een ondiepe kopie gebruikt en het object retourneert in plaats van een kopie. Het object is niet langer onveranderlijk en kan worden gewijzigd. Voer de volgende wijzigingen door in het voorbeeldbestand (of kopieer en plak uit het codevoorbeeld):

  • Verwijder de constructor-methode die een diepe kopie levert en voeg de constructor-methode toe die een ondiepe kopie levert, zoals gemarkeerd in het volgende voorbeeld.
  • In de getter-functie, verwijder return (HashMap<String, String>) testMap.clone(); en voeg return testMap; toe.

Het voorbeeldbestand zou er nu zo uit moeten zien:

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

public final class FinalClassExample {

	// velden van de 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-functie voor veranderlijke objecten

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

	// Constructor-methode voor het uitvoeren van een ondiepe kopie

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

	// Test de onveranderlijke 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);
		
		// druk de ce-waarden af
		System.out.println("ce id: "+ce.getId());
		System.out.println("ce name: "+ce.getName());
		System.out.println("ce testMap: "+ce.getTestMap());
		// wijzig de waarden van de lokale variabele
		i=20;
		s="modified";
		h1.put("3", "third");
		// druk de waarden opnieuw af
		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());

	}

}

Compileer en voer het programma uit:

  1. javac FinalClassExample.java
  2. java FinalClassExample

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}

Het resultaat toont aan dat de waarden van de HashMap zijn gewijzigd omdat de constructor-methode een ondiepe kopie gebruikt, waardoor er een directe verwijzing is naar het oorspronkelijke object in de getter-functie.

Conclusie

Je hebt enkele algemene principes geleerd om te volgen bij het maken van onveranderlijke klassen in Java, inclusief het belang van diepe kopie. Ga verder met leren met meer Java tutorials.

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