#11 clone 메서드는 신중하게 오버라이드 하자
clone 메서드를 언제? 어떻게 쓰는지와 대안에 대해 알아보겠습니다.
아무런 추상 메서드도 없고 잘 쓰이지 않는 생소한 인터페이스입니다.
Cloneable 인터페이스는 Object 클래스의 clone 메서드를 이용할지 여부를 결정합니다.
아래와 같이 클래스에 Cloneable 인터페이스를 구현하고, Object의 clone 메서드를 호출하면 해당 클래스의 객체의 복제본이 반환됩니다.
이때 복제본은 같은 필드와 같은 필드 값을 가지게 됩니다.
만약 클래스에 Cloneable 인터페이스를 구현하지 않고, clone 메서드를 호출하면 아래와 같이 CloneNotSupportedException 예외가 발생합니다.
하지만 단순히 Cloneable 인터페이스를 구현한다고 Clone 메서드를 이용할 수 있는 것은 아닙니다.
clone 메서드는 호출된 객체의 복제본을 생성하고 반환합니다.
아래에 보편적 계약들이 있는데, 사실 허점들이 있는 내용입니다.
x.clone()!= x
x.clone(). getClass() == x.getClass()
(오버라이드 시 서브 클래스를 반환할 수 도 있기 때문에 반드시 true는 아닙니다.)
x.clone(). equals(x)
(equals 메서드는 객체 주소를 비교하고, 오버라이드 시 구현에 따라 true가 아닐 수 있습니다.)
어떤 생성자도 호출되지 않는다.
(final 클래스는 상속이 안되기 때문에 생성자 호출을 통해 생성된 객체를 반환합니다.)
특히 생성자를 호출하지 않는다는 보편적 계약을 따르려면, final이 아닌 슈퍼 클래스의 clone 메서드를 오버라이드 할 경우, 서브 클래스의 clone에서는 반드시 super.clone 메서드를 통해 객체를 반환해야 합니다.
따라서 최상위 슈퍼 클래스인 Object 클래스의 clone 메서드를 호출하여 올바른 클래스의 객체를 반환하게 됩니다.
복제된 객체는 원본 객체 클래스에 선언된 필드와 필드 값을 가지게 됩니다.
따라서 만약 모든 필드가 기본형 데이터 값이거나 불변 객체 참조만 가지고 있다면, 다른 처리 없이 기본적으로 반환된 객체만으로 충분합니다.
1. implements Cloneable 인터페이스 구현
2. Object 클래스의 protected clone 메서드는 public으로 오버 라이딩
3. super.clone 메서드를 통해 객체를 반환
Stack 클래스의 clone 메서드를 구현한다면, 아래처럼 구현할 수 있을 겁니다.
하지만 이 구현법은 문제가 있습니다.
1. implements Clonealbe 인터페이스 구현
2. Object 클래스의 protected clone 메서드는 public으로 오버 라이딩
3. super.clone 메서드를 통해 객체를 반환
Object [] elements 필드는 원본 Stack 객체와 복제된 Stack 객체가 똑같은 배열을 참조하게 됩니다.
따라서 원본 또는 복제본 Stack 객체, 한쪽에서 이 배열을 변경하면 서로에게 영향을 주게 됩니다.
예상하지 못한 결과를 초래하거나 심지어 NullPointerException 예외를 발생시킬 수 있습니다.
이를 방지하기 위해서는 clone 메서드는 원본 객체에 손상을 주지 않고, 원본-복제본 객체 간 상호 영향이 없도록 해야 합니다.
즉, clone 메서드는 또 다른 생상자처럼 생각할 수 있습니다.
아래와 같이 가변 객체인 elements 필드는 재귀적으로 clone 메서드를 호출해서 해결할 수 있습니다.
1. implements Clonealbe 인터페이스 구현
2. Object 클래스의 protected clone 메서드는 public으로 오버 라이딩
3. super.clone 메서드를 통해 객체를 생성
4. 가변 객체를 재귀적으로 clone 메서드를 통해 객체를 생성
5. 객체 반환
가변 객체라도 clone을 재귀적으로 호출한다고 다 해결되는 것은 아닙니다.
아래 코드를 보면, 데이터(key와 value 쌍으로 된) bucket을 배열로 저장하는 HashTable 클래스의 clone 메서드를 재귀적으로 작성할 수 있습니다.
원본 객체와 복제본 객체 모드 자신의 bucket 배열을 따로 가지고 있지만, 배열의 각 요소들은 원본-복제본 모두 동일한 참조를 하게 됩니다.
따라서 가변 객체 참조와 같이 예측할 수 없는 결과를 유발할 수 있습니다.
따라서 아래처럼 일일이 모두 복사해야 합니다.
1. implements Clonealbe 인터페이스 구현
2. Object 클래스의 protected clone 메서드는 public으로 오버 라이딩
3. super.clone 메서드를 통해 객체를 생성
4. 필드가 참조하는 객체를 복사하여 새로운 객체를 생성
4. 참조하는 객체를 복제 객체의 필드에 반영(deep copy)
객체 참조 필드가 참조하는 인스턴스 자체를 복사하여 새로운 인스턴스를 생성
5. 객체 반환
이처럼 clone 메서드를 구현하기 위해서는 복제본의 필드에 따라 고려해야 할 점이 많습니다.
따라서 굳이 clone 메서드를 구현하기보다는 다른 대안법들을 통해 객체를 복제할 수 도 있습니다.
clone 메서드를 대체할 두 가지 방법이 있는데, 그냥 앞에 "복제"라는 단어가 붙은 생성자와 팩토리 메서드입니다.
1. 복제 생성자
public SeungHyun(SeungHyun sh);
2. 복제 팩토리 메서드
public static SeungHyun(SeungHyun sh);
이 방법들은 clone 메서드에 비해 여러 장점이 있습니다.
Cloneable 인터페이스 구현을 안 해도 됨
final 필드에 대해 신경 쓰지 않아도 됨
CloneNotSupportedException 체크 안 해도 됨
복제된 객체의 타입 변환 필요 없음
Effective 책에서도 clone 메서드를 직접 구현하기에는 단점이 많기에, 그냥 오버라이드 하지도 말고 호출하지도 말자고 기재되어 있습니다.
쓰지 맙시다ㅠ