[Effective Java 3/e] 아이템 13 - clone 재정의는 주의해서 진행하라


아이템 13 - clone 재정의는 주의해서 진행하라

가변 상태를 참조하지 않는 클래스용 clone 메서드

@Override public PhoneNumber clone() {
  try{
    return (PhoneNumber) super.clone();
  } catch (CloneNotSupportedException e) {
    throw new AssertionError();  // 일어날 수 없는 일이다.
  }
}
  • 클라이언트가 형변환하지 않아도 되게 함.
  • try-catch 블록으로 감싼 이유
    • checked exception 인 CloneNotSupportedException 을 unchecked exception 으로 변한하기 위함

가변 객체

public class Stack {
  private Object[] elements;
  private int size = 0;
  private static final int DEFAULT_INITIAL_CAPACITY = 16;

  public Stack() {
    this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
  }

  public void push(Object e) {
    ensureCapacity();
    elements[size++] = e;
  }

  public Object pop() {
    if (size ==0)
      throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null;  // 다 쓴 참조 해제
    return result;
  }

  ... // 나머지 코드는 생략
}
  • clone 메서드가 단순히 super.clone 결과를 그대로 반환 할 경우
    • Stack 인스턴스의 size 필드는 올바른 값 가짐
    • elements 필드는 원본 Stack 인스턴스와 똑같은 배열 참조
      • 원본이나 복제본 중 하나를 수정하면 같이 수정되어 불변식을 해침

가변 상태를 참조하는 클래스용 clone 메서드

@Override public Stack clone() {
  try{
    Stack result = (Stack) super.clone();
    result.elements = elements.clone();
    return result;
  } catch (CloneNotSupportedException e) {
    throw new AssertionError();  // 일어날 수 없는 일이다.
  }
}
  • 배열의 clone은 런타임 타입과 컴파일타입 모두가 원본 배열과 똑같은 배열을 반환한다.
    • 배열을 복제할 때는 배열의 clone 메서드 사용을 권장

clone 메서드

  • clone 메서드는 생성자와 같은 효과를 낸다.
  • clone은 원본 객체에 아무런 해를 끼치지 않는 동시에 복제된 객체의 불변식을 보장해야 한다.
  • Cloneable을 이미 구현한 클래스를 확장한다면 clone을 잘 작동하도록 구현해야 한다.
    • 그렇지 않은 상황에서는 복사 생성자와 복사 팩터리라는 더 나은 객체 방식을 제공할 수 있다.

복사 생성자

public Yum(Yum yum) { ... };

복사 팩터리

public static Yum newInstance(Yum yum) { ... };
  • 복사 팩터리는 복사 생성자를 모방한 정적 팩터리다.

복사 생성자와 복사 팩터리가 Cloneable/clone 방식보다 나은점

  • 언어 모순적이고 위험한 객체 생성 매커니즘(생성자를 쓰지 않는 방식)을 사용하지 않는다.
  • 정상적인 final 필드 용법과 충돌하지 않는다.
  • 불필요한 검사 예외를 던지지 않는다.
  • 형변환이 필요하지 않다.
  • 해당 클래스가 구현한 ‘인터페이스’ 타입의 인스턴스를 인수로 받을 수 있다.

정리

  • 새로운 인터페이스를 만들 때는 절대 Cloneable을 확장해서는 안 되며, 새로운 클래스도 이를 구현해서는 안 된다.
  • final 클래스라면 Cloneable을 구현해도 위험이 크지 않지만, 성능 최적화 관점에서 검토한 후 문제가 없을 때만 허용해야 한다.
  • 복제 기능은 생성자와 팩터리를 이용하는게 최고!
    • 단, 배열만은 clone 메서드 방식이 가장 깔끔함