1. Why Clone
In java, it's easy to copy a variable
int a = 5;
int b = a;
Not only the int type, but the other seven primitive data types (boolean,char,byte,short,float,double.long) are also applicable to this type of situation.
But if we need to copy an object, it is incorrect to use the "=" symbol of a variable to copy, except for "reference passing" when a function passes value, and "reference passing" whenever "=" is assigned to an object variable.
class Student {
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = stu1;
System.out.println("Student 1:" + stu1.getNumber());
System.out.println("Student 2:" + stu2.getNumber());
}
}
Result:
Student 1:12345 Student 2:12345
Here we customize a student class, which has only one number field.
We create a new student instance and assign the value to the stu2 instance. (Student stu2 = stu1;)
Let's try to change the number field of the stu2 instance and print the result.
stu2.setNumber(54321);
System.out.println("Student 1:" + stu1.getNumber());
System.out.println("Student 2:" + stu2.getNumber());
Result:
Student 1:54321 Student 2:54321
The reason lies in the sentence (stu2 = stu1). The purpose of this statement is to assign a reference to stu1 to stu2.
In this way, stu1 and stu2 point to the same object in the memory heap. As shown in the picture:
Cloned objects may contain some attributes that have been modified, and the attributes of new objects are still initialized values, so when a new object is needed to save the "state" of the current object, it depends on the clone method.
So we need to be able to make a copy of the input parameters. If the method parameters are of basic type, a new space will be created in the stack memory. All the operations in the method body are directed at this copy, and will not affect the original input parameters. If the method parameter is a reference type, the copy and the input parameter point to the same object, and the operation of the object inside the method body is aimed at the same object.
2. How to Realize Cloning
Two different cloning methods are Shallow Clone and Deep Clone.
In Java language, data types are divided into value types (basic data types) and reference types. Value types include simple data types such as int, double, byte, boolean, char and so on. Reference types include complex types such as classes, interfaces, arrays and so on. The main difference between shallow cloning and deep cloning is whether the duplication of member variables of reference type is supported.
There are two ways:
1. Implement Cloneable interface and override clone() method in Object class;
2) Realizing Serializable interface, realizing cloning through object serialization and deserialization, realizing real deep cloning;
2.1. clone() method
/*
Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.
The general intent is that, for any object x, the expression:
1) x.clone() != x will be true
2) x.clone().getClass() == x.getClass() will be true, but these are not absolute requirements.
3) x.clone().equals(x) will be true, this is not an absolute requirement.
*/
protected native Object clone() throws CloneNotSupportedException;
Look carefully, it's also a native method. As we all know, the native method is code implemented in non-Java language, which is called by Java program. Because Java program runs on JVM virtual machine, there's no way to access the lower level of operating system-related, only by the language close to the operating system.
The first declaration guarantees that the cloned object will have a separate memory address allocation.
The second statement states that the original and cloned objects should have the same class type, but it is not mandatory.
The third statement states that the original and cloned objects should be used equals() method equally, but it is not mandatory.
Because each class's direct or indirect parent class is Object, they all contain clone() methods, but because this method is protected, it cannot be accessed outside the class.
To copy an object, you need to override the clone method.
Steps to implement clone method
(1) Implementing Cloneable Interface
(2) The clone() method in the overloaded Object class should be defined as public when overloaded.
(3) Call super.clone() in overloaded methods
class Student implements Cloneable{
private int number;
public int getNumber() {
return number;
}
public void setNumber(int number) {
this.number = number;
}
@Override
public Object clone() {
Student stu = null;
try{
stu = (Student)super.clone();
}catch(CloneNotSupportedException e) {
e.printStackTrace();
}
return stu;
}
}
public class Test {
public static void main(String args[]) {
Student stu1 = new Student();
stu1.setNumber(12345);
Student stu2 = (Student)stu1.clone();
System.out.println("Student 1:" + stu1.getNumber());
System.out.println("Student 2:" + stu2.getNumber());
stu2.setNumber(54321);
System.out.println("Student 1:" + stu1.getNumber());
System.out.println("Student 2:" + stu2.getNumber());
}
}
Result:
Student 1:12345 Student 2:12345 Student 1:12345 Student 2:54321
Interpretation:
(1) clone() method is defined in the java.lang.Object class. This method is a protected method, so the property of clone() method should be set to public when overloading, so that other classes can call the clone() method of this clone class.
(2) Implement Cloneable interface: Cloneable interface does not contain any method! In fact, this interface is only a flag, and this flag is only for clone() method in Object class. If clone class does not implement Cloneable interface and calls clone() method of Object (that is, super.Clone() method), clone() method of Object will throw CloneNotSupportedException exception.
2.2. Cloning by serialization and deserialization
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class MyUtil {
private MyUtil() {
throw new AssertionError();
}
public static <T> T clone(T obj) throws Exception {
ByteArrayOutputStream bout = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(bout);
oos.writeObject(obj);
ByteArrayInputStream bin = new ByteArrayInputStream(bout.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bin);
return (T) ois.readObject();
// Description: Calling the close method of ByteArrayInputStream or ByteArrayOutputStream objects is meaningless
// These two memory-based streams can release resources as long as the garbage collector cleans up objects, which is different from the release of external resources, such as file streams.
}
}
Test code:
import java.io.Serializable;
/**
* Human beings
*/
class Person implements Serializable {
private static final long serialVersionUID = -9102017020286042305L;
private String name; // Full name
private int age; // Age
private Car car; // Driver's seat
public Person(String name, int age, Car car) {
this.name = name;
this.age = age;
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Car getCar() {
return car;
}
public void setCar(Car car) {
this.car = car;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", car=" + car + "]";
}
}
/**
* Car category
*/
class Car implements Serializable {
private static final long serialVersionUID = -5713945027627603702L;
private String brand; // brand
private int maxSpeed; // Maximum speed per hour
public Car(String brand, int maxSpeed) {
this.brand = brand;
this.maxSpeed = maxSpeed;
}
public String getBrand() {
return brand;
}
public void setBrand(String brand) {
this.brand = brand;
}
public int getMaxSpeed() {
return maxSpeed;
}
public void setMaxSpeed(int maxSpeed) {
this.maxSpeed = maxSpeed;
}
@Override
public String toString() {
return "Car [brand=" + brand + ", maxSpeed=" + maxSpeed + "]";
}
}
class CloneTest {
public static void main(String[] args) {
try {
Person p1 = new Person("Hao LUO", 33, new Car("Benz", 300));
Person p2 = MyUtil.clone(p1); // Deep cloning
p2.getCar().setBrand("BYD");
// Modifying the Brand Attribute of Cloned Person Object p2 Associated with Vehicle Object
// The car associated with the original Person object p1 will not be affected at all.
// Because when the Person object is cloned, its associated car object is also cloned.
System.out.println(p1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Note: Cloning based on serialization and deserialization is not only deep cloning, but also generic restriction, which can check whether the object to be cloned supports serialization. This check is done by the compiler, not by throwing an exception at run time. This is a clone method which is obviously better than clone method using Object class. It is always better to expose problems at compile time than to leave them at runtime.
3. Shallow cloning and deep cloning
3.1. Shallow cloning
In shallow cloning, if the member variables of the prototype object are of value type, a copy will be made to the cloned object; if the member variables of the prototype object are of reference type, the address of the reference object will be copied to the cloned object, that is to say, the member variables of the prototype object and the cloned object point to the same memory address.
In Java language, shallow cloning can be achieved by overriding clone() method of Object class.
3.2. Deep cloning
In deep cloning, whether the member variables of the prototype object are value type or reference type, a copy will be copied to the cloned object. In deep cloning, all reference objects of the prototype object will also be copied to the cloned object.
Simply put, in deep cloning, in addition to the object itself being copied, all member variables contained in the object will also be copied.
In the Java language, if deep cloning is needed, it can be achieved by clone() method which overrides the Object class, or by serialization, etc.
If there are many reference types in the reference type, or if there are reference types in the inner reference type class, the use of clone method will be troublesome. At this time, we can use serialization to achieve deep cloning of objects.
Serialization is the process of writing an object to a stream. The object written to the stream is a copy of the original object, which still exists in memory. The copy realized by serialization can not only copy the object itself, but also copy the member objects it refers to. Therefore, deep cloning can be achieved by serialization by writing the object into a stream and reading it out of the stream. It is important to note that the class of the object that can be serialized must implement the Serializable interface, otherwise the serialization operation cannot be realized.
Reference article:
Java Enhancement - Object Cloning (Replication)
Java clone() cloning objects