clone() method in Java

Posted by swamp on Wed, 12 Jan 2022 05:17:46 +0100

Convention for clone() method

The clonable interface is intended to be a mixed interface of objects, indicating that such objects allow cloning, but this interface does not define clone (). This is its defect: it cannot constrain subclasses to implement the clone () method. Object defines a protected clone () method. Cloneable does not define the clone () method, but it affects object Behavior of clone () method: if a class implements Cloneable, calling clone () of object will return a domain by domain copy of the object, otherwise clonnotsupportedexception will be thrown. This is really an unconventional usage. The clonable interface does not specify the view of the implementation class, but changes the behavior of the protected methods of the parent class. Calling clone() will create and return a copy of the object. See the Convention on clone() method in JDK document:

(1)x.clone() != x; The cloned object is not the same object as the original object

(2)x.clone().getClass() == x.getClass(); Clones are objects of the same type

(3)x.clone().equals(x) == true, if the x.equals() method is properly defined

Note that the requirements of the above three rules are not absolute. Generally speaking, the first two are necessary, and the third should be observed as much as possible.

The classes that implement the clonable interface and all its superclasses must abide by a complex, unenforceable and undocumented protocol, so as to obtain a mechanism outside the language: objects can be created without calling the constructor. However, the "no call constructor" rule is somewhat rigid. A well behaved clone() method can call the constructor to create an object, such as the final class, which does not have a subclass, so it is a reasonable choice to call the constructor to create objects in its clone() method.

Rules using clone()

If you override the clone method in non final class, you should return an object obtained by calling super.clone(), which is the rule of using clone() method. If you do not follow this rule, you call the constructor in clone() method, then you get the wrong class. As shown in the code:

class A implements Cloneable
{   //clone() of class A calls the constructor directly
	public A clone() {
		return new A();
	}
}
 
class B extends A
{
	public B clone() {
		try
		{
			return (B) super.clone();
		}
		catch (CloneNotSupportedException e)
		{
			throw new AssertionError();
		}
	}
}

The clone() method of class B will not get the correct object because super clone() returns an object of class a created using a's constructor. If the clone method of class B wants to get the correct object, the clone method of class A should be written as follows:

public A clone() {
	try{
		return (A) super.clone();
	}catch (CloneNotSupportedException e){
		throw new AssertionError();
	}
}

From this, we can see that calling super Clone () will eventually call the clone method of the Object class, provided that all superclasses of the subclass follow the above rules, otherwise it cannot be implemented. Note that the return value of the clone methods of A and B does not have to be Object, Java 1 The covariant return type is introduced as A generic type, and the return value of the overridden method can be A subclass of the return value of the overridden method.

Clonable is really a failed interface. It does not specify the responsibilities of the class implementing it. Generally, the class implementing clonable should provide a public clone() method with appropriate functions.

Shallow cloning

All variables of the cloned Object contain the same value as the original Object, and references to other objects point to the original Object. That is, shallow cloning only clones the Object under consideration. The clone of Object is "share copy". If each field of the class is a value of a basic type or a reference to an immutable Object, call Object Clone () will get the correct Object.

/**
 *If each field is a basic type or a reference to an immutable object
 *Then this class only needs to declare and implement the clonable interface and provide the public clone() method
 */
class ShallowCopy implements Cloneable
{
	private String name;
	private int no;
 
	public ShallowCopy(String name,int no) {
		this.name = name;
		this.no = no;
	}
 
	/*Just call super Clone () will get the correct behavior*/
	public ShallowCopy clone() {
		try
		{
			return (ShallowCopy)super.clone();
		}
		catch (CloneNotSupportedException e)
		{
			throw new AssertionError();
		}
	}
}

Generally, we have obtained the correct object, but if the class contains fields representing serial number or unique ID or creation time, these fields need to be modified.

Deep cloning

Deep cloning also clones the object pointed to by the reference field. Consider the following class:

class Person
{
	private Dog friend;
	public Person(Dog dog) {
		friend = dog;
	}
}
class Dog
{
	private String name;
	public Dog(String name) {
		this.name = name;
	}
}

The friend field of the Person class is not a basic type, but points to a variable object. At this time, if you call object Clone(), then the friend of the cloned object points to the original dog, that is:

Person p = new Person(new Dog("golden hair");

p.clone().friend== p.friend;//true

p.clone().friend.name = "wolf dog";

p.friend.name.equals("wolf dog")// true, the cloned object is changed, but the original object is changed at the same time

In fact, the clone method is another constructor: you have to make sure you don't hurt the original object. In order to make the clone method of Person work correctly, it is also necessary to clone friend. The simplest way is to call friend clone():

public Person clone() {
		try{
			Person result = (Person) super.clone();
			result.friend = friend.clone();
		}
		catch (CloneNotSupportedException e){
			throw new AssertionError();
		}
}

However, if the friend domain is final, the clone () above will not work because super You have already assigned a value to friend when clone(). You can't modify the friend field of the cloned object. This is a fundamental problem: The Clone schema is incompatible with the normal usage of the final domain that references variable objects!

Leaving aside the problem of the final field, can we solve the problem by calling the clone() method recursively? The problem is that it is not easy to determine the level of deep cloning. Consider the following classes:

import java.util.Arrays;
/**
 *The one-way linked list is implemented internally
 *buckets A one-way linked list is saved for each element in the
 *
 */
class NMap implements Cloneable
{
	private Entry[] buckets;
	
	public NMap(int size) {
		buckets = new Entry[size];
		for(int i = 0; i < size; i++)
			buckets[i] = new Entry("M10","Messi",new Entry("X6", "Xavi", null));
	}
 
	public Entry[] getBuckets() {
		return buckets;
	}
 
   static class Entry
	{
		final Object key;
		Object value;
		Entry next;
 
		Entry(Object key,Object value,Entry next) {
			this.key = key;
			this.value = value;
			this.next = next;
		}
 
		public void setNext(Entry next) {
			this.next = next;
		}
		
		public String toString() {
			String result =  key + ":" + value + "  ";
			if(next != null)
				result += next.toString();
			return result;
		}
	}
 
	public NMap clone(){
		try
		{
			NMap result = (NMap)super.clone();
			//Arrays are considered to implement the clonable interface
			result.buckets = buckets.clone();
			return result;
		}catch (CloneNotSupportedException e){
			throw new AssertionError();
		}
	}
 
	public static void main(String[] args) {
		NMap map = new NMap(5);
	    
		System.out.println(Arrays.toString(map.getBuckets()));
		
		NMap clone = map.clone();
		Entry entry = new Entry("G4","Guadiorla",new Entry("R9","Ronaldo",null));
		for(Entry ent : clone.getBuckets())
			ent.setNext(entry);
		
		System.out.println(Arrays.toString(map.getBuckets()));
	}
}

After running this code, you will find that although the cloned object has its own array buckets, the linked list referenced in the array is the same as the original object. If you modify the linked list in the cloned object array, the objects saved in the array in the original object will be modified accordingly. The solution to this problem is to add a "deep copy" method in the Entry class.

public Entry deepEntry() {
	Entry result = new Entry(key,value,next);
	for(Entry p = result; p.next != null; p = p.next)
		p.next = new Entry(p.next.key,p.next.value,p.next.next);
	return result;
}
NMap of clone()The method is as follows:
public NMap clone(){
	try
	{
		NMap result = (NMap)super.clone();
		//Arrays are considered to implement the clonable interface
		result.buckets = buckets.clone();
		for(int i = 0; i < buckets.length; i++)
			if(buckets[i] != null)
				result.buckets[i] = buckets[i].deepEntry();
		return result;
	}catch (CloneNotSupportedException e){
		throw new AssertionError();
	}
}

Another way to clone complex objects is to call super Clone () gets the right type of object, then sets all fields in a blank state, and then calls the high-level method to regenerate the state of the object. This approach will produce a simple, reasonable and beautiful clone method, which runs slightly slower.

summary

1. Clonable interface is a failed interface. It does not provide clone() method, but affects object clone() cloning behavior: if the class does not implement the clonable interface, call super The clone() method will get CloneNotSupportedException.

2. All classes that implement the clonable interface should provide a public method to override clone (). This public method first calls super clone(), and then modify the domain. Generally, this public method should not declare to throw CloneNotSupportedException.

3. If the class designed for inheritance should not implement the clonable interface, it can make the subclasses have the freedom to implement or not implement the clonable interface, as if they directly extend the Object. The parent class does not implement the Cloneable interface, nor does it cover clone(). If the subclass implements Cloneable, it calls super. in the overlay clone(). clone() can get the correct Object.

It is said that many expert programmers never use the clone() method.

Better way

Wait, is it necessary to be so complex in order to implement the function of clone() method? This is rarely necessary. A better way to copy objects is to provide a copy constructor or copy factory that accepts an object of this type as a parameter.

public Yum( Yum yum);// copy constructor

public static YumcopyInstance(Yum yum);// copy factory

Topics: Java Back-end