소개
얕은 복사는 단순히 원본 객체의 필드 값을 새 객체로 복사합니다. 얕은 복사에서는 원본 객체가 다른 클래스 객체에 대한 참조를 가지고 있는 경우, 해당 객체의 참조만 복제됩니다. 따라서 이 참조된 객체에 대한 변경 사항은 원본 객체와 복제된 객체 모두에 반영됩니다. 반면, 깊은 복사 (deep copy) 는 모든 참조된 객체를 포함하여 새로운 객체를 생성하며, 복제된 객체에 가해진 변경 사항은 원본 객체에 영향을 미치지 않습니다.
복사할 객체 생성 (Creating the Object to Be Copied)
먼저 학생의 GPA 를 저장하기 위해 GPA(사용자 정의 클래스) 타입의 멤버 변수를 갖는 Student 클래스를 생성합니다.
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;
}
}
얕은 복사를 위한 clone() 메서드 구현
얕은 복사를 구현하기 위해 Object 클래스에서 제공하는 clone() 메서드를 사용합니다. clone() 메서드는 protected 메서드이므로 사용하려면 Cloneable 인터페이스를 구현해야 합니다. 얕은 복제를 수행하기 위해 Student 클래스에서 단순히 clone() 메서드를 호출합니다.
class Student implements Cloneable {
// ...
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
}
clone() 메서드를 사용한 Deep Copy 구현 (Implementing a Deep Copy Using the Clone() Method)
아래와 같이 clone() 메서드를 오버라이딩하여 깊은 복사를 수행할 수 있습니다. clone() 메서드 내에서 new 키워드를 사용하여 새로운 객체를 생성하고, 원본 객체의 값을 새 객체에 할당합니다. 새로운 객체를 생성한 후, 기본형 (primitive type) 을 포함하지 않는 모든 참조 클래스에 대해 새로운 객체를 생성하고, 원본 참조 대신 새로운 참조를 할당합니다.
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();
}
}
복사 생성자 구현 (Implementing Copy Constructor)
깊은 복사를 생성하는 또 다른 방법은 복사 생성자 (copy constructor) 를 사용하는 것입니다. 복사 생성자는 동일한 클래스의 객체를 입력으로 받아 동일한 값을 가지지만 새로운 참조를 가진 새로운 객체를 반환하는 생성자입니다. Student 클래스에 대한 복사 생성자를 생성합니다.
class Student {
// ...
public Student(Student student) {
this(student.getName(), new GPA(student.getGpa().getFirstYear(), student.getGpa().getSecondYear()));
}
}
직렬화 구현 (Implementing Serialization) - Java, Python 등
직렬화 (Serialization) 는 깊은 복사를 생성하는 또 다른 방법을 제공합니다. 객체를 직렬화 (serialize) 한 다음 역직렬화 (deserialize) 함으로써, 본질적으로 새로운 참조를 가지지만 동일한 값을 가진 새로운 객체를 생성할 수 있습니다. 이를 위해 깊은 복사가 필요한 클래스에서 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;
}
}
}
테스팅 (Testing) - 단위 테스트, 통합 테스트, TDD
이제 다음 코드와 같이 깊은 복사의 다양한 방법을 테스트할 수 있습니다.
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());
}
실행 (Running) - 애플리케이션 실행, 배포, 서버 운영
다음 명령을 실행한 후:
javac Lab.java && java Lab
다음과 같은 출력을 볼 수 있습니다.
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
요약
이 랩에서는 Java 에서 객체의 깊은 복사 (deep copy) 를 만드는 방법을 배웠습니다. 얕은 복사 (shallow copy) 는 단순히 원래 객체의 필드 값을 새 객체로 복사하는 반면, 깊은 복사는 포함된 모든 객체에 대해 새롭고 별도의 참조를 가진 새 객체를 생성합니다. clone() 메서드, 복사 생성자 (copy constructor), 또는 직렬화 (serialization) 를 사용하여 깊은 복사를 수행할 수 있습니다. 참조된 클래스가 있는 객체로 작업할 때 예상치 못한 동작을 방지하기 위해 깊은 복사를 수행하는 것이 중요합니다.



