Copie superficielle vs. copie profonde d'objets

JavaJavaBeginner
Pratiquer maintenant

💡 Ce tutoriel est traduit par l'IA à partir de la version anglaise. Pour voir la version originale, vous pouvez cliquer ici

Introduction

Une copie superficielle copiera simplement les valeurs des champs de l'objet original dans le nouvel objet. Dans une copie superficielle, si l'objet original a des références à d'autres objets de classe, alors seule la référence de cet objet est clonée. Par conséquent, tout changement dans cet objet référencé sera réflété à la fois dans l'objet original et dans l'objet cloné. Cependant, une copie profonde crée un nouvel objet, y compris tous les objets référencés, et tout changement effectué sur l'objet cloné n'affectera pas l'objet original.


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{{"Copie superficielle vs. copie profonde d'objets"}} java/classes_objects -.-> lab-117441{{"Copie superficielle vs. copie profonde d'objets"}} java/constructors -.-> lab-117441{{"Copie superficielle vs. copie profonde d'objets"}} java/encapsulation -.-> lab-117441{{"Copie superficielle vs. copie profonde d'objets"}} java/interface -.-> lab-117441{{"Copie superficielle vs. copie profonde d'objets"}} java/serialization -.-> lab-117441{{"Copie superficielle vs. copie profonde d'objets"}} java/io -.-> lab-117441{{"Copie superficielle vs. copie profonde d'objets"}} java/object_methods -.-> lab-117441{{"Copie superficielle vs. copie profonde d'objets"}} end

Création de l'objet à copier

Nous allons tout d'abord créer la classe Student qui a une variable membre de type GPA (classe définie par l'utilisateur) pour stocker la moyenne générale du étudiant.

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

Implémentation de l'interface Cloneable et redéfinition de la méthode clone() pour une copie superficielle

Pour implémenter une copie superficielle, nous allons utiliser la méthode clone() fournie par la classe Object. L'interface Cloneable doit être implémentée pour utiliser la méthode clone() car il s'agit d'une méthode protégée. Nous appellerons simplement la méthode clone() dans la classe Student pour effectuer un clonage superficiel.

class Student implements Cloneable {
    //...

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

Implémentation d'une copie profonde en utilisant la méthode clone()

Nous pouvons effectuer une copie profonde en redéfinissant la méthode clone() comme indiqué ci-dessous. Dans la méthode clone(), nous allons créer un nouvel objet à l'aide du mot clé new et attribuer les valeurs de l'objet original à ce nouvel objet. Une fois que nous avons un nouvel objet, nous allons créer de nouveaux objets pour toutes les classes référencées qui ne contiennent pas de types primitifs et attribuer de nouvelles références au lieu des références 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();
    }
}

Implémentation du constructeur de copie

Une autre manière de créer une copie profonde est d'utiliser un constructeur de copie. Un constructeur de copie est un constructeur qui prend en entrée un objet de la même classe et renvoie un nouvel objet avec les mêmes valeurs mais de nouvelles références. Nous allons créer un constructeur de copie pour la classe Student.

class Student {
    //...

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

Implémentation de la sérialisation

La sérialisation fournit également un moyen de créer une copie profonde. En sérialisant l'objet puis en le désérialisant, nous pouvons essentiellement produire un nouvel objet avec de nouvelles références mais les mêmes valeurs. Pour ce faire, nous devons implémenter l'interface Serializable dans les classes qui nécessitent une copie profonde.

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

Tests

Maintenant, nous pouvons tester nos différentes méthodes de copie profonde comme le montre le code suivant.

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

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

    // Copie profonde en utilisant clone()
    Student cloneCopy = null;
    try {
        cloneCopy = (Student)original.clone();
    } catch(CloneNotSupportedException e) {
        e.printStackTrace();
    }

    // Copie profonde en utilisant le constructeur de copie
    Student constructorCopy = new Student(original);

    // Copie profonde en utilisant la sérialisation
    Student serializationCopy = original.deepCopyUsingSerialization();

    // Test de la copie profonde
    System.out.println("Objet original : \t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("Copie superficielle : \t\t" + shallowCopy.getGpa().getFirstYear() + " " + shallowCopy.getGpa().getSecondYear());
    System.out.println("Copie par clonage : \t\t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
    System.out.println("Copie par constructeur de copie : \t" + constructorCopy.getGpa().getFirstYear() + " " + constructorCopy.getGpa().getSecondYear());
    System.out.println("Copie par sérialisation : \t\t" + serializationCopy.getGpa().getFirstYear() + " " + serializationCopy.getGpa().getSecondYear());

    cloneCopy.setGpa(new GPA(10, 9));
    System.out.println("\nAprès avoir modifié le champ GPA de cloneCopy : ");
    System.out.println("Objet original : \t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("Copie par clonage : \t\t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
}

Exécution

Après avoir exécuté la commande ci-dessous :

javac Lab.java && java Lab

Vous verrez la sortie suivante :

Objet original : 		7 8
Copie superficielle : 	7 8
Copie par clonage : 	7 8
Copie par constructeur de copie : 	7 8
Copie par sérialisation : 	7 8

Après avoir modifié le champ GPA de cloneCopy :
Objet original : 		7 8
Copie par clonage : 	10 9

Récapitulatif

Dans ce laboratoire, nous avons appris à effectuer une copie profonde d'un objet en Java. Une copie superficielle copie simplement les valeurs des champs de l'objet original vers le nouvel objet, tandis qu'une copie profonde crée un nouvel objet avec de nouvelles références séparées pour tous les objets inclus. Nous pouvons effectuer une copie profonde en utilisant la méthode clone(), les constructeurs de copie ou la sérialisation. Il est important de réaliser des copies profondes pour éviter tout comportement inattendu lorsqu'on travaille avec des objets qui ont des classes référencées.