일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | |
7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | 20 |
21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 29 | 30 |
- 0.75px border
- &연산
- jwt
- entity
- 문서번호
- Websocket
- 으
- 전역변수
- TypeScript
- 당근마켓
- 컴포넌튼
- ZOOM
- TS
- 서버리스 #
- 0.5px border
- 1px border
- 0.25px border
- 10px
- github
- 클론코딩
- 데이터베이스 #try #이중
- literal
- Props
- npm
- Strict
- es6
- angular
- ES5
- 타입스크립트
- font-size
- Today
- Total
복잡한뇌구조마냥
[JAVA] Effective Java - clone 재정의는 주의해서 진행하라 (13) 본문
✅ 요약
Java에서 Cloneable 인터페이스와 clone() 메서드를 사용하면 객체 복제를 간편하게 구현할 수 있을 것처럼 보입니다. 하지만 clone()은 매우 허술하고, 설계적으로 불완전한 메커니즘이며, 잘못 사용하면 예기치 못한 동작, 불변성 훼손, 깊은 복사 실패, 스택 오버플로우 등 수많은 문제가 발생할 수 있습니다.
이 아이템에서는 clone()의 문제점과 안전하게 복제를 구현하는 방법, 그리고 대안으로 복사 생성자/팩터리 사용을 권장하는 이유를 설명합니다.
🧨 1. Cloneable의 근본적인 문제
- Cloneable은 표시(marker) 인터페이스일 뿐, 아무 메서드도 정의하지 않음
- Object.clone()은 기본적으로 얕은 복사(shallow copy) 만 수행하며, Cloneable을 구현하지 않으면 CloneNotSupportedException 발생
- clone()은 생성자를 호출하지 않고 객체를 생성하기 때문에 **불변식(invariant)**을 깨뜨릴 위험이 있음
- 부모 클래스의 clone()을 재정의하지 않으면 하위 클래스에서 super.clone() 호출 시 문제가 발생할 수 있음
📌 즉, Cloneable은 인터페이스임에도 불구하고 강제할 수 있는 계약이 없고, clone()은 동작 방식이 복잡하고 모순적입니다.
⚠️ 2. clone() 사용 시 발생할 수 있는 문제
✅ 얕은 복사로 인한 문제
- 배열이나 가변 필드가 포함된 객체는 얕은 복사로 인해 원본과 복제본이 같은 내부 객체를 공유하게 됨
- 이는 불변성을 깨고, 복제 객체를 수정하면 원본에도 영향을 줌
class Foo implements Cloneable {
int[] data;
public Foo clone() throws CloneNotSupportedException {
Foo clone = (Foo) super.clone(); // 얕은 복사
// clone.data = Arrays.copyOf(data, data.length); // 깊은 복사 필요
return clone;
}
}
✅ 재귀적 복사 시 스택 오버플로우
- 연결 리스트, 트리 등 깊은 구조를 가진 객체를 clone()으로 재귀적으로 복사하면 스택 오버플로우 위험
- 반복자(iterator)를 사용한 복사가 더 안전
✅ 3. 안전하게 clone() 구현하려면
항목 | 해야 할 일 |
인터페이스 구현 | Cloneable 구현 |
예외 처리 | CloneNotSupportedException 명시 |
접근 제한자 | clone()은 public으로 재정의해야 외부에서 호출 가능 |
반환 타입 | 클래스 자신의 타입으로 형변환 |
깊은 복사 | 모든 가변 필드를 개별적으로 복사해야 함 |
상속 관계 | 상속용 클래스는 Cloneable을 구현하지 않는 것이 좋음 |
final 클래스 | 위험이 적지만, 성능 분석 후 신중히 결정 |
📌 결론적으로 Cloneable을 구현한다면 clone()은 반드시 재정의해야 하며, 단순하게 넘길 수 있는 문제가 아닙니다.
🧼 4. 더 나은 대안: 복사 생성자와 복사 팩터리
✅ 복사 생성자
public class Person {
private final String name;
private final int age;
public Person(Person other) {
this.name = other.name;
this.age = other.age;
}
}
✅ 복사 팩터리 메서드
public static Person copyOf(Person other) {
return new Person(other);
}
복사 생성자는 인터페이스 타입을 받아서 다형성 지원, 구현이 명시적이고 예측 가능하며, 불변성 유지가 쉬움
📚 5. 컬렉션 복제의 모범 사례
모든 범용 컬렉션 구현체는 다음과 같은 생성자를 제공합니다:
List<String> newList = new ArrayList<>(originalList);
Map<String, Integer> newMap = new HashMap<>(originalMap);
이는 인터페이스 기반의 변환 생성자/팩터리로, clone()으로는 불가능한 유연한 복제 방식을 제공합니다.
❌ 6. Cloneable은 확장하지 마라
- 새로운 인터페이스를 정의할 때 절대 Cloneable을 확장하지 말 것
- 새로 만드는 클래스도 가능하면 Cloneable을 구현하지 않는 것이 좋음
- 만약 불가피하게 구현해야 한다면, 성능 분석 및 안전성 검증을 선행할 것
✅ 정리
내용 | 요역 |
Cloneable의 문제 | 계약 위반 가능성, 얕은 복사, 불변성 훼손 |
clone()의 문제 | 생성자 미호출, 예외 처리 필요, 접근 제한자 문제 |
안전한 사용법 | 깊은 복사 필수, 형변환, 예외처리, override 필요 |
대안 | 복사 생성자, 복사 팩터리 사용 권장 |
권장 방식 | Cloneable은 되도록 피하고, 명시적 복제 방식 사용 |
✍️ 마무리
clone()은 자바의 설계적 흠결 중 하나로, 실무에서는 복사 생성자나 팩터리 메서드를 통한 복제가 훨씬 안정적입니다. 특히, 가변 객체를 다룰 때는 깊은 복사가 필수이며, 재귀 복사 대신 반복자 기반 복사, 불변성 유지 등에 각별한 주의가 필요합니다.
“복제는 쉬워 보여도, 안전하게 구현하려면 훨씬 복잡하다. 그래서 차라리 생성자를 쓰는 게 낫다.”
'BE > JAVA' 카테고리의 다른 글
[JAVA] 서블릿(Servlet) (0) | 2025.08.08 |
---|---|
[JAVA] JSP ( Java Server Pages ) (0) | 2025.08.07 |
[JAVA] Effective Java - 다 쓴 객체 참조를 해제하라 (7) (3) | 2025.08.03 |
[JAVA] 반복문 간결하게 쓰는 법 - 람다와 메서드 참조로 리팩토링 (2) | 2025.07.29 |
[JAVA] 구성 ( Composition ) (0) | 2025.07.29 |