Copia superficial vs. copia profunda de objetos

JavaJavaBeginner
Practicar Ahora

💡 Este tutorial está traducido por IA desde la versión en inglés. Para ver la versión original, puedes hacer clic aquí

Introducción

Una copia superficial simplemente copiará los valores de los campos del objeto original en el nuevo objeto. En una copia superficial, si el objeto original tiene alguna referencia a otros objetos de clase, entonces solo se clona la referencia de ese objeto. Por lo tanto, cualquier cambio en este objeto referenciado se reflejará tanto en el objeto original como en el objeto clonado. Sin embargo, una copia profunda crea un nuevo objeto, incluyendo todos los objetos referenciados, y cualquier cambio realizado en el objeto clonado no afectará al objeto original.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL java(("Java")) -.-> java/ObjectOrientedandAdvancedConceptsGroup(["Object-Oriented and Advanced Concepts"]) java(("Java")) -.-> java/FileandIOManagementGroup(["File and I/O Management"]) java(("Java")) -.-> java/SystemandDataProcessingGroup(["System and Data Processing"]) java(("Java")) -.-> java/ProgrammingTechniquesGroup(["Programming Techniques"]) java/ProgrammingTechniquesGroup -.-> java/method_overriding("Method Overriding") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/classes_objects("Classes/Objects") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/constructors("Constructors") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/encapsulation("Encapsulation") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/interface("Interface") java/ObjectOrientedandAdvancedConceptsGroup -.-> java/serialization("Serialization") java/FileandIOManagementGroup -.-> java/io("IO") java/SystemandDataProcessingGroup -.-> java/object_methods("Object Methods") subgraph Lab Skills java/method_overriding -.-> lab-117441{{"Copia superficial vs. copia profunda de objetos"}} java/classes_objects -.-> lab-117441{{"Copia superficial vs. copia profunda de objetos"}} java/constructors -.-> lab-117441{{"Copia superficial vs. copia profunda de objetos"}} java/encapsulation -.-> lab-117441{{"Copia superficial vs. copia profunda de objetos"}} java/interface -.-> lab-117441{{"Copia superficial vs. copia profunda de objetos"}} java/serialization -.-> lab-117441{{"Copia superficial vs. copia profunda de objetos"}} java/io -.-> lab-117441{{"Copia superficial vs. copia profunda de objetos"}} java/object_methods -.-> lab-117441{{"Copia superficial vs. copia profunda de objetos"}} end

Creando el Objeto a Copiar

Primero crearemos la clase Student que tiene una variable miembro de tipo GPA (clase definida por el usuario) para almacenar la calificación media del estudiante.

class GPA {
    int firstYear;
    int secondYear;

    public GPA(int firstYear, int secondYear) {
        this.firstYear = firstYear;
        this.secondYear = secondYear;
    }

    public int getFirstYear() {
        return this.firstYear;
    }

    public int getSecondYear() {
        return this.secondYear;
    }

    public void setFirstYear(int firstYear) {
        this.firstYear = firstYear;
    }

    public void setSecondYear(int secondYear) {
        this.secondYear = secondYear;
    }
}

class Student {
    private String name;
    private GPA gpa;

    public Student(String name, GPA gpa) {
        this.name = name;
        this.gpa = gpa;
    }

    public String getName() {
        return this.name;
    }

    public GPA getGpa() {
        return this.gpa;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setGpa(GPA gpa) {
        this.gpa = gpa;
    }
}

Implementando la Interfaz Cloneable y Sobrescribiendo el Método clone() para una Copia Superficial

Para implementar una copia superficial, usaremos el método clone() proporcionado por la clase Object. La interfaz Cloneable debe ser implementada para usar el método clone() ya que es un método protegido. Simplemente llamaremos al método clone() en la clase Student para realizar una clonación superficial.

class Student implements Cloneable {
    //...

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

Implementando una Copia Profunda Usando el Método clone()

Podemos hacer una copia profunda sobrescribiendo el método clone() como se muestra a continuación. En el método clone(), crearemos un nuevo objeto usando la palabra clave new y asignaremos los valores del objeto original al nuevo objeto. Una vez que tenemos un nuevo objeto, crearemos nuevos objetos para todas las clases referenciadas que no contengan tipos primitivos y asignaremos nuevas referencias en lugar de las originales.

class Student implements Cloneable {
    //...

    @Override
    public Object clone() throws CloneNotSupportedException {
        Student newStudent = (Student)super.clone();
        newStudent.gpa = (GPA)this.gpa.clone();
        return newStudent;
    }
}

class GPA implements Cloneable {
    //...

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

Implementando el Constructor de Copia

Otra forma de crear una copia profunda es usar un constructor de copia. Un constructor de copia es un constructor que toma un objeto de la misma clase como entrada y devuelve un nuevo objeto con los mismos valores pero nuevas referencias. Crearemos un constructor de copia para la clase Student.

class Student {
    //...

    public Student(Student student) {
        this(student.getName(), new GPA(student.getGpa().getFirstYear(), student.getGpa().getSecondYear()));
    }
}

Implementando la Serialización

La serialización también proporciona una forma de crear una copia profunda. Al serializar el objeto y luego deserializarlo, esencialmente podemos producir un nuevo objeto con nuevas referencias pero los mismos valores. Para hacer esto, debemos implementar la interfaz Serializable en las clases que requieren una copia profunda.

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

class GPA implements Serializable {
    //...
}

class Student implements Serializable {
    //...

    public Student deepCopyUsingSerialization() {
        try {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(this);

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

            return (Student)ois.readObject();
        } catch(Exception e) {
            return null;
        }
    }
}

Pruebas

Ahora podemos probar nuestros diferentes métodos de copia profunda como se muestra en el siguiente código.

public static void main(String[] args) {
    GPA gpa = new GPA(7, 8);
    Student original = new Student("John", gpa);

    // Copia superficial
    Student shallowCopy = null;
    try {
        shallowCopy = (Student)original.clone();
    } catch(CloneNotSupportedException e) {
        e.printStackTrace();
    }

    // Copia profunda usando clone()
    Student cloneCopy = null;
    try {
        cloneCopy = (Student)original.clone();
    } catch(CloneNotSupportedException e) {
        e.printStackTrace();
    }

    // Copia profunda usando constructor de copia
    Student constructorCopy = new Student(original);

    // Copia profunda usando serialización
    Student serializationCopy = original.deepCopyUsingSerialization();

    // Prueba de copia profunda
    System.out.println("Objeto original: \t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("Copia superficial: \t\t" + shallowCopy.getGpa().getFirstYear() + " " + shallowCopy.getGpa().getSecondYear());
    System.out.println("Copia por clonación: \t\t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
    System.out.println("Copia por constructor de copia: \t" + constructorCopy.getGpa().getFirstYear() + " " + constructorCopy.getGpa().getSecondYear());
    System.out.println("Copia por serialización: \t\t" + serializationCopy.getGpa().getFirstYear() + " " + serializationCopy.getGpa().getSecondYear());

    cloneCopy.setGpa(new GPA(10, 9));
    System.out.println("\nDespués de cambiar el campo GPA de cloneCopy: ");
    System.out.println("Objeto original: \t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("Copia por clonación: \t\t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
}

Ejecución

Después de ejecutar el comando siguiente:

javac Lab.java && java Lab

Verás la siguiente salida:

Objeto original: 		7 8
Copia superficial: 			7 8
Copia por clonación: 			7 8
Copia por constructor de copia: 	7 8
Copia por serialización: 		7 8

Después de cambiar el campo GPA de cloneCopy:
Objeto original: 		7 8
Copia por clonación: 			10 9

Resumen

En este laboratorio, hemos aprendido cómo hacer una copia profunda de un objeto en Java. Una copia superficial simplemente copia los valores de los campos del objeto original al nuevo objeto, mientras que la copia profunda crea un nuevo objeto con nuevas referencias separadas para todos los objetos incluidos. Podemos hacer una copia profunda usando el método clone(), constructores de copia o serialización. Es importante hacer copias profundas para evitar comportamientos inesperados al trabajar con objetos que tienen clases referenciadas.