Effective Java | #13. clone 재정의는 주의해서 진행하라

보람·2022년 5월 14일
0

Effective-Java

목록 보기
14/25

Cloneable

  • 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스(item-20)
  • but, 의도한 목적을 제대로 이루지 못함
    • clone메서드가 Cloneable 에 선언되지 않고 Object에 선언됐고 protected임
      • 믹스인 인터페이스는 부모클래스가 되지 않으면서 다른 클래스가 해당 클래스의 메서드를 사용할 수 있는 것인데 protected(상속된)에 interface(Cloneable)가 아닌 Object에 선언됨
      • 따라서 빈곽인 Cloneable 을 구현(implements)하는 것만으로는 clone 메서드 호출 X

메서드 1도 없는 Cloneable 이 하는일은?

(깨끗)

(Object,protected clone 메서드)

  • Object의 protected 메서드인 clone 동작 방식 결정
    • Cloneable 구현된 클래스의 인스턴스에서 clone 호출하면 복사 객체 반환
    • 구현이 안된 클래스에서 clone 호출한다면 CloneNotSupportedException 던짐
  • Cloneable을 구현한 클래스는 clone 메서드를 public으로 제공하면서 당연히 복제가 될 것이라고 기대하며 이 기대를 만족시키면 모순적인 매커니즘이 탄생됨
    • 생성자를 호출하지 않고도 객체를 생성할 수 있게 됨

허술한 clone 메서드 규약

다음에 나오는 규약들은 모두 참이다.

  • x.clone() != x
  • x.clone().getClass() == x.getClass()
  • x.clone().equals(x) : 필수는 아님
  • x.clone().getClass() == x.getClass() , super.clone() 반환전 타입 수정

final 필드 용법과 충돌

  • Cloneable 아키텍처는 '가변 객체를 참조하는 필드는 final로 선언하라'는 일반 용법과 충돌
    • 복제가능 클래스를 만들기 위해 일부 필드에서 final 한정자를 제거해야 할 수도 있음
    • final 필드는 새로운 값 할당 X

clone 메서드 재정의

@Override public PhoneNumber clone() {
    try {
        return (PhoneNumber) super.clone(); //공변반환타이핑
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();  // 일어날 수 없는 일이다.
    }
}
  • 모든 필드는 원본 필드와 똑같은 값을 갖는다(super.clone())
  • 쓸데 없는 복사를 지양하기에 불변클래스는 clone메서드 제공하지 않는 것이 좋음
  • 공변반환타입핑 : Object의 clone 메서드는 Object를 반환하지만 PhoneNumber의 clone메서드는 PhoneNumber를 반환하게 됨
  • CloneNotSupportedException : PhoneNumber가 Cloneable을 구현하기 때문에 이는 비검사 예외 코드(item-71)
  • public인 clone 메서드에서는 throws 절을 없애야 함

    Cloneable을 구현하는 모든 클래스는 clone을 재정의, 접근 제한자는 public, 반환 타입은 클래스 자신으로

배열은 clone에 합당

  • clone 메서드는 사실상 생성자와 같은 효과를 낸다. 즉, clone은 원본 객체에 아무런 해를 끼치지 않는 동시에 복제된 객체의 불변식을 보장해야 함
  • 불변식을 보장해야하기 때문에 clone메서드에서 super.clone으로 객체를 가져온 후 새로운 복사 객체를 만들어서 반환해야 한다.
    • 전체를 복사하는 deepCopy보다는 반복자를 써서 순회하는 방향으로 복사를 한다.

복사생성자, 복사팩터리

복사 생성자 : 단순히 자신과 같은 클래스의 인스턴스를 인수로 받는 생성자

public Yum(Yum yum) { ... };

복사 팩터리 : 복사 팩터리는 복사 생성자를 모방한 정적 팩터리(item-1)

public static Yum new Instance(Yum yum) { ... };
  • Cloneable이 구현된 클래스를 확장한다면 clone 을 잘 작동하도록 구현해야 하고 구현된게 아니라면 복사 생성자 & 복사 팩터리라는 더 나은 객체 복사 방식을 제공 가능
  • 엉성한 규약에 기대지 않으며 정상적인 final 필드 용법과도 충돌 X
  • 불필요한 예외 검사를 던지지 않음
  • 형변환 필요 X

핵심 정리

  • Cloneable이 몰고 온 모든 문제를 보면.. 새로운 인터페이스 구현시 Cloneable을 확장하지 말고 새로운 클래스도 이를 구현해서는 X
    • 위험이 적은 final 클래스도 성능 최적화 관점에서 검토 후 문제가 없다는 판단하에 드물게 허용할 것(item-67)
  • 복제 기능은 생성자와 팩터리를 이용할 것
  • 배열만은 clone 메서드 방식이 가장 깔끔하고 합당
profile
백엔드 개발자

0개의 댓글