clone을 재정의할 때는 신중하라!

2015. 6. 11. 10:17Language/Java

반응형
Cloneable : Note that this interface does not contain the clone method
Even if the clone method is invoked reflectively, there is no guarantee that it will succeed.


단점 :
 기본적인 문제는 clone메소드가 없으며 Object의 clone메소드가 protected로 선언되어 있다는 것이다.
 리플렉션(reflection)을 사용하지 않고서는 Cloneable을 구현한 객체라도 clone 메소드를 호출할 방법이 없다.
 리플렉션을 사용한 호출도 실패할 가능성이 있다. 해당 객체에 호출 가능한 clone메소드가 구현되어 있으리라는 
 보장이 없기 때문.

참고내용
접근 제어자
구분
modifier
설명
접근권한
public
모든 클래스에서 접근이 가능!

protected
동일 패키지에 속하는 클래스와 하위 클래스 관계의 클래스에 의해 접근이 가능.

private
클래스 내에서만 접근이 가능.



다시 돌아와서
  • x.clone() != x   : clone()는 아래 말한것처럼 "객체를 복사한다. 참조가 아니라는 뜻.
           Creates and returns a copy of this object. The precise meaning of "copy" may depend on the class of the object.

  • x.clone().getClass() == x.getClass() : 원본 객체와 복사된 객체이므로 참! 그러나 반드시 그래야 하는 것은 아니다!

  • x.clone().equals(x) : 당연히 true이지만 반드시 그래야하는 것은 아니다.(내부 자료구조까지 복사해야 될 수도 있다.)<— 정확히 감 오지 않음.
    • victor : 오버라이딩 시에 다르게 할수도 있기 때문.
    • ace-t : equals() & hashCode() 처럼 같은 맥락의 내용으로 추정.


  • 어떤 생성자도 호출되지 않는다. : 생성자가 호출이 되지 않는다는 것은 객체가 자바에서 흔히 new해서 생성되지 않음을 뜻한다.  이펙티브자바를 보면 이 규칙에 대해서 저자는 너무 심한거 아니냐고 말하고 있다.ㅋㅋㅋ 
          특히 복사본 내부객체에서 생성자로 만든 객체를 반환하도록 clone을 구현하거나 클래스가 final로 선언되어있다면, 생성자로 만든 객체를 리턴하도록 clone을 구현할 수도 있다고 반박한다. ㅋㅋㅋ 
또한 조금 논란이 있는데 java.util.HashMap을 보면 clone()을 타고가다보면 new Entry가 호출된다.
public Object clone() {
HashMap<K,V> result = null;
try {
result = (HashMap<K,V>)super.clone();
} catch (CloneNotSupportedException e) {
// assert false;
}
if (result.table != EMPTY_TABLE) {
result.inflateTable(Math.min(
(int) Math.min(
size * Math.min(1 / loadFactor, 4.0f),
// we have limits...
HashMap.MAXIMUM_CAPACITY),
table.length));
}
result.entrySet = null;
result.modCount = 0;
result.size = 0;
result.init();
result.putAllForCreate(this);

return result;
}
아래에 보면 new Entry를 하고 있다.
private void inflateTable(int toSize) {
// Find a power of 2 >= toSize
int capacity = roundUpToPowerOf2(toSize);

threshold = (int) Math.min(capacity * loadFactor, MAXIMUM_CAPACITY + 1);
table = new Entry[capacity];
initHashSeedAsNeeded(capacity);
}

이제 clone()을 알아보고 구현해보자!
java.lang.Object에 속한 clone() !!! 
protected native Object clone() throws CloneNotSupportedException;

 위와 같이 protected로 되어있다. 그러므로 override 시에 public으로 하지 않으면 외부에서 호출이 불가능 하다.
그래서 이펙티브 자바 굵은 글씨로 “실질적으로 Cloneable 인터페이스를 구현하는 클래스는 제대로 동작하는 public clone 메소드를 제공해야한다." 라고 말한다.

clone을 구현 시 반드시 java.lang.cloneable인터페이스를 implements해야 한다.
위에서 보았던 Cloneable 인터페이스는 아무런 메소드를 정의되어있지 않지만 Cloneable을 구현하지 않는다면 clone 메소드를 호출
할 때 CloneNotSupportedException을 발생 시킵니다.

final 필드가 있으면 clone을 제대로 구현하기 어렵다. 복사를 해서 다시 대입할 수 없기 때문 입니다.
이펙티브 자바에서는 “clone의 아키텍처는 변경 가능한 객체를 참조하는 final 필드의 일반적 용법과 호환되지 않는다.” 라고 하네요.
간단한 예)
public class Test implement Cloneable{
    private final String errorString;
    ....
    public Object clone() throws CloneNotSupportedException {
        Test test = super.clone();
        test.errorString = errorString; // <= 컴파일 에러!
    }
}

비-fianl 클래스에 clone을 재정의할 때는 반드시 super.clone()을 호출해 얻은 객체를 반환해야 한다.











반응형