Поверхностная копия объекта vs. Глубокая копия объекта

JavaJavaBeginner
Практиковаться сейчас

💡 Этот учебник переведен с английского с помощью ИИ. Чтобы просмотреть оригинал, вы можете перейти на английский оригинал

Введение

Поверхностная копия просто копирует значения полей исходного объекта в новый объект. При поверхностной копии, если исходный объект содержит ссылки на другие объекты класса, то только ссылка на этот объект клонируется. Поэтому любые изменения в этом ссылке на объект будут отражаться как в исходном, так и в клонированном объекте. Однако глубокая копия создает новый объект, включая все ссылки на объекты, и любые изменения, внесенные в клонированный объект, не повлияют на исходный объект.


Skills Graph

%%%%{init: {'theme':'neutral'}}%%%% flowchart RL java(("Java")) -.-> java/ProgrammingTechniquesGroup(["Programming Techniques"]) 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/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{{"Поверхностная копия объекта vs. Глубокая копия объекта"}} java/classes_objects -.-> lab-117441{{"Поверхностная копия объекта vs. Глубокая копия объекта"}} java/constructors -.-> lab-117441{{"Поверхностная копия объекта vs. Глубокая копия объекта"}} java/encapsulation -.-> lab-117441{{"Поверхностная копия объекта vs. Глубокая копия объекта"}} java/interface -.-> lab-117441{{"Поверхностная копия объекта vs. Глубокая копия объекта"}} java/serialization -.-> lab-117441{{"Поверхностная копия объекта vs. Глубокая копия объекта"}} java/io -.-> lab-117441{{"Поверхностная копия объекта vs. Глубокая копия объекта"}} java/object_methods -.-> lab-117441{{"Поверхностная копия объекта vs. Глубокая копия объекта"}} end

Создание объекта для копирования

Сначала создадим класс Student, который имеет переменную члена типа GPA (пользовательский класс), чтобы хранить средний балл студента.

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

Реализация интерфейса Cloneable и переопределение метода clone() для поверхностной копии

Для реализации поверхностной копии мы будем использовать метод clone(), предоставляемый классом Object. Интерфейс Cloneable должен быть реализован для использования метода clone(), так как это защищенный метод. Мы просто вызовем метод clone() в классе Student для выполнения поверхностной клонирования.

class Student implements Cloneable {
    //...

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

Реализация глубокой копии с использованием метода clone()

Мы можем сделать глубокую копию, переопределив метод clone(), как показано ниже. В методе clone() мы создадим новый объект с использованием ключевого слова new и присвоим значения исходного объекта новому объекту. Когда у нас есть новый объект, мы создадим новые объекты для всех ссылочных классов, которые не содержат примитивных типов, и присвоим новые ссылки вместо исходных.

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

Реализация конструктора копирования

Другой способ создания глубокой копии - использовать конструктор копирования. Конструктор копирования - это конструктор, который принимает объект того же класса в качестве входных данных и возвращает новый объект с теми же значениями, но новыми ссылками. Мы создадим конструктор копирования для класса Student.

class Student {
    //...

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

Реализация сериализации

Сериализация также предоставляет способ создания глубокой копии. Сериализируя объект и затем десериализируя его, мы в основном можем получить новый объект с новыми ссылками, но теми же значениями. Для этого мы должны реализовать интерфейс Serializable в классах, которые требуют глубокой копии.

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

Тестирование

Теперь мы можем протестировать наши различные методы глубокой копирования, как показано в следующем коде.

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

    // Поверхностная копия
    Student shallowCopy = null;
    try {
        shallowCopy = (Student)original.clone();
    } catch(CloneNotSupportedException e) {
        e.printStackTrace();
    }

    // Глубокая копия с использованием clone()
    Student cloneCopy = null;
    try {
        cloneCopy = (Student)original.clone();
    } catch(CloneNotSupportedException e) {
        e.printStackTrace();
    }

    // Глубокая копия с использованием конструктора копирования
    Student constructorCopy = new Student(original);

    // Глубокая копия с использованием сериализации
    Student serializationCopy = original.deepCopyUsingSerialization();

    // Тест на глубокую копирование
    System.out.println("Исходный объект: \t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("Поверхностная копия: \t\t" + shallowCopy.getGpa().getFirstYear() + " " + shallowCopy.getGpa().getSecondYear());
    System.out.println("Копия с использованием clone(): \t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
    System.out.println("Копия с использованием конструктора копирования: \t" + constructorCopy.getGpa().getFirstYear() + " " + constructorCopy.getGpa().getSecondYear());
    System.out.println("Копия с использованием сериализации: \t\t" + serializationCopy.getGpa().getFirstYear() + " " + serializationCopy.getGpa().getSecondYear());

    cloneCopy.setGpa(new GPA(10, 9));
    System.out.println("\nПосле изменения поля GPA в cloneCopy: ");
    System.out.println("Исходный объект: \t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("Копия с использованием clone(): \t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
}

Запуск

После выполнения команды ниже:

javac Lab.java && java Lab

Вы увидите следующий вывод:

Исходный объект: 		7 8
Поверхностная копия: 			7 8
Копия с использованием clone(): 			7 8
Копия с использованием конструктора копирования: 	7 8
Копия с использованием сериализации: 		7 8

После изменения поля GPA в cloneCopy:
Исходный объект: 		7 8
Копия с использованием clone(): 			10 9

Резюме

В этом лабе мы узнали, как делать глубокую копию объекта на Java. Поверхностная копия просто копирует значения полей из исходного объекта в новый объект, в то время как глубокая копия создает новый объект с новыми, отдельными ссылками для всех вложенных объектов. Мы можем сделать глубокую копию, используя метод clone(), конструктор копирования или сериализацию. Важно делать глубокие копии, чтобы избежать неожиданного поведения при работе с объектами, имеющими ссылки на классы.