clone() 메소드는 Cloneable 인터페이스를 구현한 클래스의 객체 복사본을 생성하는 데 사용됩니다. 기본적으로 필드 단위(field-by-field) 복사를 수행하는데, 그 이유는 Object 클래스가 clone() 메소드를 호출하는 특정 클래스의 멤버 정보를 알 수 없기 때문입니다.
따라서 클래스가 기본형(primitive) 데이터 타입 멤버만 가지고 있다면 새로운 객체 복사본이 생성되고, 그 새 객체에 대한 참조가 반환됩니다. 하지만 클래스에 다른 클래스 타입의 멤버가 포함되어 있다면 해당 멤버들의 객체 참조만 복사되므로, 원본 객체와 복제된 객체 모두 동일한 객체를 참조하게 됩니다.
만약 Cloneable 인터페이스를 구현하지 않은 클래스의 객체에서 clone() 메소드를 호출하려고 하면 CloneNotSupportedException 예외가 발생합니다. 이 인터페이스는 마커 인터페이스(marker interface)로, 이를 구현한다는 것 자체가 해당 클래스의 객체에 대해 Object.clone() 메소드를 호출할 수 있음을 나타내는 신호 역할을 합니다.
문법(Syntax)
protected Object clone() throws CloneNotSupportedException
clone() 메소드는 두 가지 방식으로 구현할 수 있습니다.
얕은 복사(Shallow Copy)
얕은 복사는 클래스에 비기본형(non-primitive) 데이터 타입 멤버가 있을 때 Object.clone() 메소드가 제공하는 기본 복제 기능의 결과입니다. 얕은 복사에서는 객체 참조만 복사되고 참조 대상인 실제 객체는 복사되지 않으므로, 복제된 객체도 원본 객체와 동일한 객체를 참조하게 됩니다.
예제
public class ShallowCopyTest {
public static void main(String args[]) {
A a1 = new A();
A a2 = (A) a1.clone();
a1.sb.append("Tutorialspoint!");
System.out.println(a1);
System.out.println(a2);
}
}
class A implements Cloneable {
public StringBuffer sb = new StringBuffer("Welcome to ");
public String toString() {
return sb.toString();
}
public Object clone() {
try {
return super.clone();
} catch(CloneNotSupportedException e) {
}
return null;
}
}실행 결과
Welcome to Tutorialspoint! Welcome to Tutorialspoint!
위 실행 결과에서 볼 수 있듯이, 원본 객체(a1)의 StringBuffer 내용을 변경하자 복제된 객체(a2)의 출력 결과도 함께 변경되었습니다. 이는 두 객체가 동일한 StringBuffer 객체를 공유하고 있기 때문입니다.
깊은 복사(Deep Copy)
비기본형(non-primitive) 타입 멤버를 가진 클래스에서 깊은 복사(Deep Copy)를 구현하려면 clone() 메소드를 직접 오버라이드해야 합니다. 깊은 복사는 멤버 객체들까지 함께 복제해야 하는데, 이 작업은 기본 복제 메커니즘이 자동으로 수행해 주지 않기 때문입니다.
예제
public class DeepCopyTest {
public static void main(String args[]) {
A a1 = new A();
A a2 = (A) a1.clone();
a1.sb.append(" TutorialsPoint!");
System.out.println(a1);
System.out.println(a2);
}
}
class A implements Cloneable {
public StringBuffer sb = new StringBuffer("Welcome to ");
public String toString() {
return sb.toString();
}
public Object clone() {
try {
A a = (A) super.clone();
a.sb = new StringBuffer(sb.toString());
return a;
}
catch(CloneNotSupportedException e) {
}
return null;
}
}실행 결과
Welcome to TutorialsPoint! Welcome to
깊은 복사 예제에서는 clone() 메소드 내부에서 StringBuffer 객체를 새로 생성하여 할당했습니다(a.sb = new StringBuffer(sb.toString())). 따라서 원본 객체(a1)의 내용이 변경되어도 복제된 객체(a2)는 영향을 받지 않고 독립적인 상태를 유지합니다.
정리
clone() 메소드를 사용할 때는 클래스의 멤버 구성에 따라 얕은 복사와 깊은 복사의 차이를 반드시 이해해야 합니다. 참조 타입 멤버가 존재하는 경우, 의도치 않은 부작용(side effect)을 방지하려면 clone() 메소드를 오버라이드하여 깊은 복사를 구현하는 것이 안전합니다.