Cloneable 인터페이스를 구현하는 모든 클래스는 clone을 재정의해야 한다
- 이때 접근 제한자는 public으로, 반환 타입은 클래스 자신으로 변경한다.
- 이 메서드는 가장 먼저
super.clone
을 호출한 후 필요한 필드를 전부 적절히 수정한다. - 일반적으로 이 말은 그 객체의 내부
깊은 구조
에 숨어 있는 모든 가변 객체를 복사하고, 복제본이 가진 객체 참조 모두가 복사된 객체들을 가리키게 함을 뜻한다. - 이러한 내부 복사는 주로 clone을 재귀적으로 호출해 구현하지만, 이 방식이 항상 최선인 것은 아니다.
- 기본 타입 필드와 불변 객체 참조만 갖는 클래스라면 아무 필드도 수정할 필요가 없다.
- 단, 일련번호나 고유 ID는 비록 기본 타입이나 immutable일지라도 수정해줘야 한다.
위의 모든 작업이 꼭 필요할까?
- 다행히도 이처럼 복잡한 경우는 드물다.
- cloneable을 이미 구현한 클래스를 확장한다면 어쩔 수 없이 clone을 잘 작동하도록 구현해줘야 한다.
- 그렇지 않은 상황에서 복사 생성자와 복사 패터리라는 더 나은 객체 복사 방식을 제공할 수 있다.
- 복사 생성자란 단순히 자신과 같은 클래스의 인스턴스를 인수로 받는 생성자를 말한다.
복사 생성자
1
public Yum(Yum yum){...};
복사 팩터리
1
public static Yum newInstance(Yum yum){...};
- 복사 생성자와 그 변형인 복사 팩터리는 cloneable/clone 방식보다 나은 면이 많다.
언어 모순적이고 위험천만한 객체 생성 메커니즘(생성자를 쓰지 않는 방식)을 사용하지 않으며, 엉성하게 문서화된 규약에 기대지 않고, 정상적인 final 필드 용법과도 충돌하지 않으며, 불필요한 검사 예외를 던지지도 않고, 형변환도 필요하지 않다.
- 또한 해당 클래스가 구현한 ‘인터페이스’타입의 인스턴스를 인수로 받을 수 있다.
예컨대 관례상 모든 범용 컬렉션 구현체는 Collection이나 Map 타입을 받는 생성자를 제공한다.
- 인터페이스 기반 복사 생성자와 복사 팩터리의 더 정확한 이름은
변환 생성자(converstion constructor)
,변환 팩터리(conversion factory)
다. - 이들을 활용하면 클라이언트는 원본의 구현 타입에 얽매이지 않고 복제본의 타입을 직접 선택할 수 있다.
- 예를 들어 HashSet 객체 s를 TreeSet 타입으로 복제할 수 있다. clone으로는 불가능한 이 기능을 간단히
new TreeSet<>(s)
로 처리 할 수 있다.
- 예를 들어 HashSet 객체 s를 TreeSet 타입으로 복제할 수 있다. clone으로는 불가능한 이 기능을 간단히
핵심 정리: Cloneable이 몰고 온 모든 문제를 되짚어봤을 때, 새로운 인터페이스를 만들 때는 절대 Cloneable을 확장해서는 안 되며, 새로운 클래스도 이를 구현해서는 안 된다. final 클래스라면 Cloneable을 구현해도 위험이 크지 않지만, 성능 최적화 관점에서 검토한 후 별다른 문제가 없을 때만 드물게 허용해야 한다.(아이템67) 기본 원칙은 복제 기능은 생성자와 팩터리를 이용하는 것이 ‘최고’라는 것이다. 단 배열만은 clone 메서드 방식이 가장 깔끔한, 이 규칙의 합당한 예외라 할 수 있다.