Java's copy (deep copy, shallow copy)

In the copying of objects, many beginners may not be able to figure out whether they are copying references or copying objects. In the copy, it is divided into reference copy, shallow copy, and deep copy.

copy by reference

Reference copy will generate a new object reference address, but the two final points still point to the same object. How to better understand reference copy? It's very simple. Take us as an example, we usually have a name, but different occasions and people may call us differently, but we are very clear which names belong to "me"!

 Let everyone have a look through a code example (for simplicity, methods such as get and set are not written):

class Son {

    String name;
    int age;

    public Son(String name, int age) {

        this.name = name;
        this.age = age;
    }
}

public class test {
    
    public static void main(String[] args) {

        Son s1 = new Son("son1", 12);
        Son s2 = s1;
        s1.age = 22;
        System.out.println(s1);
        System.out.println(s2);
        System.out.println("s1的age:" + s1.age);
        System.out.println("s2的age:" + s2.age);
        System.out.println("s1==s2" + (s1 == s2));//相等
    }
}

Test Results:

Son@135fbaa4
Son@135fbaa4
s1的age:22
s2的age:22
true

shallow copy

How to create an object and copy the content of the target object instead of directly copying the reference?

Let's talk about shallow copy first. Shallow copy will create a new object. The new object has nothing to do with the original object itself. The new object is not equal to the original object, but the properties of the new object are the same as the old object . Specifically, you can see the following differences:

  • If the attribute is a basic type (int, double, long, boolean, etc.), the value of the basic type is copied;

  • If the attribute is a reference type, what is copied is the memory address (that is, the reference is copied but not the referenced object), so if one of the objects changes this address, it will affect the other object.

If you use a picture to describe shallow copy, it should look like this:

How to implement shallow copy? It is also very simple, that is, implement the Cloneable interface on the class that needs to be copied and rewrite its clone () method .

@Override
protected Object clone() throws CloneNotSupportedException {

    return super.clone();
}

 When using it, just call the clone () method of the class directly. The specific cases are as follows:

class Father{

    String name;
    public Father(String name) {

        this.name=name;
    }

    @Override
    public String toString() {

        return "Father{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Son implements Cloneable {
 
    int age;
    String name;
    Father father;

    public Son(String name,int age) {

        this.age=age;
        this.name = name;
    }

    public Son(String name,int age, Father father) {

        this.age=age;
        this.name = name;
        this.father = father;
    }

    @Override
    public String toString() {

        return "Son{" +
                "age=" + age +
                ", name='" + name + '\'' +
                ", father=" + father +
                '}';
    }

    @Override
    protected Son clone() throws CloneNotSupportedException {
        // 字段father指向同一个引用。
        return (Son) super.clone();
    }
}

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

        Father f=new Father("bigFather");
        Son s1 = new Son("son1",13);
        s1.father=f;
        Son s2 = s1.clone();
        
        System.out.println(s1);
        System.out.println(s2);
        System.out.println("s1==s2:"+(s1 == s2));//不相等
        System.out.println("s1.name==s2.name:"+(s1.name == s2.name));//相等
        System.out.println();

        //但是他们的Father father 和String name的引用一样
        s1.age=12;
        s1.father.name="smallFather";//s1.father引用未变
        s1.name="son222";//类似 s1.name=new String("son222") 引用发生变化
        System.out.println("s1.Father==s2.Father:"+(s1.father == s2.father));//相等
        System.out.println("s1.name==s2.name:"+(s1.name == s2.name));//不相等
        System.out.println(s1);
        System.out.println(s2);
    }
}

operation result:

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:true//此时相等

s1.Father==s2.Father:true
s1.name==s2.name:false//修改引用后不等
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='smallFather'}}

Not surprisingly, this kind of shallow copy has the same parts and relationships as well as the copied object except for the object itself, just like twins, two people, but their initial appearance and various relationships (parents and relatives) are the same. It should be noted that the name is initially ==equal, because the initial shallow copy points to the same String, and then s1.name="son222"changes the reference point.

deep copy

For the above problem, although the two copied objects are different, some internal references are still the same. How to absolutely copy this object so that this object is completely independent of the original object? Just use our deep copy. Deep copy: When copying a reference data type, a new object is created and its member variables are copied.

In the specific implementation of deep copy, here are two ways, rewriting the clone () method and the sequence method.

Override the clone() method

If the clone () method is overridden to implement deep copying, then all classes of custom reference variables in the class must also implement the Cloneable interface to implement the clone () method. For character classes, a new string can be created for copying.

For the above code, the Father class implements the Cloneable interface and overrides the clone () method. The clone () method of son needs to copy all references .

//Father clone()方法
@Override
protected Father clone() throws CloneNotSupportedException {
   
    return (Father) super.clone();
}

//Son clone()方法
@Override
protected Son clone() throws CloneNotSupportedException {
   
    Son son= (Son) super.clone();//待返回克隆的对象
    son.name=new String(name);
    son.father=father.clone();
    return son;
}

 Other codes remain unchanged, and the execution results are as follows:

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:false

s1.Father==s2.Father:false
s1.name==s2.name:false
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}

Serialization

It can be found that this method implements deep copy. But there is a problem in this situation, what if there are too many references or layers?

It is impossible to write clone () for each object one by one, right? then what should we do? With serialization.

Because after serialization: write the binary byte stream content to a medium (text or byte array), and then read data from this medium, the original object is written to this medium and then copied to the clone object, the modification of the original object will not affect the clone object, because the clone object is read from this medium.

Those who are familiar with object caching know that we often cache Java objects in Redis, and then read and generate Java objects from Redis, which uses serialization and deserialization. Generally, Java objects can be stored as byte streams or json strings and then deserialized into Java objects. Because serialization will store the properties of the object but will not and cannot store information about the address of the object in memory . So all reference objects will be recreated when deserialized into Java objects.

In terms of specific implementation, custom classes need to implement the Serializable interface . Define a function in the class (Son) that needs deep copy to return the object of this class:

protected Son deepClone() throws IOException, ClassNotFoundException {
   
      Son son=null;
      //在内存中创建一个字节数组缓冲区,所有发送到输出流的数据保存在该字节数组中
      //默认创建一个大小为32的缓冲区
      ByteArrayOutputStream byOut=new ByteArrayOutputStream();
      //对象的序列化输出
      ObjectOutputStream outputStream=new ObjectOutputStream(byOut);//通过字节数组的方式进行传输
      outputStream.writeObject(this);  //将当前student对象写入字节数组中

      //在内存中创建一个字节数组缓冲区,从输入流读取的数据保存在该字节数组缓冲区
      ByteArrayInputStream byIn=new ByteArrayInputStream(byOut.toByteArray()); //接收字节数组作为参数进行创建
      ObjectInputStream inputStream=new ObjectInputStream(byIn);
      son=(Son) inputStream.readObject(); //从字节数组中读取
      return  son;
}

When using it, just call the method we wrote, and the other remains unchanged. The effect achieved is:

Son{age=13, name='son1', father=Father{name='bigFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}
s1==s2:false
s1.name==s2.name:false

s1.Father==s2.Father:false
s1.name==s2.name:false
Son{age=12, name='son222', father=Father{name='smallFather'}}
Son{age=13, name='son1', father=Father{name='bigFather'}}

Note: 2023/07/01 is an example of overriding the clone method.

package learnjavafx8;

import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;

/**
 * @copyright 2023-2022
 * @package   learnjavafx8
 * @file      Person.java
 * @date      2023-07-01 14:34
 * @author    qiao wei
 * @version   1.0
 * @brief     
 * @history
 */
public class Person implements Cloneable {

	public static void main(String[] args) {
		Person p01 = new Person(10, 20, true);
		System.out.println(p01);
		
		Person p02 = (Person) p01.clone();
		p02.firstIntegerProperty.set(-233);
		p02.secondIntegerProperty.set(666);
		p02.flag = false;

		System.out.println(p01);
		System.out.println(p02);
	}
	
	public Person() {
		firstIntegerProperty = new SimpleIntegerProperty();
		secondIntegerProperty = new SimpleIntegerProperty();
		
		flag = false;
	}
	
	public Person(int first, int second, boolean flag) {
		firstIntegerProperty = new SimpleIntegerProperty(first);
		secondIntegerProperty = new SimpleIntegerProperty(second);
		
		this.flag = flag;
	}

	@Override
	protected Object clone() {
		Person person = null;
		
		try {
			person = (Person) super.clone();
			
			person.firstIntegerProperty =
				new SimpleIntegerProperty(this.firstIntegerProperty.get());
			person.secondIntegerProperty =
				new SimpleIntegerProperty(this.secondIntegerProperty.get());
			person.flag = this.flag;
		} catch (CloneNotSupportedException exception) {
			exception.printStackTrace();
		} finally {
			return person;
		}
	}

	@Override
	public String toString() {
		return "Person{" +
			"firstIntegerProperty=" + firstIntegerProperty +
			", secondIntegerProperty=" + secondIntegerProperty +
			", flag=" + flag +
			'}';
	}

	public IntegerProperty firstIntegerProperty = null;
	
	public IntegerProperty secondIntegerProperty = null;
	
	public boolean flag = false;
}

As a result of the test, the modification of the p02 reference field does not affect the p01 reference field. A deep copy was made.

Person{firstIntegerProperty=IntegerProperty [value: 10], secondIntegerProperty=IntegerProperty [value: 20], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: 10], secondIntegerProperty=IntegerProperty [value: 20], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: -233], secondIntegerProperty=IntegerProperty [value: 666], flag=false}

Process finished with exit code 0

Modify the clone method in the Person class, change the deep copy to shallow copy (delete the reference type data copy content), and the rest of the code remains unchanged:

	@Override
	protected Object clone() {
		Person person = null;
		
		try {
			person = (Person) super.clone();
			
//			person.firstIntegerProperty =
//				new SimpleIntegerProperty(this.firstIntegerProperty.get());
//			person.secondIntegerProperty =
//				new SimpleIntegerProperty(this.secondIntegerProperty.get());
//			person.flag = this.flag;
		} catch (CloneNotSupportedException exception) {
			exception.printStackTrace();
		} finally {
			return person;
		}
	}

operation result:

Person{firstIntegerProperty=IntegerProperty [value: 10], secondIntegerProperty=IntegerProperty [value: 20], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: -233], secondIntegerProperty=IntegerProperty [value: 666], flag=true}
Person{firstIntegerProperty=IntegerProperty [value: -233], secondIntegerProperty=IntegerProperty [value: 666], flag=false}

Process finished with exit code 0

The modification of the reference field of p02 affects the content of the reference field of p01.

Guess you like

Origin blog.csdn.net/weiweiqiao/article/details/131490567