본문 바로가기
프로그래밍 놀이터/디자인 패턴, 리펙토링

[Effective Java] clone 메소드는 신중하게 오버라이드 하자.

by 돼지왕 왕돼지 2016. 10. 10.
반응형

 [Effective Java] clone 메소드는 신중하게 오버라이드 하자.


checked 예외, clonable, clone, clone method, CloneNotSupportedException, collection, deep copy, Effective JAVA, Final, ID, implements, implements Cloneable, Interface, Java, mixin interface, override, public 메소드, Reference, reference field, soft copy, Super, [Effective Java] clone 메소드는 신중하게 오버라이드 하자., 객체, 객체 복제, 객체 타입 변환, 고유번호, 근본적 문제점, 기본형, 동기화, 문서화된 규약, 믹스인 인터페이스, 복사본, 복제, 복제 생성자, 복제 중인 객체의 final 이 아닌 메소드, 복제 팩토리 메소드, 복제본, 불변형, 상호 영향, 생성 메커니즘, 생성자, 서브 클래스, 선택 여지, 슈퍼 클래스, 스레드, 신중하게, 오버라이드, 원본, 원본 객체, 일련번호, 자바, 재귀적, 좋은 방법, 필드, 필드값


-
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) 라이브러리에서 할 수 있는 것을 클라이언트가 하도록 하지 말라!





반응형

댓글