Flache vs. tiefe Objektkopie

JavaJavaBeginner
Jetzt üben

💡 Dieser Artikel wurde von AI-Assistenten übersetzt. Um die englische Version anzuzeigen, können Sie hier klicken

Einführung

Eine flache Kopie kopiert einfach die Werte der Felder des ursprünglichen Objekts in das neue Objekt. Bei einer flachen Kopie wird, wenn das ursprüngliche Objekt Referenzen auf andere Klassenobjekte hat, nur die Referenz auf jenes Objekt kloniert. Änderungen an diesem referenzierten Objekt werden daher sowohl im ursprünglichen als auch im klonierten Objekt widergespiegelt. Eine tiefe Kopie erstellt dagegen ein neues Objekt, einschließlich aller referenzierten Objekte, und Änderungen am klonierten Objekt haben keine Auswirkungen auf das ursprüngliche Objekt.


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{{"Flache vs. tiefe Objektkopie"}} java/classes_objects -.-> lab-117441{{"Flache vs. tiefe Objektkopie"}} java/constructors -.-> lab-117441{{"Flache vs. tiefe Objektkopie"}} java/encapsulation -.-> lab-117441{{"Flache vs. tiefe Objektkopie"}} java/interface -.-> lab-117441{{"Flache vs. tiefe Objektkopie"}} java/serialization -.-> lab-117441{{"Flache vs. tiefe Objektkopie"}} java/io -.-> lab-117441{{"Flache vs. tiefe Objektkopie"}} java/object_methods -.-> lab-117441{{"Flache vs. tiefe Objektkopie"}} end

Erstellen des zu kopierenden Objekts

Wir werden zunächst die Student-Klasse erstellen, die eine Member-Variable vom Typ GPA (benutzerdefinierte Klasse) hat, um die GPA der Studentin/des Studenten zu speichern.

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

Implementierung des Cloneable-Interfaces und Überschreiben der clone()-Methode für eine flache Kopie

Um eine flache Kopie zu implementieren, werden wir die clone()-Methode verwenden, die von der Object-Klasse bereitgestellt wird. Das Cloneable-Interface muss implementiert werden, um die clone()-Methode zu verwenden, da es sich um eine geschützte Methode handelt. Wir werden einfach die clone()-Methode in der Student-Klasse aufrufen, um eine flache Klonierung durchzuführen.

class Student implements Cloneable {
    //...

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

Implementierung einer tiefen Kopie mit der clone()-Methode

Wir können eine tiefe Kopie erstellen, indem wir die clone()-Methode wie unten gezeigt überschreiben. In der clone()-Methode werden wir ein neues Objekt mit dem new-Schlüsselwort erstellen und die Werte des ursprünglichen Objekts auf das neue Objekt zuweisen. Nachdem wir ein neues Objekt haben, werden wir neue Objekte für alle referenzierten Klassen erstellen, die keine primitiven Typen enthalten, und neue Referenzen zuweisen, anstatt die ursprünglichen.

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

Implementierung des Kopierkonstruktors

Ein anderer Weg, um eine tiefe Kopie zu erstellen, ist die Verwendung eines Kopierkonstruktors. Ein Kopierkonstruktor ist ein Konstruktor, der ein Objekt derselben Klasse als Eingabe nimmt und ein neues Objekt mit denselben Werten, aber neuen Referenzen zurückgibt. Wir werden einen Kopierkonstruktor für die Student-Klasse erstellen.

class Student {
    //...

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

Implementierung der Serialisierung

Die Serialisierung bietet ebenfalls einen Weg, um eine tiefe Kopie zu erstellen. Indem wir das Objekt serialisieren und anschließend deserialisieren, können wir im Grunde genommen ein neues Objekt mit neuen Referenzen, aber denselben Werten erzeugen. Dazu müssen wir das Serializable-Interface in den Klassen implementieren, für die eine tiefe Kopie erforderlich ist.

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

Testen

Jetzt können wir unsere verschiedenen Methoden der tiefen Kopie wie im folgenden Code gezeigt testen.

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

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

    // Tiefkopie mit clone()
    Student cloneCopy = null;
    try {
        cloneCopy = (Student)original.clone();
    } catch(CloneNotSupportedException e) {
        e.printStackTrace();
    }

    // Tiefkopie mit Kopierkonstruktor
    Student constructorCopy = new Student(original);

    // Tiefkopie mit Serialisierung
    Student serializationCopy = original.deepCopyUsingSerialization();

    // Test auf Tiefkopie
    System.out.println("Ursprüngliches Objekt: \t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("Flache Kopie: \t\t\t" + shallowCopy.getGpa().getFirstYear() + " " + shallowCopy.getGpa().getSecondYear());
    System.out.println("Klonkopie: \t\t\t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
    System.out.println("Kopierkonstruktor-Kopie: \t" + constructorCopy.getGpa().getFirstYear() + " " + constructorCopy.getGpa().getSecondYear());
    System.out.println("Serialisierungskopie: \t\t" + serializationCopy.getGpa().getFirstYear() + " " + serializationCopy.getGpa().getSecondYear());

    cloneCopy.setGpa(new GPA(10, 9));
    System.out.println("\nNachdem das GPA-Feld von cloneCopy geändert wurde: ");
    System.out.println("Ursprüngliches Objekt: \t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("Klonkopie: \t\t\t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
}

Ausführen

Nach Ausführung des folgenden Befehls:

javac Lab.java && java Lab

Wird die folgende Ausgabe angezeigt:

Ursprüngliches Objekt: 		7 8
Flache Kopie: 			7 8
Klonkopie: 			7 8
Kopierkonstruktor-Kopie: 	7 8
Serialisierungskopie: 		7 8

Nachdem das GPA-Feld von cloneCopy geändert wurde:
Ursprüngliches Objekt: 		7 8
Klonkopie: 			10 9

Zusammenfassung

In diesem Lab haben wir gelernt, wie man in Java eine tiefe Kopie eines Objekts erstellt. Eine flache Kopie kopiert einfach die Feldwerte vom ursprünglichen Objekt in das neue Objekt, während eine tiefe Kopie ein neues Objekt mit neuen, separaten Referenzen für alle enthaltenen Objekte erstellt. Wir können eine tiefe Kopie erstellen, indem wir die clone()-Methode, Kopierkonstruktoren oder Serialisierung verwenden. Es ist wichtig, tiefe Kopien zu erstellen, um unerwartetes Verhalten zu vermeiden, wenn man mit Objekten arbeitet, die referenzierte Klassen haben.