[Effective Java] clone 메소드는 신중하게 오버라이드 하자. |
-
Cloneable 인터페이스는 복제를 허용하는 객체라는 것을 알리는 목적으로 사용하는 믹스인 인터페이스( mixin interface ) 이다.
믹스인 인터페이스이기 때문에 자신이 clone method 를 가지고 있는 것도 아니다.
Object 의 clone 은 Cloneable 을 implement 하지 않으면 사용할 수 없다.
-
Cloneable 을 implements 한 class 에 clone 을 호출하면, 해당 객체의 복제본을 만들어 반환한다.
복제 객체는 원본 객체와 같은 필드를 가지며 각 필드의 값도 복사된다.
하지만 reference 를 가진 녀석들은 deep copy 가 아닌 soft copy 를 수행한다는 점에 주목해야 한다.
clone 메소드는 또 다른 생성자와 같다.
clone 메소드가 원본 객체에 손상을 주지 않으면서 원본과 복제 객체 간의 상호 영향도 없도록 해야 한다.
즉 reference 의 경우 deep copy 를 해주어야 한다.
이 deep copy 는 해당 객체의 clone 을 호출해주는 것만으로 끝나는 것이 아니다.
예를 들어 해당 객체가 Collection 일 경우에는 Collection 안의 내용물들도 clone 을 해주는 진정한 deep copy 가 되어야 한다.
-
Cloneable 을 implements 하지 않은 class 에 clone 을 호출하면,
CloneNotSupportedException 예외가 발생한다.
-
clone 메소드를 통해 복제하는 경우에는 생성자를 호출하지 않고 객체가 생성되어 복제된다.
따라서 해당 클래스와 그 클래스의 모든 수퍼 클래스들의 문서화된 규약을 clone 에서 지켜줘야 한다.
-
final 이 아닌 수퍼 클래스의 clone 메소드를 오버라이드 할 경우, 서브 클래스의 clone 에서는 반드시
super.clone 을 호출하여 얻은 객체를 반환해야 한다.
super 가 Cloneable 을 implements 하지 않았다면 또 다시 문제가 된다...
-
clone 의 또 다른 문제점 중 하나는 reference field 가 final 로 선언될 경우
clone 을 override 해도 제대로 복제하기가 어렵다. 근본적인 문제점이다.
클래스가 복제 가능하도록 하기 위해서는 일부 필드의 final 키워드를 없애야 할 필요가 생긴다.
-
생성자처럼 clone 메소드에서는 복제 중인 객체의 final 이 아닌 어떤 메소드도 호출하면 안 된다.
만일 clone 에서 오버라이드된 메소드를 호출하면 이 메소드는 자신이 정의된 서브 클래스의 상태가 복제 객체에 정착되기 전에 실행될 것이므로, 복제본과 원본 객체 모두 정상이 아닌 상태가 될 가능성이 많다.
-
implements Cloneable 을 선언한 클래스가 스레드에서 안전하게 사용될 수 있게 하려면, 올바르게 동기화되어야 한다.
-
객체 복제를 하기 위해 Cloneable 을 implements 하는 모든 클래스에서는 반드시 자신의 클래스를 반환 타입으로 하는 public 메소드로 clone 을 오버라이드 해야 한다.
그리고 그 clone 메소드에서는 super.clone 을 제일 먼저 호출한 후 여타 필드들의 복제를 처리해야 한다.
필드는 일반적으로 다른 가변 객체의 참조를 갖고 있는 것으로써, 그 필드가 참조하는 객체를 복사하여 새로운 객체를 생성 후 그 객체의 참조를 복제 객체의 필드에 변경하는 것을 말한다.
그런 작업은 clone 을 재귀적으로 호출하여 처리할 수 있지만 그런 방법이 항상 제일 좋은 것은 아니다.
만일 복제할 객체의 클래스가 기본형 필드나 불변 객체의 참조만을 갖고 있다면 그런 추가 작업을 할 필요가 없다.
예를 들어 일련번호나 어떤 고유 번호(ID) 객체 생성 시간을 나타내는 필드 등의 경우는 기본형이나 불변 객체일지라도 값을 조정하는 작업이 필요하다.
-
Cloneable 을 implements 하는 클래스의 서브 클래스에서는 선택의 여지 없이 규약을 지키며 implements 해줘야 한다.
하지만 그렇지 않다면 객체를 복제하는 다른 방법을 제공하거나, 복제할 수 없도록 하는 것이 좋다.
-
객체를 복제하는 좋은 방법은 복제 생성자나 복제 팩토리 메소드를 제공하는 것이다.
복제 생성자와 복제 팩토리 메소드를 사용하는 방법은 Cloneable 과 Clone 을 사용하는 것에 비해 많은 장점을 갖고 있다.
자바 언어 영역을 벗어난 위험스러운 객체 생성 메커니즘에 의존하지 않는다는 것.
final 필드를 올바르게 사용하는지에 대해 신경 쓸 필요도 없고, 처리해야 하는 checked 예외를 불필요하게 발생시키지도 않는다.
복제된 객체의 타입 변환도 필요 없다.
ex) public MyObject( MyObject obj ){ ... }
public static MyObject newInstance( MyObject obj ){ ... }
-
Cloneable 인터페이스와 관련된 모든 문제점을 고려한다면, 다른 인터페이스에서 Cloneable 을 extends 하지 않아야 하며, 상속을 해주기 위해 설계된 클래스에서도 Cloneable 을 implements 하지 않아야 한다.
Cloneable 은 단점이 많기 때문에 배열 복제 정도로 사용하면 모를까, 일부 숙련 프로그래머들은 clone 메소드를 전혀 오버라이드 하지 않고 호출하지도 않는다.
상속을 해주기 위한 클래스를 설계할 때, 잘 동작하는 protected clone 메소드를 그 클래스에 두지 않는다면 서브 클래스에서 Cloneable 인터페이스를 제대로 구현할 수 없다는 것을 명심해야 한다.
cf) 라이브러리에서 할 수 있는 것을 클라이언트가 하도록 하지 말라!
'프로그래밍 놀이터 > 디자인 패턴, 리펙토링' 카테고리의 다른 글
[Effective Java] 클래스와 그 멤버의 접근성을 최소화하자. (0) | 2016.10.17 |
---|---|
[Effective Java] Comparable 인터페이스의 구현을 고려하자. (0) | 2016.10.14 |
[Effective Java] toString 메소드는 항상 오버라이드 하자. (0) | 2016.10.07 |
[Effective Java] equals 메소드를 오버라이드 할 때는 hashCode 메소드도 항상 같이 오버라이드 하자. (0) | 2016.10.05 |
[Effective Java] 쓸모 없는 객체 참조를 제거하자. (0) | 2016.01.04 |
댓글