아이템 13. clone 재정의는 주의해서 진행하라
1. Cloneable 인터페이스의 문제점
Cloneable 은 복제 가능함을 의미하는 마커 인터페이스 (maker interface ; 메서드 시그니처만 존재하고, 구현체는 없음) 이지만, 복제 메커니즘을 제대로 정의하지 않는다.
Cloneable 을 구현하는 클래스는 Object 의 clone() 메서드를 호출 할 수 있다.
하지만 Cloneable 을 구현한다고 해서 반드시 안전한 복제가 보장되지는 않는다
Cloneable 을 구현한 객체에서 clone() 을 호출하면, 객체의 모든 필드를 메모리 단위로 복사하는 '얕은 복사(shallow copy)' 가 수행된다.
즉, 객체 내부에 가변 참조 필드(mutable reference fields) 가 있다면, 복제본이 원본과 같은 인스턴스를 공유할 수 있어, 원치 않는 부작용이 발생할 수 있다
2. Object 의 clone 메서드 동작 방식
Object 의 clone() 메서드는 protected 메서드이며, 직접 호출하면 CloneNotSupportedException 이 발생한다.
이를 호출할 수 있으려면 Cloneable 을 구현하고, clone() 을 Public 으로 오버라이딩 해야 한다.
기본적으로 super.clone() 을 호출하면 해당 객체의 모든 필드를 비트 단위로 복사하는 얕은 복사(shallow copy) 수행된다.
얕은 복사의 문제점 예시
위 코드에선 clone() 을 호출하면 elements 배열의 참조만 복사됨
결과적으론 복제된 객체와 원본 객체가 같은 elements 배열을 공유하게 되어 원본이 변경되면 복제본에도 영향을 미친다.
3. clone() 의 올바른 재정의 방법
깊은 복사(deep copy) 를 적용해야 한다.
super.clone() 을 호출하여 기본적인 복사를 수행한 후, 가변 필드를 명시적으로 복사해야 한다.
elements.clone() 을 호출하여 새로운 배열을 생성하고, 복제된 객체가 이를 참조하도록 만든다.
이렇게 하면 원본과 복제본이 서로 영향을 주지 않는 깊은 복사를 할 수 있다.
가변 객체의 복사 문제
만약 객체 내부의 필드가 배열이 아니라 컬렉션(List, Set, Map) 이라면, clone() 을 호출해도 자동으로 깊은 복사가 되지 않는다.
이런 경우, 새로운 컬렉션을 직접 생성하여 복사해야 한다.
위처럼 새로운 ArrayList 를 생성하여 복사하면, 원본과 복제본이 독립적으로 동작할 수 있다.
4. clone() 을 대체할 방법
clone() 을 직접 사용하는 것은 버그를 유발할 가능성이 많고, 유지보수가 어려운 방식이므로, 더 나은 대안이 필요하다.
대체 방법으로 복사 생성자(copy constructor) 와 복사 팩터리(copy factory method) 를 사용할 수 있다.
복사 매커니즘을 허용된 인터페이스로만 제한하는 이상적인 방법
복사 생성자(Copy Constructor)
복사 팩터리 메서드(Copy Factory Method)
복사 생성자/팩터리의 장점
clone() 처럼 예외를 던지지 않음(CloneNotSupportedException 없음)
하위 클래스에서 super.clone()을 호출할 필요가 없어 캡슐화를 해치지 않음
깊은 복사를 보다 명확하게 제어할 수 있음
참고) 얕은 복사 vs 깊은 복사
얕은 복사
객체를 복사할 때 객체의 필드 값을 그대로 복사하지만,
참조(Reference) 타입 필드는 필드 값이 주소 값 이기 때문에, 복사하면 원본 객체와 공유된다.
즉, 복제된 객체가 같은 참조 타입 필드를 가리키므로,
복제된 객체에서 참조 필드의 내용을 변경하면 원본 객체도 영향을 받는다.
깊은 복사
객체를 복사할 때, 참조 타입 필드도 새로운 객체를 생성하여 복사하는 방식
복제된 객체와 원본 객체가 서로 독립적인 객체를 가지므로, 복제된 객체를 수정해도 원본 객체는 영향을 받지 않는다.
5. 결론
Cloneable 은 잘 설계되지 않은 인터페이스로, 새로운 코드에서는 사용을 지양해야 한다.
clone() 을 구현할 경우 반드시 super.clone() 을 호출하고, 가변 필드를 명시적으로 깊은 복사를 해야 한다.
clone() 을 오버라이딩 하는 것 보다 복사 생성자나 복사 팩터리 메서드를 사용하는 것이 일반적으로 더 안전한 방법이다.