Cópia de Objetos: Shallow vs. Deep

JavaBeginner
Pratique Agora

Introdução

Uma cópia rasa (shallow copy) simplesmente copiará os valores dos campos do objeto original para o novo objeto. Em uma cópia rasa, se o objeto original tiver alguma referência a outros objetos de classe, apenas a referência desse objeto é clonada. Portanto, quaisquer alterações neste objeto referenciado serão refletidas tanto no objeto original quanto no objeto clonado. No entanto, uma cópia profunda (deep copy) cria um novo objeto, incluindo todos os objetos referenciados, e quaisquer alterações feitas no objeto clonado não afetarão o objeto original.

Criando o Objeto a Ser Copiado

Primeiramente, criaremos a classe Student que possui uma variável membro do tipo GPA (classe definida pelo usuário) para armazenar o GPA do aluno.

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 o Método clone() para Cópia Raspa

Para implementar a cópia rasa (shallow copy), usaremos o método clone() fornecido pela classe Object. A interface Cloneable deve ser implementada para usar o método clone(), pois ele é um método protegido. Simplesmente chamaremos o método clone() na classe Student para realizar a clonagem rasa.

class Student implements Cloneable {
    // ...

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

Implementando uma Cópia Profunda (Deep Copy) Usando o Método clone()

Podemos fazer uma cópia profunda (deep copy) sobrescrevendo o método clone(), como mostrado abaixo. No método clone(), criaremos um novo objeto usando a palavra-chave new e atribuiremos os valores do objeto original ao novo objeto. Uma vez que tenhamos um novo objeto, criaremos novos objetos para todas as classes referenciadas que não contêm tipos primitivos e atribuiremos novas referências em vez das originais.

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 um Construtor de Cópia

Outra maneira de criar uma cópia profunda é usar um construtor de cópia (copy constructor). Um construtor de cópia é um construtor que recebe um objeto da mesma classe como entrada e retorna um novo objeto com os mesmos valores, mas novas referências. Criaremos um construtor de cópia para a classe Student.

class Student {
    // ...

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

Implementando Serialização

A serialização (serialization) também oferece uma maneira de criar uma cópia profunda. Ao serializar o objeto e, em seguida, deserializá-lo (deserializing), podemos essencialmente produzir um novo objeto com novas referências, mas com os mesmos valores. Para fazer isso, devemos implementar a interface Serializable nas classes que exigem cópia 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;
        }
    }
}

Testando

Agora podemos testar nossos diferentes métodos de cópia profunda, como mostrado no código a seguir.

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

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

    // Deep copy using clone()
    Student cloneCopy = null;
    try {
        cloneCopy = (Student)original.clone();
    } catch(CloneNotSupportedException e) {
        e.printStackTrace();
    }

    // Deep copy using copy constructor
    Student constructorCopy = new Student(original);

    // Deep copy using serialization
    Student serializationCopy = original.deepCopyUsingSerialization();

    // Test for deep copying
    System.out.println("Original object: \t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("Shallow copy: \t\t\t" + shallowCopy.getGpa().getFirstYear() + " " + shallowCopy.getGpa().getSecondYear());
    System.out.println("Clone copy: \t\t\t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
    System.out.println("Copy constructor copy: \t" + constructorCopy.getGpa().getFirstYear() + " " + constructorCopy.getGpa().getSecondYear());
    System.out.println("Serialization copy: \t\t" + serializationCopy.getGpa().getFirstYear() + " " + serializationCopy.getGpa().getSecondYear());

    cloneCopy.setGpa(new GPA(10, 9));
    System.out.println("\nAfter changing cloneCopy's GPA field: ");
    System.out.println("Original object: \t\t" + original.getGpa().getFirstYear() + " " + original.getGpa().getSecondYear());
    System.out.println("Clone copy: \t\t\t" + cloneCopy.getGpa().getFirstYear() + " " + cloneCopy.getGpa().getSecondYear());
}

Executando

Após executar o comando abaixo:

javac Lab.java && java Lab

Você verá a seguinte saída:

Original object:   7 8
Shallow copy:    7 8
Clone copy:    7 8
Copy constructor copy:  7 8
Serialization copy:   7 8

After changing cloneCopy's GPA field:
Original object:   7 8
Clone copy:    10 9

Resumo

Neste laboratório, aprendemos como fazer uma cópia profunda (deep copy) de um objeto em Java. Uma cópia rasa (shallow copy) simplesmente copia os valores dos campos do objeto original para o novo objeto, enquanto a cópia profunda cria um novo objeto com novas referências separadas para todos os objetos incluídos. Podemos fazer uma cópia profunda usando o método clone(), construtores de cópia ou serialização. É importante fazer cópias profundas para evitar comportamentos inesperados ao trabalhar com objetos que possuem classes referenciadas.