규칙 11. clone을 재정의할 떄는 신중하라

Cloneable의 괴상함

Cloneable은 어떤 객체가 복제를 허용한다는 사실을 알리는 데 쓰려고 고안된 믹스인(mixin) 인터페이스이다. 해당 인터페이스에는 clone 메서드가 없으며 Object의 clone 메서드는 protected로 선언되어 있다.
Cloneabledms protected로 선언된 Object의 clone 메소드가 어떻게 동작할지 결정한다. 어떤 클래스가 Clonable을 구현하면 Object의 clone메서드는 해당 객체를 필드 다누이로 복사한 객체를 반환하고 Cloneable을 구현하지 않은 클래스라면 clone 메서드는 CloneNotSupportedException을 던진다.

clone 메서드의 일반 규약

객체의 복사본을 만들어서 반환한다. 그리고 다음을 따른다.
x.clone() != x 의 조건은 참이어야 한다,

x.clone().getClass() == x.getClass()
위의 조건은 참이겠지만 반드시 그래야 하는 것은 아니다.

x.clone().equals(x)
위의 코드를 실행한 결과도 true가 되겠지만 반드시 그래야하는것은 아니다.

위의 코드를 실행한 결과도 true가 되겠지만 반드시 그래야 하는 것은 아니다.
객체를 복사하면 보통 같은 클래스의 새로운 객체가 만들어지는데, 내부 자료 구조까지 복사해야 될 수도 있다. 어떤 생성자도 호출되지 않는다.

규약의 문제점

어떠한 생성자도 호출되지 않는다는 점은 심하다. 복사본의 내부 객체는 생성자로 만들 수도있다. 클래스가 final로 선언되어 있다면, 생성자로 만든 객체를 반환하도록 clone을 구현할 수도 있다. 하지만 클래스가 final이든 아니든 생성자로 객체를 반환한다면 이 경우 원하는 클래스가 아닐것이다. 즉 clone를 재정의 할 때는 반드시 super.clone을 호출 해 얻은 객체를 반환해야 한다.

제대로된 사용방법

1.Cloneable인터페이스를 구현하는 클래스는 제대로 동작하는 public clone 메서드를 제공해야 하고 이를 위해서는 상위클래스들이 제대로된 public 또는 protected clone 메서드를 제공해야 한다.

2.clone() 메소드의 반환형은 적절한 형태로 반환하여 라이브러리가 할 수 있는 일을 클라이언트에게 미루지 말자. 즉 라이브러리가 형변환을 하고 클라이언트는 변환하여 사용하지 않도록 하자.

3.복제할 객체가 변경가능 객체에 대한 참조 필드를 가지고 있을경우 deepCopy를 통해 극복하도록 하자.(ex 링크드 리스트의 배열!) 이방법이 싫다면 super.clone 호출 결과로 반환된 객체의 모든 필드를 초기상태로 되돌려 놓은 다음에 상위레벨 메서드를 호출해서 객체상태를 다시 만드는것이다.

  1. 다중스레드에 안전해야 하는 thread-safe 클래스를 Cloneable로 만들려면, clone 메서드에도 동기화 메커니즘을 적용해야 한다. Object.clone 메서드에는 동기화 메커니즘이 적용되어 있지 않으므로 설사 Object.clone 이 대체로 만족스럽다고 하더라도 super.clone()을 호출하는 동기화된 clone 메서드를 만들어야 할 것이다.

clone의 주의사항

1.clone 메서드는 또 다른 형태의 생성자이다. 원래 객체를 손상시키지 않아야 하며 복사본의 불변식도 만족시켜야 한다.
2.clone 메서드는 복사본의 비-final 메서드, 즉 재정의 가능 메서드를 복사 도중에 호출해서는 안된다. 만일 하위 클래스에서 재정의한 메서드를 clone 안에서 호출하면 해당 메서드는 복사본의 상태가 완성되기 전에 호출될 것이며 복사본의 상태를 망가뜨릴것이다.

정리하자면

계승을 위해 설계된 클래스에 clone을 재정의 할 때 Object.clone을 그대로 흉내내야 한다. protected로 선언하고, CloneNotSupportedException 예외를 던지고, Cloneable 인터페이스는 구현하지 않아야한다. 그래야 Object 클래스를 직접 계승한 클래스가 그렇듯, 하위 클래스가 Cloneable 구현 여부를 마음대로 정할 수 있다.

또한 Cloneable을 구현하는 모든 클래스는 반환값 자료형이 자기 자신은 public clone 메서드를 재정의 해야한다. 또한 처음에 super.clone을 호출해야한다. 그렇게 해서 만들어진 객체를 두고 수정해야 하는 필드를 수정한다.(deepCopy 등을 활용)

반전의 결론

객체복제를 지원하는 좋은 방법은, 복사 생성자나 복사 팩터리를 제공하는 것이다. 복사 생성자는 단순히 같은 클래스의 객체 하나를 인자로 받는 생성자이다. 복사 팩터리 또한 같은 클래스의 객체 하나를 인자로 받는것이다.
이는 Cloneable/clone보다 좋은점이 많다. 언어 외적 객체생성 수단에 의존하지 않으며 , 규약에 충실할 것을 요구하지도 않으며, final 필드 용법과 충돌하지도 않으며, 불필요한 예외를 검사하도록 요구하지도 않으며 형 변환도 필요없다.
또한 해당 메서드가 정의된 클래스가 구현하는 인터페이스를 인자로 받을 수 있다.
즉 Cloneable은 단점이 너무많다.. 쓰는것을 지양하자.. 배열의 clone메소드는 어쩔수 없이 쓰는경우가 있다.

Share