Java Object clone() Methode – Klonen in Java

Klonen is het proces van het maken van een kopie van een Object. De Java Object-klasse wordt geleverd met de native `clone()` methode die een kopie van de bestaande instantie retourneert. Omdat Object de basisklasse is in Java, ondersteunen alle objecten standaard klonen.

Java Object Klonen

Als je de Java Object `clone()` methode wilt gebruiken, moet je de `java.lang.Cloneable` marker-interface implementeren. Anders wordt er tijdens runtime een `CloneNotSupportedException` gegooid. Ook is `Object clone` een beschermde methode, dus je moet deze overschrijven. Laten we Object klonen in Java bekijken met een voorbeeldprogramma.

package com.journaldev.cloning;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class Employee implements Cloneable {

	private int id;

	private String name;

	private Map<String, String> props;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Map<String, String> getProps() {
		return props;
	}

	public void setProps(Map<String, String> p) {
		this.props = p;
	}

	 @Override
	 public Object clone() throws CloneNotSupportedException {
	 return super.clone();
	 }

}

We gebruiken de Object `clone()` methode, dus we hebben de `Cloneable` interface geïmplementeerd. We roepen de superklasse `clone()` methode aan, d.w.z. de Object `clone()` methode.

Het Gebruik van de Object `clone()` Methode

Laten we een testprogramma maken om de object clone() methode te gebruiken om een kopie van de instantie te maken.

package com.journaldev.cloning;

import java.util.HashMap;
import java.util.Map;

public class CloningTest {

	public static void main(String[] args) throws CloneNotSupportedException {

		Employee emp = new Employee();

		emp.setId(1);
		emp.setName("Pankaj");
		Map props = new HashMap<>();
		props.put("salary", "10000");
		props.put("city", "Bangalore");
		emp.setProps(props);

		Employee clonedEmp = (Employee) emp.clone();

		// Controleer of de attributen emp en clonedEmp hetzelfde of verschillend zijn
		System.out.println("emp and clonedEmp == test: " + (emp == clonedEmp));
		
		System.out.println("emp and clonedEmp HashMap == test: " + (emp.getProps() == clonedEmp.getProps()));
		
		// Laten we het effect zien van het gebruik van standaard klonen
		
		// Verander emp eigenschappen
		emp.getProps().put("title", "CEO");
		emp.getProps().put("city", "New York");
		System.out.println("clonedEmp props:" + clonedEmp.getProps());

		// Verander emp naam
		emp.setName("new");
		System.out.println("clonedEmp name:" + clonedEmp.getName());

	}

}

Output:

emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: true
clonedEmp props:{city=New York, salary=10000, title=CEO}
clonedEmp name:Pankaj

CloneNotSupportedException tijdens runtime

Als onze Employee-klasse de Cloneable-interface niet implementeert, zal het bovenstaande programma een CloneNotSupportedException runtime-uitzondering veroorzaken.

Exception in thread "main" java.lang.CloneNotSupportedException: com.journaldev.cloning.Employee
	at java.lang.Object.clone(Native Method)
	at com.journaldev.cloning.Employee.clone(Employee.java:41)
	at com.journaldev.cloning.CloningTest.main(CloningTest.java:19)

Begrip van Objectklonen

Laten we naar de bovenstaande output kijken en begrijpen wat er gebeurt met de Object clone()-methode.

  1. emp en clonedEmp == test: false: Het betekent dat emp en clonedEmp twee verschillende objecten zijn en niet naar hetzelfde object verwijzen. Dit komt overeen met de vereiste voor het klonen van Java-objecten.
  2. emp en clonedEmp HashMap == test: true: Dus zowel de variabelen emp als clonedEmp verwijzen naar hetzelfde object. Dit kan een serieus probleem met gegevensintegriteit zijn als we de onderliggende objectwaarde veranderen. Elke verandering in de waarde kan ook worden weerspiegeld in het gekloonde exemplaar.
  3. clonedEmp eigenschappen:{stad=New York, salaris=10000, functie=CEO}: We hebben geen wijzigingen aangebracht in de eigenschappen van clonedEmp, maar toch zijn ze veranderd omdat zowel emp als clonedEmp variabelen verwijzen naar hetzelfde object. Dit gebeurt omdat de standaard Object clone() methode een ondiepe kopie maakt. Dit kan een probleem zijn wanneer u volledig losgekoppelde objecten wilt maken via het kloonproces. Dit kan leiden tot ongewenste resultaten, vandaar de noodzaak om de Object clone() methode correct te overschrijven.
  4. clonedEmp naam:Pankaj: Wat is hier gebeurd? We hebben de naam van emp veranderd, maar de naam van clonedEmp is niet veranderd. Dit komt omdat String onveranderlijk is. Dus wanneer we de naam van emp instellen, wordt er een nieuwe string gemaakt en wordt de referentie naar de naam van emp gewijzigd in this.name = name;. Daarom blijft de naam van clonedEmp onveranderd. U zult een vergelijkbaar gedrag vinden voor elk ander primitief variabel type. Dus we zitten goed met de standaard klonen van Java-objecten zolang we alleen primitieve en onveranderlijke variabelen in het object hebben.

Object Kloon Types

Er zijn twee soorten objectkloons – oppervlakkige kloon en diepe kloon. Laten we elk van hen begrijpen en de beste manier vinden om kloons te implementeren in onze Java-programma’s.

1. Oppervlakkige Kloon

De standaardimplementatie van de Java Object clone() methode gebruikt oppervlakkige kopie. Het gebruikt de reflectie API om de kopie van de instantie te maken. Het onderstaande codefragment toont de implementatie van oppervlakkige kloon.

@Override
 public Object clone() throws CloneNotSupportedException {
 
	 Employee e = new Employee();
	 e.setId(this.id);
	 e.setName(this.name);
	 e.setProps(this.props);
	 return e;
}

2. Diepe Kloon

Bij diepe klonen moeten we velden één voor één kopiëren. Als we een veld hebben met geneste objecten zoals List, Map, etc. dan moeten we de code schrijven om ze ook één voor één te kopiëren. Daarom wordt het diepe klonen of diepe kopiëren genoemd. We kunnen de Employee clone-methode overschrijven zoals in de volgende code voor diepe klonen.

public Object clone() throws CloneNotSupportedException {

	Object obj = super.clone(); //utilize clone Object method

	Employee emp = (Employee) obj;

	// diepe klonen voor onveranderlijke velden
	emp.setProps(null);
	Map hm = new HashMap<>();
	String key;
	Iterator it = this.props.keySet().iterator();
	// Diepe kopiëren van veld voor veld
	while (it.hasNext()) {
		key = it.next();
		hm.put(key, this.props.get(key));
	}
	emp.setProps(hm);
	
	return emp;
}

Met deze clone() methode-implementatie zal ons testprogramma de volgende output produceren.

emp and clonedEmp == test: false
emp and clonedEmp HashMap == test: false
clonedEmp props:{city=Bangalore, salary=10000}
clonedEmp name:Pankaj

In de meeste gevallen is dit wat we willen. De clone() methode moet een nieuw object retourneren dat volledig losgekoppeld is van de oorspronkelijke instantie. Dus als je van plan bent Object clone en klonen in je programma te gebruiken, doe het dan verstandig en overschrijf het goed door rekening te houden met muteerbare velden. Het kan een ontmoedigende taak zijn als je klasse een andere klasse uitbreidt die op zijn beurt weer een andere klasse uitbreidt, enzovoort. Je zult helemaal moeten afdalen in de objectoverervingshiërarchie om voor een diepe kopie van alle muteerbare velden te zorgen.

Klonen met behulp van Serialisatie?

Een manier om gemakkelijk diep klonen uit te voeren is via serialisatie. Maar serialisatie is een kostbare procedure en je klasse moet de interface Serializable implementeren. Alle velden en superklassen moeten ook Serializable implementeren.

Gebruik van Apache Commons Util

Als je al Apache Commons Util klassen gebruikt in je project en je klasse serialiseerbaar is, gebruik dan de onderstaande methode.

Employee clonedEmp = org.apache.commons.lang3.SerializationUtils.clone(emp);

Kopieconstructeur voor klonen

We kunnen een kopieconstructeur definiëren om een kopie van het object te maken. Waarom afhankelijk zijn van de Object clone() methode? Bijvoorbeeld, we kunnen een Employee kopieconstructeur hebben zoals de volgende code.

public Employee(Employee emp) {
	
	this.setId(emp.getId());
	this.setName(emp.getName());
	
	Map hm = new HashMap<>();
	String key;
	Iterator it = emp.getProps().keySet().iterator();
	// Diepe kopie van veld per veld
	while (it.hasNext()) {
		key = it.next();
		hm.put(key, emp.getProps().get(key));
	}
	this.setProps(hm);

}

Telkens wanneer we een kopie van het werknemersobject nodig hebben, kunnen we deze krijgen met Employee clonedEmp = new Employee(emp);. Het schrijven van een kopieconstructeur kan echter een vervelende klus zijn als uw klasse veel variabelen heeft, vooral primitieve en onveranderlijke.

Best Practices voor Java Object Klonen

  1. Gebruik de standaard Object clone() methode alleen wanneer uw klasse primitieve en onveranderlijke variabelen heeft of u een ondiepe kopie wilt. In geval van overerving moet u alle klassen controleren die u uitbreidt tot het Objectniveau.

  2. Je kunt ook een kopieconstructor definiëren als je klasse voornamelijk muteerbare eigenschappen heeft.

  3. Gebruik de Object clone() methode door super.clone() aan te roepen in de overschreven clone methode, maak vervolgens de nodige wijzigingen voor het diep kopiëren van muteerbare velden.

  4. Als je klasse serializable is, kun je serialisatie gebruiken voor klonen. Het zal echter ten koste gaan van de prestaties, dus doe wat benchmarking voordat je serialisatie gebruikt voor klonen.

  5. Als je een klasse uitbreidt en deze heeft de kloonmethode correct gedefinieerd met een diepe kopie, dan kun je de standaard kloonmethode gebruiken. Bijvoorbeeld, laten we de clone() methode correct definiëren in de Employee klasse zoals hieronder.

    @Override
    public Object clone() throws CloneNotSupportedException {
    
    	Object obj = super.clone();
    
    	Employee emp = (Employee) obj;
    
    	// diepe klonen voor onveranderlijke velden
    	emp.setProps(null);
    	Map<String, String> hm = new HashMap<>();
    	String key;
    	Iterator<String> it = this.props.keySet().iterator();
    	// Diepe kopiëren van veld naar veld
    	while (it.hasNext()) {
    		key = it.next();
    		hm.put(key, this.props.get(key));
    	}
    	emp.setProps(hm);
    
    	return emp;
    }
    

    We kunnen een subklasse maken en de superklasse diepe klonen gebruiken zoals hieronder.

    package com.journaldev.cloning;
    
    public class EmployeeWrap extends Employee implements Cloneable {
    
    	private String title;
    
    	public String getTitle() {
    		return title;
    	}
    
    	public void setTitle(String t) {
    		this.title = t;
    	}
    
    	@Override
    	public Object clone() throws CloneNotSupportedException {
    
    		return super.clone();
    	}
    }
    

    De EmployeeWrap klasse heeft geen wijzigbare eigenschappen en maakt gebruik van de implementatie van de kloonmethode van de superklasse. Als er wijzigbare velden zijn, moet je alleen voor die velden zorgen voor een diepe kopie. Hier is een eenvoudig programma om te testen of deze manier van klonen goed werkt of niet.

    package com.journaldev.cloning;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class CloningTest {
    
    	public static void main(String[] args) throws CloneNotSupportedException {
    
    		EmployeeWrap empWrap = new EmployeeWrap();
    
    		empWrap.setId(1);
    		empWrap.setName("Pankaj");
    		empWrap.setTitle("CEO");
    		
    		Map<String, String> props = new HashMap<>();
    		props.put("salary", "10000");
    		props.put("city", "Bangalore");
    		empWrap.setProps(props);
    
    		EmployeeWrap clonedEmpWrap = (EmployeeWrap) empWrap.clone();
    		
    		empWrap.getProps().put("1", "1");
    		
    		System.out.println("empWrap mutable property value = "+empWrap.getProps());
    
    		System.out.println("clonedEmpWrap mutable property value = "+clonedEmpWrap.getProps());
    		
    	}
    
    }
    

    Output:

    empWrap mutable property value = {1=1, city=Bangalore, salary=10000}
    clonedEmpWrap mutable property value = {city=Bangalore, salary=10000}
    

    Het werkte perfect zoals we verwachtten.

Dat is alles over Object cloning in Java. Ik hoop dat je een idee hebt gekregen van de Java Object `clone()` methode en hoe je deze op de juiste manier kunt overschrijven zonder nadelige effecten.

Je kunt het project downloaden van mijn GitHub Repository.

Referentie: API-docs voor Object clone

Source:
https://www.digitalocean.com/community/tutorials/java-clone-object-cloning-java