아이템 11. clone을 재정의할 때는 신중하라.
11. clone을 재정의할 때는 신중하라.
java.lang.Object명세(JavaSE6) clone 메서드의 일반 규약
- 객체의 복사본을 만들어 반환한다. “복사”라는 의미는 clone을 정의한 클래스마다 다르다.
- 일반적으로 다음과 같은 조건을 충족되어야 한다.
- x.clone() != x : 이 조건은 참어야 한다. (clone은 x에 대한 새로운 객체를 만들기 때문에 메모리 비교시 무조건 다르다)
- x.clone.getClass() == x.getClass() : 이 조건은 참이다. 단, 상속 관계가 존재할지 다를수도 있다.
- x.clone().equals(x) : 이 조건도 참어야 한다. 하지만 때에 따라서 클래스 내부의 자료구조가 복잡하거나 해서 비교 되는 데이터가 부분적으로 다를때, 논리적으로 참일수 있으나, 물리적으론 다른 값이 섞여 있을수 있다. 거기다 명세서에는 생성자 호출도 강제저이지 않기 때문에 초기화 여부도 명확하지 않다.
명세 3과 같은 이유로 인해, 비 final 클래스에 clone을 재정의 할때는 반드시 super.clone을 호출해서 얻은 객체를 반환해야 한다.
@Override
public PhoneNumber clone(){
try{
return (PhoneNumber) super.clone();
}catch( CloneNotSupportedException e ){
throw new AssertionError(); // JavaSE 1.5 이후에선 수행될 일 없음
}
}
위의 메서드는 Object가 아니라 PhoneNumber 객체를 반환한다. 1.5 버전 이후 부터 공변 반환형(convariant return type)이 도입되었기 때문이다.
Stack 클래스와 같이 내부 구조가 복잡한 클래스에 clone를 구현하려면 다음과 같이 구현되어야 한다.
@Override
public Stack clone(){
try{
Stack stack = (Stack) super.clone(); // Stack의 껍대기 구조만 복사함
stack.elements = elements.clone(); // 내부 데이터들도 복제해야 함( 주의, elements가 final로 선언되어 있으면 동작하지 않음)
return stack;
}catch( CloneNotSupportedException e ){
throw new AssertionError();
}
}
사실상, clone메서드는 또 다른 형태의 생성자다. 원래 객체를 손상시키는 일이 없도록 해야 하고, 복사본의 불변식(invariant)도 제대로 만족시켜야 한다.
복사해야 하는 데이터가 점점 복잡해 질수록 clone 메서드에서 고려해야 될 사항이 아주 많아진다.(Effective Java 2/E 77.p 참조)
결국, 객체 복제를 지원하는 좋은 방법은, 복사 생성자(copy constructor)나 복사 팩터리(copy factory)를 제공하는 것이다.