이펙티브 자바 완벽 공략 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()은 “가능은 하지만 추천하지 않는 기능” — 필요하면 정확히 알고 제한적으로 사용하라



© 2020. All rights reserved.

SIKSIK