핵심 정리: 애매모호한 clone 규약
x.clone() != x 반드시 truex.clone().getClass() == x.getClass() 반드시 truex.clone().equals(x)는 true가 아닐 수도 있다.
Cloneable 인터페이스를 구현하고clone() 메서드를 재정의한다. 이때 super.clone()을 사용해야 한다. // 일반적으로 override 방법은 아니다.
// 코드 13-1 가변 상태를 참조하지 않는 클래스용 clone 메서드 (79쪽)
@Override
public PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch (**CloneNotSupportedException** e) {
throw new AssertionError(); // 일어날 수 없는 일이다.
}
}
===
// clone 을 호출할 때는 생성자를 호출하면 안된다.
// anti pattern
public class Item implements Cloneable {
private String name;
/**
* 이렇게 구현하면 하위 클래스의 clone()이 깨질 수 있다. p78
* @return
*/
@Override
public Item clone() {
Item item = new Item();
item.name = this.name;
return item;
}
}
public class SubItem extends Item implements Cloneable {
private String name;
@Override
public SubItem clone() {
return (SubItem)super.clone();
}
public static void main(String[] args) {
SubItem item = new SubItem();
SubItem clone = item.clone();
System.out.println(clone != item);
System.out.println(clone.getClass() == item.getClass());
System.out.println(clone.equals(item));
}
}
핵심 정리: 가변 객체의 clone 구현하는 방법
배열을 복제할 때는 배열의 clone 메서드를 사용하라.
// 코드 13-2 가변 상태를 참조하는 클래스용 clone 메서드
// TODO stack -> elementsS[0, 1]
// TODO copy -> elementsC[0, 1]
// TODO elementsS[0] == elementsC[0]
@Override public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
경우에 따라 final을 사용할 수 없을지도 모른다.
필요한 경우 deep copy를 해야한다.
/**
* TODO hasTable -> entryH[],
* TODO copy -> entryC[]
* TODO entryH[0] != entryC[0]
*
* @return
*/
@Override
public HashTable clone() {
HashTable result = null;
try {
result = (HashTable)super.clone();
result.buckets = new Entry[this.buckets.length];
for (int i = 0 ; i < this.buckets.length; i++) {
if (buckets[i] != null) {
result.buckets[i] = this.buckets[i].deepCopy(); // p83, deep copy
}
}
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
public static void main(String[] args) {
HashTable hashTable = new HashTable();
Entry entry = new Entry(new Object(), new Object(), null);
hashTable.buckets[0] = entry;
HashTable clone = hashTable.clone();
System.out.println(hashTable.buckets[0] == entry);
System.out.println(hashTable.buckets[0] == clone.buckets[0]);
}
// iterative way not recursive as stackoverflow
public 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;
}
super.clone으로 객체를 만든 뒤, 고수준 메서드를 호출하는 방법도 있다.
오버라이딩 할 수 있는 메서드는 참조하지 않도록 조심해야 한다.
상속용 클래스(상위 클래스)는 Cloneable을 구현하지 않는 것이 좋다.
Cloneable을 구현한 스레드 안전 클래스를 작성할 때는 동기화를 해야 한다.
현실적으로는 생성자를 사용하여 copy 한다.
사용자가 원하는 하위 타입으로 변환이 가능하다.
// 1. 생성자를 이용한 방법
public static void main(String[] args) {
Set<String> hashSet = new HashSet<>();
hashSet.add("keesun");
hashSet.add("whiteship");
System.out.println("HashSet: " + hashSet);
**Set<String> treeSet** = new TreeSet<>(hashSet);
System.out.println("TreeSet: " + treeSet);
====
// 2. copy 생성자 전용 메서드
public PhoneNUmber(PhoneNUmber phoneNumber){
this(phoneNUmber.areaCode, .. )
}