Copia de Java (copia profunda, copia superficial)

En la copia de objetos, es posible que muchos principiantes no puedan darse cuenta si están copiando referencias o copiando objetos. En la copia, se divide en copia de referencia, copia superficial y copia profunda.

copia por referencia

La copia de referencia generará una nueva dirección de referencia de objeto, pero los dos puntos finales seguirán apuntando al mismo objeto. ¿Cómo entender mejor la copia de referencia? Es muy simple, tómenos como ejemplo, generalmente tenemos un nombre, pero en diferentes ocasiones y personas pueden llamarnos de manera diferente, ¡pero tenemos muy claro qué nombres pertenecen a "yo"!

 Deje que todos prueben a través de un ejemplo de código (por simplicidad, los métodos como get y set no están escritos):

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));//相等
    }
}

Resultados de la prueba:

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

copia superficial

¿Cómo crear un objeto y copiar el contenido del objeto de destino en lugar de copiar directamente la referencia?

Hablemos primero de la copia superficial . La copia superficial creará un nuevo objeto. El nuevo objeto no tiene nada que ver con el objeto original en sí. El nuevo objeto no es igual al objeto original, pero las propiedades del nuevo objeto son las mismas que las del objeto anterior . En concreto, se pueden ver las siguientes diferencias:

  • Si el atributo es de tipo básico (int, double, long, boolean, etc.), se copia el valor del tipo básico;

  • Si el atributo es de tipo referencia, lo que se copia es la dirección de memoria (es decir, se copia la referencia pero no el objeto referenciado), por lo que si uno de los objetos cambia esta dirección, afectará al otro objeto.

Si usa una imagen para describir una copia superficial, debería verse así:

¿Cómo implementar una copia superficial? También es muy simple, es decir, implemente la interfaz Cloneable en la clase que necesita ser copiada y reescriba su método clone () .

@Override
protected Object clone() throws CloneNotSupportedException {

    return super.clone();
}

 Cuando lo use, simplemente llame directamente al método clon () de la clase. Los casos específicos son los siguientes:

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

resultado de la operación:

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

No es sorprendente que este tipo de copia superficial tenga las mismas partes y relaciones, así como el objeto copiado, excepto por el objeto en sí, como gemelos, dos personas, pero su apariencia inicial y varias relaciones (padres y parientes) son las mismas. Cabe señalar que el nombre es inicialmente ==igual, porque la copia superficial inicial apunta a la misma Cadena y luego s1.name="son222"cambia el punto de referencia.

copia profunda

Para el problema anterior, aunque los dos objetos copiados son diferentes, algunas referencias internas siguen siendo las mismas.¿Cómo copiar absolutamente este objeto para que este objeto sea completamente independiente del objeto original? Solo usa nuestra copia profunda. Copia profunda: al copiar un tipo de datos de referencia, se crea un nuevo objeto y se copian sus variables miembro.

En la implementación específica de la copia profunda, aquí hay dos formas, reescribiendo el método de clonación () y el método de secuencia.

Anula el método clon()

Si el método clone () se anula para implementar la copia profunda, entonces todas las clases de variables de referencia personalizadas en la clase también deben implementar la interfaz Cloneable para implementar el método clone (). Para las clases de caracteres, se puede crear una nueva cadena para copiar.

Para el código anterior, la clase Padre implementa la interfaz Cloneable y anula el método clone (). El método clon () de son necesita copiar todas las referencias .

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

 Otros códigos permanecen sin cambios, y los resultados de ejecución son los siguientes:

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

Publicación por entregas

Se puede encontrar que este método implementa una copia profunda. Pero hay un problema en esta situación, ¿y si hay demasiadas referencias o capas?

Es imposible escribir clon () para cada objeto uno por uno, ¿verdad? entonces que debemos hacer? Con serialización.

Porque después de la serialización: escriba el contenido del flujo de bytes binarios en un medio (texto o matriz de bytes) y luego lea los datos de este medio, el objeto original se escribe en este medio y luego se copia en el objeto clonado, la modificación del objeto original no afectará al objeto clonado, porque el objeto clonado se lee desde este medio.

Aquellos que están familiarizados con el almacenamiento en caché de objetos saben que a menudo almacenamos en caché objetos Java en Redis y luego leemos y generamos objetos Java desde Redis, que utiliza serialización y deserialización. En general, los objetos de Java se pueden almacenar como flujos de bytes o cadenas json y luego deserializarse en objetos de Java. Porque la serialización almacenará las propiedades del objeto, pero no almacenará ni puede almacenar información sobre la dirección del objeto en la memoria . Por lo tanto, todos los objetos de referencia se volverán a crear cuando se deserialicen en objetos Java.

En términos de implementación específica, las clases personalizadas deben implementar la interfaz Serializable . Defina una función en la clase (Son) que necesita una copia profunda para devolver el objeto de esta clase:

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

Al usarlo, simplemente llame al método que escribimos, y el otro permanece sin cambios.El efecto logrado es:

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

Nota: 2023/07/01 es un ejemplo de anulación del método de clonación.

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

Como resultado de la prueba, la modificación del campo de referencia p02 no afecta al campo de referencia p01. Se hizo una copia profunda.

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

Modifique el método de clonación en la clase Person, cambie la copia profunda a una copia superficial (elimine el contenido de la copia de datos de tipo de referencia) y el resto del código permanece sin cambios:

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

resultado de la operación:

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

La modificación del campo de referencia de p02 afecta al contenido del campo de referencia de p01.

Supongo que te gusta

Origin blog.csdn.net/weiweiqiao/article/details/131490567
Recomendado
Clasificación