Java deep copy and shallow copy realize deep copy by serialization

Posted by cmattoon on Sat, 25 Dec 2021 15:44:32 +0100

Java deep copy and shallow copy

deep clone and shallow clone

Shallow copy (shallow copy, shallow clone): all variables of the copied object contain the same value as the original object, while all references to other objects still point to the original object.

In other words, a shallow copy copies only the object under consideration, not the object it references.

Deep copy (deep copy, deep clone): all variables of the copied object contain the same value as the original object, except those variables that reference other objects.

Variables that reference other objects will point to the copied new objects instead of the original referenced objects.

In other words, deep copy copies all the objects referenced by the object to be copied.

Object cloning in Java

  1. In order to obtain a copy of the Object, we can use the clone() method of the Object class.

  2. Override the clone() method of the base class in the derived class and declare it public.

(the clone() method in the Object class is protected.).

When subclasses are overridden, you can expand the scope of access modifiers.

  3. In the clone () method of the derived class, call super.. clone().

Because at runtime, clone() in the Object class identifies which Object you want to copy, then allocates space for this Object, copies the Object, and copies the contents of the original Object one by one to the storage space of the new Object.

  4. Implement the clonable interface in a derived class.

There are no methods in this interface, just describing the function.

Note: inherited from Java The clone() method of lang.Object class is shallow copy

clone() method of Java

The clone() method is defined in the Object class.

The clone() method copies a copy of the object and returns it to the caller. The exact meaning of copy will depend on the class of the object.

Generally speaking, the clone() method satisfies:

1. The cloned object is not the same as the original object. For any object x:

x.clone() != x

2. The cloned object is of the same type as the original object. For any object x:

x.clone().getClass() == x.getClass()

3. If the equals() method of object x is properly defined, the following formula should hold:

x.clone().equals(x)

Because a well-defined equals() method should be used to compare whether the contents are equal

Practice procedure

Program 1: Clone test1 to copy:

CloneTest1

public class CloneTest1
{

    public static void main(String[] args) throws CloneNotSupportedException
    {
        Student student1 = new Student();
        student1.setName("ZhangSan");
        student1.setAge(20);

        Student student2 = new Student();
        student2 = (Student) student1.clone();

        System.out.println("Copy the obtained information");
        System.out.println(student2.getName());
        System.out.println(student2.getAge());
        System.out.println("-------------");

        // Modify the information of the second object
        student2.setName("LiSi");
        student2.setAge(25);

        System.out.println("Modify the properties of the second object to lisi,25 After:");
        System.out.println("First object:");
        System.out.println(student1.getName());
        System.out.println(student1.getAge());
        System.out.println("Second object:");
        System.out.println(student2.getName());
        System.out.println(student2.getAge());
        System.out.println("-------------");
        
        // Note that the two references student1 and student2 point to different objects

    }
}

class Student implements Cloneable
{
    private String name;
    private int age;

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

    @Override
    public Object clone() throws CloneNotSupportedException
    {
        // Note that protected should be changed to public here

        Object object = super.clone();

        return object;
    }
}

The output of the program is:

Copy the obtained information ZhangSan 20 --- After modifying the attribute of the second object to lisi,25: First object: ZhangSan 20 Second object: LiSi 25 ---

Clonetes1 indicates that the copy generates two objects.

Procedure 2: CloneTest2: add a reference to the Teacher class in the Student class and copy it:

CloneTest2

public class CloneTest2
{
    public static void main(String[] args) throws CloneNotSupportedException
    {
        Teacher teacher = new Teacher();
        teacher.setName("Teacher Zhang");
        teacher.setAge(40);

        Student2 student1 = new Student2();
        student1.setName("ZhangSan");
        student1.setAge(20);
        student1.setTeacher(teacher);

        Student2 student2 = (Student2) student1.clone();
        System.out.println("Copy the obtained information");
        System.out.println(student2.getName());
        System.out.println(student2.getAge());
        System.out.println(student2.getTeacher().getName());
        System.out.println(student2.getTeacher().getAge());
        System.out.println("-------------");

        // Modify the teacher's information
        teacher.setName("Teacher Zhang has changed");
        System.out.println(student1.getTeacher().getName());
        System.out.println(student2.getTeacher().getName());

        // The two references student1 and student2 point to two different objects
        // However, the two teacher references in student1 and student2 point to the same object
        // So it's a shallow copy
    }

}

class Teacher implements Cloneable
{
    private String name;
    private int age;

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

}

class Student2 implements Cloneable
{
    private String name;
    private int age;
    private Teacher teacher;

    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 Teacher getTeacher()
    {
        return teacher;
    }

    public void setTeacher(Teacher teacher)
    {
        this.teacher = teacher;
    }

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

}

Program output:

Copy the obtained information ZhangSan 20 Teacher Zhang 40 --- Teacher Zhang has changed Teacher Zhang has changed

Clonetes2 indicates that the clone() method of the Object class performs a shallow copy.

Program 3: change CloneTest2 to deep copy:

First, add the clone() method in the teacher class (it is necessary because it needs to be changed to public, otherwise it cannot be called), then modify the clone() method in the Student2 class to make the teacher reference copy an object, and then set it back with the set method.

Clonetes2 deep copy

public class CloneTest2
{
    public static void main(String[] args) throws Exception
    {
        Teacher teacher = new Teacher();
        teacher.setName("Teacher Zhang");
        teacher.setAge(40);

        Student2 student1 = new Student2();
        student1.setName("ZhangSan");
        student1.setAge(20);
        student1.setTeacher(teacher);

        Student2 student2 = (Student2) student1.clone();
        System.out.println("Copy the obtained information");
        System.out.println(student2.getName());
        System.out.println(student2.getAge());
        System.out.println(student2.getTeacher().getName());
        System.out.println(student2.getTeacher().getAge());
        System.out.println("-------------");

        // Modify the teacher's information
        teacher.setName("Teacher Zhang has changed");
        System.out.println(student1.getTeacher().getName());
        System.out.println(student2.getTeacher().getName());

        // The two references student1 and student2 point to two different objects
        // However, the two teacher references in student1 and student2 point to the same object
        // So it's a shallow copy

        // After changing to deep copy, changes to the teacher object can only affect the first object
    }
}

class Teacher implements Cloneable
{
    private String name;
    private int age;

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

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

}

class Student2 implements Cloneable
{
    private String name;
    private int age;
    private Teacher teacher;

    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 Teacher getTeacher()
    {
        return teacher;
    }

    public void setTeacher(Teacher teacher)
    {
        this.teacher = teacher;
    }

    @Override
    public Object clone() throws CloneNotSupportedException
    {
        // When shallow copying:
        // Object object = super.clone();
        // return object;

        // Change to deep copy:
        Student2 student = (Student2) super.clone();
        // It was originally a shallow copy. Now copy the Teacher object and set it again
        student.setTeacher((Teacher) student.getTeacher().clone());
        return student;
    }

}

Program output:

Copy the obtained information ZhangSan 20 Teacher Zhang 40 --- Teacher Zhang has changed Teacher Zhang

Deep replication using serialization

The method in the above example is troublesome to implement deep replication.

Here is a new method: using serialization for deep replication.

The process of writing objects to the stream is Serialization, while the process of reading objects from the stream is called Deserialization.

It should be noted that what is written in the stream is a copy of the object, and the original object still exists in the JVM.

To deeply copy an object in the Java language, you can often make the object implement the Serializable interface first, then write the object (actually just a copy of the object) to a stream, and then read it from the stream to reconstruct the object.

The premise of this is that the object and all referenced objects within the object are serializable. Otherwise, it is necessary to carefully investigate whether those non serializable objects can be set to transient, so as to exclude them from the replication process.

Note that Cloneable and Serializable interfaces are marker interfaces, that is, they only identify interfaces and do not define any methods.

Program 4: deep copy using serialization example: CloneTest3

Clonetes3 serialized deep copy

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

public class CloneTest3
{
    public static void main(String[] args) throws Exception
    {
        Teacher3 t = new Teacher3();
        t.setName("Teacher Wang");
        t.setAge(50);

        Student3 s1 = new Student3();
        s1.setAge(20);
        s1.setName("ZhangSan");
        s1.setTeacher(t);

        Student3 s2 = (Student3) s1.deepClone();

        System.out.println("Copy the obtained information:");
        System.out.println(s2.getName());
        System.out.println(s2.getAge());
        System.out.println(s2.getTeacher().getName());
        System.out.println(s2.getTeacher().getAge());
        System.out.println("---------------------------");

        // Modify the teacher information of the copied object:
        s2.getTeacher().setName("New Teacher Wang");
        s2.getTeacher().setAge(28);

        System.out.println("After modifying the teacher of the copy object:");
        System.out.println("Copy object's teacher:");
        System.out.println(s2.getTeacher().getName());
        System.out.println(s2.getTeacher().getAge());
        System.out.println("Original target teacher:");
        System.out.println(s1.getTeacher().getName());
        System.out.println(s1.getTeacher().getAge());

        // This proves that the serialization method realizes the deep copy of the object

    }

}

class Teacher3 implements Serializable
{
    private String name;
    private int age;

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

}

class Student3 implements Serializable
{
    private String name;
    private int age;
    private Teacher3 teacher;

    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 Teacher3 getTeacher()
    {
        return teacher;
    }

    public void setTeacher(Teacher3 teacher)
    {
        this.teacher = teacher;
    }

    public Object deepClone() throws Exception
    {
        // serialize
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        ObjectOutputStream oos = new ObjectOutputStream(bos);

        oos.writeObject(this);

        // Deserialization
        ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
        ObjectInputStream ois = new ObjectInputStream(bis);

        return ois.readObject();
    }

}

Program output:

Copied information: ZhangSan 20 Teacher Wang 50 ----— After modifying the teacher of the copy object: Copy object's teacher: New Teacher Wang 28 Original target teacher: Teacher Wang 50

serialVersionUID problem

When a class implements the Serializable interface, it indicates that the class can be serialized. At this time, Eclipse will give a warning and ask you to define a field for the class. The field name is serialVersionUID and the type is long. The prompt information is as follows:

  The serializable class Teacher3 does not declare a static final serialVersionUID field of type long.

There are two ways to generate in Eclipse:

One is the default 1L;

private static final long serialVersionUID = 1L;

One is to generate a 64 bit hash field according to class name, interface name, member method and attribute, such as:

private static final long serialVersionUID = -932183802511122207L;

If you don't consider the compatibility problem, turn it off, but it's good to have this function. As long as any class implements the Serializable interface, if you don't add the serialVersionUID, Eclipse will prompt you that this serialVersionUID is to make the class Serializable backward compatible.

If your object is serialized and stored on the hard disk, but you change the field of the class (increase or decrease or change the name), an exception will occur when you deserialize, which will cause incompatibility.

However, when the serialVersionUID is the same, it will Deserialize different field s with the default value of type, which can avoid the problem of incompatibility.

reference material

Shengsiyuan teacher Zhang Long Java SE series video tutorials. Lectures 105-106.

Java IO serialization and deserialization