public class PhoneNumber implements Cloneable{
...
@Override
public PhoneNumber clone() {
try{
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
위와 같은 방법으로 사용할 수는 있으나 일반적인 인터페이스의 사용방법과는 차이가 있다. 아주 이상하다.
Cloneable 구현한 클래스가 불변 객체만을 참조한다면 문제는 없지만 만약 가변 객체를 참조한다면 원본, 복사본 모두 가변 객체를 참조하게 되니 이거 아주 위험하다.
public class Stack {
private Object[] element;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY = 16;
public Stack() {
this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
}
...
}
복제된 인스턴스가 생성자를 통해 생성된 것이 아니라 참조값을 가져오기 때문에 elements 필드는 원본 인스턴스와 동일한 배열을 참조하게 됩니다.
따라서 양쪽에서 모두 하나의 element에 관여하게 되고 이는 치명적인 버그를 일으킬 수 있다.
@Override
public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
elements 배열의 clone을 재귀적으로 호출해주면 괜찮긴 하다만 element가 final로 선언되어 있으면 불가능한 방식이긴 하다.
public class HashTable implements Cloneable {
private Entry[] buckets = ...;
@AllArgsConstructor
private static class Entry {
final Object key;
Object value;
Entry next;
}
@Override
public HashTable clone() {
try {
HashTable result = (HashTable) super.clone();
result.buckets = buckets.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
원본과 동일한 연결 리스트를 참조하게 되는 경우에는 충분치 않기에 아래와 같이 연결 리스트까지 복제해야 합니다.
public class HashTable implements Cloneable {
private Entry[] buckets = ...;
@AllArgsConstructor
private static class Entry {
final Object key;
Object value;
Entry next;
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();
}
}
}
단, 이 경우도 재귀 호출로 인해 배열의 길이가 길어지면 스택오버플로우를 일으킬 수 있다.
자신과 같은 클래스의 인스턴스를 인수로 받는 복사 생성자
public Yum(Yum yum) { ... }
복사 생성자를 모방한 정적 팩터리 메서드.
public static Yum newInstance(Yum yum) { ... }
이 두 가지 패턴에서는 해당 클래스가 구현한 인터페이스 타입의 인스턴스를 매개변수로 받을 수 있다.
단..! 배열은 clone이 좋다.