13. clone 재정의는 주의해서 진행하라
Cloneable의 문제점
Mixin 인터페이스의 실패:
clone() 메서드가 선언된 곳
Object접근제어자
protected외부에서 직접 호출 불가
리플렉션은 불안정
이런 이유들로
Cloneable구현만으로는 복제 기능 활용 불가
널리 쓰이는 방식:
문제점에도 불구하고
Cloneable방식은 여전히 널리 사용되므로, 개발자는 그 동작 방식과 주의사항을 숙지할 필요가 있습니다.
Cloneable 인터페이스의 특이한 역할
메서드 없는 인터페이스
clone()동작 방식:Cloneable구현 클래스의 인스턴스에서 모든 필드를 복사한 객체 반환구현하지 않은 경우
CloneNotSupportedException
인터페이스의 역할 재정의:
인터페이스의 일반적인 역할 : 정의 기능 제공 선언
Cloneable은 상위 클래스(Object)의protected메서드 동작 방식을 변경
따라 하지 말 것
Object.clone() 메서드 명세의 허술함
모호한 복사 정의:
Object.clone()명세는 "복사"의 정확한 의미를 클래스의 구현에 의존.
느슨한 일반 규약:
x.clone() != x,x.clone().getClass() == x.getClass()x.clone().equals(x) == true
super.clone()관례:clone()메서드 내에서super.clone()호출은 관례, 강제 사항 X.하위 클래스의 복제 기능 손상 위험
클래스 종류별 clone() 구현 방식
final 클래스:
super.clone()호출 불필요,Cloneable구현 불필요생성자를 직접 호출하여 복제하는 것이 효율적일 수 있습니다.
상속 가능한 클래스:
super.clone()호출 (관례)깊은 복사 구현 필요 (가변 객체 필드)
공변 반환 타이핑 (Covariant Return Typing)
하위 클래스 반환 타입:
Java 5부터 공변 반환 타이핑 지원으로, 하위 클래스에서
clone()재정의 시 반환 타입을 하위 클래스 타입으로 지정 가능합니다.
형변환 불필요:
클라이언트 코드에서 불필요한 형변환을 줄여줍니다.
CloneNotSupportedException 예외 처리 비판
검사 예외의 부적절성:
CloneNotSupportedException은 검사 예외이지만,Cloneable구현 시super.clone()은 항상 성공 하므로 비검사 예외로 설계되었어야 합니다.
불필요한 예외 처리:
Cloneable구현 클래스에서super.clone()호출 시 불필요한try-catch블록이 필요합니다.
깊은 복사 구현 방법
재귀적 clone() 호출
가변 객체가 배열 또는 컬렉션인 경우, 내부 요소에 대해 재귀적으로
clone()을 호출하여 깊은 복사를 구현
반복문:
복잡한 자료구조의 경우 반복문을 사용하여 깊은 복사를 구현
고수준 API 활용:
super.clone()으로 얕은 복사 후, 복제본 객체의 필드를 초기화하고 원본 객체의 상태를 복제하는 고수준 메서드를 호출하여 깊은 복사를 구현HashTable의
put(key, value)메서드 활용
저수준 처리보다 낮은 성능
전체 Cloneable 아키텍처와 어울리지 않는 방식
final 필드와 Cloneable의 충돌
final 필드의 제약:
final필드는 생성자에서만 값 할당이 가능하므로,clone()메서드에서 final 필드의 깊은 복사가 어려울 수 있습니다.
final 제약 완화:
복제 가능한 클래스를 만들기 위해 일부
final제약자를 제거해야 할 수도 있습니다단, 원본과 복제본이 가변 객체를 공유해도 안전한 경우 제외
재정의 가능한 메서드 호출 주의
생성자와 clone()의 유사점:
생성자와 마찬가지로
clone()메서드 내에서 재정의 가능한 메서드 호출을 피해야 합니다.
하위 클래스 상태 교정 문제:
clone()이 하위 클래스에서 재정의한 메서드를 호출하면, 하위 클래스는 복제 과정에서 자신의 상태를 제대로 설정하지 못할 수 있습니다.
해결 방안:
복제 관련 메서드는
final또는private으로 선언하여 재정의를 막아야 합니다.
상속을 고려한 Cloneable 처리
상속용 클래스가 Cloneable 구현을 피하는 경우
하위 클래스가 필요에 따라 복제 기능을 추가
복제에 대한 어떤 가정도 하지 않기 때문에 더 안전한 설계
상속을 고려한 클래스는
Cloneable구현을 피하는 것이 좋습니다.
Object 방식 모방
상위 클래스에서
Object처럼protected clone()메서드를 제공하고CloneNotSupportedException을 던지도록 선언하위 클래스에서 Cloneable을 구현하여 복제 기능을 선택적으로 추가 가능
clone() 퇴화
clone()을 동작하지 않도록 구현final로 선언하여 하위 클래스에서 재정의를 방지
스레드 안전 클래스와 Cloneable
동기화 필요
Cloneable을 구현한 스레드 안전 클래스는clone()메서드 역시 동기화
Object.clone() 비동기화
Object.clone()은 동기화를 고려하지 않았으므로,clone()재정의 시 명시적인 동기화 처리가 필요
Clone 재정의 요약
필수 재정의:
Cloneable구현 클래스는 반드시clone()을 재정의.접근 제한자:
public반환 타입: 클래스 자신
구현 내용:
super.clone()호출 후 필요한 필드 수정 (깊은 복사)
깊은 복사
객체 내부의 모든 가변 객체를 복사하고, 복제본이 복사된 객체를 참조하도록 구현
기본 타입/불변 객체 필드:
수정 불필요 (단, 일련번호, 고유 ID 등은 수정 필요)
Cloneable 대안: 복사 생성자와 복사 팩토리
복사 생성자:
동일 클래스 인스턴스를 인수로 받는 생성자
명시적 초기화
모든 필드에 대한 초기화를 직접 제어
final 필드 호환
생성자 내에서 final 필드 초기화 가능 (Cloneable은 final 필드 복제 불가)
계층 구조 지원
상속 시 하위 클래스에서 super() 호출로 기반 클래스 복제 가능
복사 팩토리:
복사 생성자를 모방한 정적 팩토리 메서드
Cloneable 대비 결정적 장점
특성Cloneable복사 생성자/팩토리객체 생성 제어
JVM 의존적
생성자 직접 제어
예외 처리
CheckedException 발생
예외 불필요
final 필드
복제 불가
완벽 지원
타입 안정성
형변환 필요
타입 보장
인터페이스 활용
구현 클래스에 종속
추상 타입으로 복제 가능
상속 구조
super.clone() 관리 어려움
명시적 super() 호출
결론
Cloneable 확장/구현 지양:
새로운 인터페이스 확장 시
Cloneable사용 금지새로운 클래스
Cloneable구현 지양
final 클래스 예외적 허용:
final클래스의Cloneable구현은 성능 최적화 관점에서 신중히 고려 (위험성 적음)
복사 생성자/팩토리 권장:
객체 복사 기능 구현 시 복사 생성자 또는 복사 팩토리 사용 권장
배열 clone() 예외 : 배열 복사는
clone()메서드 방식이 가장 적합
Last updated