cloneable 를 구현한 클래스에서 clone 을 호출하면 그 객체의 필드를 하나하나 복사한 객체를 반환하고, 그렇지 않은 클래스의 인스턴스에서 호출하면 CloneNotSupportedException 을 던진다.
clone 메서드에서 생성자를 통해 인스턴스를 반환하면 컴파일러는 오류를 내지 않을 것이다. 하지만 그 클래스의 하위클래스에서 super.clone()을 호출한다면 잘못된 클래스 객체가 만들어져 하위 클래스의 clone 메서드가 제대로 작동하지 않을 것이다.
@Override
public PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch (ConleNotSupportedException e) {
throw new AssertionError(); // 일어날 수 없는 일이다.
}
}
가변 상태를 참조하는 클래스는 깊은 복사
를 진행해줘야 한다. 단순히 super.clone() 을 호출한다면 원본의 참조 필드와 동일한 필드를 참조할 것이고, 원본이나 복제본 중 하나를 수정하면 다른 하나에도 영향을 끼치게 된다.
아래와 같은 방법으로 깊은 복사
를 진행한다.
@Override
public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
clone을 재귀적으로 호출하는 것만으로 충분하지 않은 경우도 있다.
@Override
public HashTable clone() {
try {
HashTable result = (HashTable) super.clone();
result.buckets = buckets.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
Entry deepCopy() {
return new Entry(key, value, next == null ? null : next.deepCopy());
}
@Override
public HashTable clone() {
try {
HashTable result = (HashTable) super.clone();
result.buckets = new Entry[buckets.length];
for (int i = 0; i < buckets.length; i++)
if (buckets[i] != null)
result.buckets[i] = buckets[i].deepCopy();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
Entry deepCopy() {
Entry result = new Entry(key, value, next);
for (Entry p = result; p.next != null; p = p.next)
p.next = new Entry(p.next.key, p.next.value, p.next.next);
return result;
}
복사 생성자와 복사 팩터리를 이용한 방법도 있다.
public Yum(Yum yum) { ... }; // 복사 생성자
public static Yun newInstance(Yun yun) { ... }; // 복사 팩터리
위 방법들은 생성자를 쓰지 않는 방식을 사용하지 않고, 정상적인 final 필드 용법과 충돌하지도 않고, 불필요한 검사 예외를 던지지도 않고 형변환도 필요없다. 그리고 해당 클래스가 구현한 인터페이스 타입의 인스턴스를 인수로 받을 수도 있다.
이를 통하면 클라이언트는 원본의 구현 타입에 얽매이지 않고 복제본의 타입을 직접 선택할 수 있다.