Cloneable은 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이지만, 아쉽게도 의도한 목적을 제대로 이루지 못했다.
가장 큰 문제는 clone 메서드가 선언된 곳이 Cloneable이 아닌 Object이고, 그마저도 protected 라는 데 있다.
그래서 Closeable을 구현하는 것만으로는 외부 객체에서 clone 메서드를 호출할 수 없다.
리플렉션을 사용하면 가능하지만, 100% 성공하는 것도 아니다.
해당 객체가 접근이 허용된 clone 메서드를 제공한다는 보장이 없기 때문이다.
하지만 이를 포함한 여러 문제점에도 불구하고 Cloneable 방식은 널리 쓰이고 있어서 잘 알아두는 것이 좋다.
Cloneable 인터페이스는 Object의 protected 메서드인 clone의 동작 방식을 결정한다.
Cloneable을 구현한 클래스의 인스턴스에서 clone을 호출하면 그 객체의 필드들을 하나한 복사한 객체를 반환하며,
그렇지 않은 클래스의 인스턴스에서 호출하면 CloneNotSupportedExcetpion을 던진다.
인터페이스를 구현한다는 것은 일반적으로 해당 클래스가 그 인터페이스에서 정의한 기능을 제공한다고 선언하는 행위다.
그런데 Cloneable의 경우에는 상위 클래스에 정의된 protected 메서드의 동작 방식을 변경한 것이다.
명세에서는 이야기하지 않지만 실무에서 Cloneable을 구현한 클래스는 clone 메서드를 public으로 제공하며,
사용자는 당연히 복제가 제대로 이뤄지리라 기대한다.
이 기대를 만족시키려면 모든 상위 클래스는 복잡하고, 강제할 수 없고, 허술하게 기술된 프로토콜을 지켜야만 하는데,
그 결과로 위험하고 모순적인 매커니즘이 탄생한다.
생성자를 호출하지 않고도 객체를 생성할 수 있게 된다.
하위 클래스에서 super.clone을 호출하면 잘못된 클래스의 객체가 만들어져서 하위 클래스의 clone 메서드가 제대로 동작하지 않게 된다.
@Override
public PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // 일어날 수 없는 일이다.
}
}
클래스의 선언에 Cloneable을 implements 해서 clone()을 오버라이드 한다.
clone으로 반환되는 Object를 캐스팅 해줬다.
자바가 공변 반환 타이핑을 지원하니 이렇게 하는 것이 가능하고 권장하는 방식이기도 하다.
하지만 클래스가 가변 객체를 참조하는 순간 재앙으로 돌아온다.
예를 들어 클래스가 어떠한 배열을 참조한다면 clone으로 만들어진 배열은 원본과 같은 배열을 참조할 것이다.
원본이나 복제본 중 하나를 수정하면 다른 하나도 수정되어 불변식을 해치게 된다.
그래서 clone 메서드는 사실상 생성자와 같은 효과를 낸다.
즉, clone은 원본 객체에 아무런 해를 끼치지 않는 동시에 복제된 객체의 불변식을 보장해야 한다.
그래서 이러한 가변객체를 참조하는 클래스를 clone한다면 배열의 clone 메서드를 사용하여 clone()을 정의해야 한다.
ex)
@Override public Stack clone() {
try {
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
하지만 이러한 경우 가변객체가 final이면 사용할 수 없다.
final 필드에는 새로운 값을 할당할 수 없기 때문이다.
그래서 복제할 수 있는 클래스를 만들기 위해 일부 필드에서 final 한정자를 제거해야 할 수도 있다.
이 외에도 복잡한 clone을 구현하기 위한 몇가지 방법이 있지만
Cloneable이 몰고 온 모든 문제를 짚어보면 , 새로운 인터페이스를 만들 때는 Cloneable을 확장해서는 안되고,
새로운 클래스도 이를 구현하면 안된다.
final 클래스라면 구현해도 위험이 크지 않지만, 성능 최적화 관점에서 검토한 후 별다른 문제가 없을 때만 드물게 허용해야 한다.
기본 원칙은 복제 기능은 생성자와 팩터리를 이용하는게 최고 라는 것이다.
다만 배열은 clone 메서드 방식이 가장 깔끔한, 이 규칙의 합당한 예외라고 할 수 있다.
'JAVA > 이펙티브 자바' 카테고리의 다른 글
15. 클래스와 멤버의 접근 권한을 최소화하라 (0) | 2023.07.01 |
---|---|
14. Comparable을 구현할지 고려하라 (0) | 2023.07.01 |
12. toString을 항상 재정의하라 (0) | 2023.07.01 |
11. equals를 재정의하려거든 hashCode도 재정의하라 (0) | 2023.07.01 |
10. equals는 일반 규약을 지켜 재정의하라 (0) | 2023.06.26 |