이펙티브 자바 완벽 공략 1부
아이템 13. clone 재정의는 주의해서 진행하라
카테고리 : 이펙티브 자바 완벽 공략 1부
아이템 13. clone 재정의는 주의해서 진행하라
- 아이템 13. clone 재정의는 주의해서 진행하라
아이템 12. 핵심 정리1 - clone 규약
clone()과 Cloneable 제대로 이해하기 (아이템 13)
clone()은 자바에서 제공하는 객체 복제 메커니즘이지만, 실제로는 가장 오해가 많고 실수하기 쉬운 기능 중 하나다. 표면적으로는 “객체를 복사한다”는 단순한 개념처럼 보이지만, 내부 동작과 규약을 제대로 이해하지 못하면 버그와 설계 문제를 만들기 쉽다.
Cloneable의 정체: 인터페이스인데 아무 것도 없다
Cloneable은 매우 특이한 인터페이스다.
- 메서드 정의 없음
- 단순히 “복제 가능”이라는 표시 역할
- 일종의 마커 인터페이스
class MyClass implements Cloneable {
}
👉 이 인터페이스를 구현하지 않으면?
CloneNotSupportedException 발생
즉, clone()을 사용할 수 있는지 여부를 런타임에서 체크하는 스위치 역할이다.
clone()의 기본 정의
Object에 정의된 메서드:
protected Object clone() throws CloneNotSupportedException
👉 문제점
- protected → 외부에서 호출 불가
- Object 반환 → 매번 캐스팅 필요
- 체크 예외 → 사용성 떨어짐
실제 구현 시 바꿔야 할 것들
1. 접근 제어자: protected → public
@Override
public PhoneNumber clone()
👉 이유
- 외부에서 호출 가능해야 의미 있음
2. 반환 타입: Object → 자기 타입
public PhoneNumber clone()
👉 장점
- 캐스팅 제거
- 타입 안정성 확보
3. 예외 처리: 제거 or 런타임 변환
try {
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
👉 이유
Cloneable구현 시 예외 발생 가능성 거의 없음
clone()의 핵심 규약
1. 반드시 다른 인스턴스여야 한다
a != a.clone()
👉 참조값은 달라야 함
2. 같은 클래스여야 한다
a.getClass() == a.clone().getClass()
3. equals는 상황에 따라 다르다
- 대부분 true (특히 불변 객체)
- 경우에 따라 false 가능 (ID 등 변경 필요 시)
❗ 가장 중요한 규칙: 생성자 사용 금지
잘못된 코드
public PhoneNumber clone() {
return new PhoneNumber(...);
}
👉 문제 발생
왜 위험한가
상속 구조에서 깨진다.
class Item {
public Item clone() {
return new Item();
}
}
class SubItem extends Item {}
SubItem s = new SubItem();
SubItem clone = (SubItem) s.clone(); // ❌ ClassCastException
👉 이유
- 생성자는 항상 자기 클래스 타입만 생성
- 런타임 타입 유지 불가
✔ 정답: 반드시 super.clone()
@Override
public PhoneNumber clone() {
try {
return (PhoneNumber) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
👉 핵심
- 런타임 타입 유지
- 상속 구조 안전
내부 동작 이해
super.clone()은
- 생성자를 호출하지 않는다
- 메모리를 그대로 복사하는 방식 (shallow copy)
👉 그래서 빠르고, 특이한 동작을 한다
불변 객체에서의 clone()
특징
- 모든 필드가 변경 불가
- 상태 동일
👉 equals == true
예
PhoneNumber a = ...
PhoneNumber b = a.clone();
a.equals(b) == true
a != b
👉 가장 안전한 clone 사용 케이스
⚠️ 가변 객체에서의 문제
여기서부터 복잡해진다.
문제: 얕은 복사 (Shallow Copy)
class Example {
List<String> list;
}
Example clone = original.clone();
👉 결과
original.list == clone.list // 같은 객체
위험
- 한쪽 수정 → 다른 쪽 영향
- 의도치 않은 공유 상태
👉 해결 방법
- Deep Copy 직접 구현
- 내부 필드도 복제
clone()을 추천하지 않는 이유
이제 핵심을 짚어보자.
문제 요약
- 설계가 직관적이지 않음
- Cloneable이 이상한 구조
- shallow copy 문제
- 상속과 충돌
- 예외 처리 awkward
👉 그래서 현대 자바에서는
clone() 사용을 권장하지 않는다
대안
1. 복사 생성자
public PhoneNumber(PhoneNumber other)
2. 정적 팩토리
public static PhoneNumber copyOf(PhoneNumber other)
👉 장점
- 명확함
- 안전함
- 유연함
핵심 정리
Cloneable은 마커 인터페이스다- clone()은 반드시
super.clone()사용 - 생성자 사용하면 상속 깨진다
- 반환 타입은 자기 타입으로
- 불변 객체에서는 비교적 안전
- 가변 객체에서는 매우 위험
- 실무에서는 clone보다 복사 생성자 선호
한 줄 결론
clone()은 “가능은 하지만 추천하지 않는 기능” — 필요하면 정확히 알고 제한적으로 사용하라