Search

Item 13. clone 재정의는 주의해서 진행하라

생성일
2023/07/27 10:36
챕터
3장 - 모든 객체의 공통 메서드

Cloneable

Cloneable은 해당 클래스가 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스(mixin interface)이다.
Cloneablem의 가장 큰 문제는 Object에 clone 메서드가 protected로 선언되어 있어, Cloneable을 구현하는 것만으로는 외부 객체에서 clone을 호출할 수 없다는 것이다.
Cloneable을 구현한 클래스는 clone 메서드를 public으로 제공하는데, 이를 통해 생성자 없이도 객체를 생성할 수 있게 된다.
clone 메서드의 규약은 다음과 같다.
x.clone() != x
x.clone().getClass() == x.getClass()
x.clone().equals(x) == true
clone 메서드는 super.clone을 호출해 객체를 얻어 반환해야한다. 그렇지 않으면 하위 클래스에서 clone 메서드가 제대로 동작하지 않을 수 있다.
@Override public PhoneNumber clone() { try { return (PhoneNumber) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); // 일어날 수 없다. } }
Java
복사
이와 같이 super.clone을 통해 호출하여, 자바의 공변 반환 타이핑(covariant return typing)을 이용해 사용하는 것이 권장된다.
다만 아래와 같이 클래스 내에 가변 인수를 가지면 조금 더 주의를 기울여야 한다.
public class Stack { private Object[] elements; private int size = 0; public Stack() { this.elements = new Object[16]; } ... }
Java
복사
위의 클래스를 super.clone 호출 후 바로 반환하면, size 값은 제대로 가지지만 elements는 원본 Stack과 똑같은 배열을 참조하게 되어 불변식이 깨질 수 있다.
clone 메서드는 생성자와 같다고 말했는데, 그렇기 때문에 원본 객체에 아무런 영향을 미치지 않으면서 복제된 객체의 불변식을 보장해야한다.
@Override public Stack clone() { try { Stack result = (Stack) super.clone(); result.elemnts = elements.clone(); return result; } catch (CloneNotSupportedException e) { throw new AssertionError(); } }
Java
복사
그렇기 때문에 이와 같이 elements 배열의 clone을 호출하여 복제된 배열을 저장하도록 만드는 방법을 사용하자.
하지만 elements가 final이라면 위 방법은 사용하지 못하므로, 복제를 위해 일부 필드에서 final을 제거해야 할 수도 있다.
이는 Cloneable 아키텍처의 가변객체를 참조하는 필드는 final로 선언하라는 일반 용법과 충돌한다.
Cloneable은 이처럼 잘못 설계된 인터페이스로, 감안하고 사용해야한다.
하위 클래스가 생성되기도 전에 하위 클래스의 메서드를 호출하기 때문에 생성자는 하위 클래스에서 재정의 될 수 있는 메서드를 호출하면 사용해서는 안되는데, 이는 clone 메서드에서도 동일하다.
clone 메서드를 재정의하여 구현할 때는 throws 절을 없애야 한다. 그래야 사용하기 편하다.
상속용 클래스는 Cloneable을 구현해서는 안되는데, 이를 막는 방법은 두 가지가 있다.
첫 번째는 Object에서 구현한 방법처럼 제대로 동작하는 clone 메서드를 protected로 선언해두고 CloneNotSupportedException을 던질 수도 있다고 선언하는 방법이다. Object를 상속할 때처럼 하위 클래스에서 Cloneable 구현 방법을 선택할 수 있게 만들어주는 방법이다.
두 번째는 다음과 같이 clone을 동작하지 않게 구현하고 재정의도 못하게 막는 것이다.
@Override protected final Object clone() throws CloneNotSupportedException { throw new CloneNotSupportedException(); }
Java
복사
Cloneable을 구현한 클래스를 멀티 스레딩 환경에서 스레드 안전하게 사용하고 싶다면, clone 메서드 역시 적절하게 동기화 해주어야한다.
복사 생성자나 복사 팩터리 같은 더 나은 객체 복사 방식을 제공하는 것을 고려해보자.