아이템 13 clone 재정의는 주의해서 진행하라
Cloneable 인터페이스는 객체 복제를 허용하는 클래스임을 표시하는 용도다. 하지만 clone 메서드는 Cloneable이 아닌 Object 클래스에 정의되어 있으며, 기본적으로 protected로 선언되어 있어 상속을 통해 접근해야 한다. Cloneable 인터페이스에는 실제로 메서드가 존재하지 않지만, 이 인터페이스를 구현하는 클래스에서 clone을 호출할 수 있게 된다.
1. clone의 명세는 허술하다
clone의 명세는 매우 허술하며, 이를 제대로 구현하는 책임은 개발자에게 전가된다. 예를 들어, 상위 클래스에서 super.clone() 대신 생성자를 호출해 반환해도 컴파일러는 이를 문제없이 통과시킨다. 이는 clone 메서드의 동작이 일관되지 않음을 보여준다.
public class FirstRedboy implements Cloneable {
@Override
protected FirstRedboy clone() {
return new FirstRedboy();
}
}
public class SecondRedboy extends FirstRedboy {
@Override
protected FirstRedboy clone() {
return super.clone();
}
}
// 메인메소드
SecondRedboy second = new SecondRedboy();
System.out.println(second.clone() instanceof FirstRedboy); // true
System.out.println(second.clone() instanceof SecondRedboy); // false
위 예시에서 상위 클래스의 clone을 호출하면, 복제된 객체가 상위 타입으로 반환되어 하위 클래스에서는 형변환이 필요해진다.
또한, super.clone()은 CloneNotSupportedException을 던지기 때문에 이를 try-catch로 감싸야 한다. 이로 인해, clone 메서드를 사용하려면 불필요한 checked exception 처리와 형변환이 발생하는 문제가 있다.
2. clone 재정의 시 참고사항
- clone은 사실상 생성자와 같은 역할을 한다. 즉, 원본 객체를 손상하지 않으면서 복제된 객체의 불변성을 보장해야 한다.
- **Cloneable**을 구현하는 클래스에서는 clone을 재정의할 때, 접근제어자를 public으로 변경하고, 반환형을 자기 타입으로 설정하며, throws 절을 없애는 것이 권장된다. 이를 통해 clone 메서드를 더욱 사용하기 쉽게 만들 수 있다.
- 상속을 고려한 클래스는 implements Cloneable을 피해야 한다. 하위 클래스에서 스스로 복제 여부를 결정할 수 있도록 하는 것이 좋다.
@Override
public FirstRedboy clone() {
try {
return (FirstRedboy) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError(); // 이 오류는 절대 발생하지 않아야 한다.
}
}
3. clone 방식보다 복사 생성자와 복사 팩토리를 사용해라
clone 메서드는 여러 문제점을 안고 있기 때문에, 이를 사용하는 대신 복사 생성자와 복사 팩토리를 사용하는 것이 더 바람직하다.
- 복사 생성자는 동일한 타입의 객체를 인수로 받아 그 객체의 복사본을 만든다.
- 복사 팩토리는 동일한 목적을 위해 정적 팩토리 메서드로 구현될 수 있다.
public Redboy(Redboy redboy) {
this.name = redboy.name;
this.age = redboy.age;
}
public static Redboy newInstance(Redboy redboy) {
return new Redboy(redboy);
}
이 방식을 사용하면 형변환 문제, 불필요한 checked exception, 언어 모순, final 필드와의 충돌 등을 피할 수 있으며, 명확한 복사 방법을 제공하게 된다.
핵심 정리
Cloneable이 몰고 온 모든 문제를 되짚어봤을 때, 새로운 인터페이스를 만들 때는 절대 Cloneable을 확장해서는 안 되며, 새로운 클래스도 이를 구현해서는 안 된다. final 클래스라면 Cloneable을 구현해도 위험이 크지 않지만, 성능 최적화 과나점에서 검토한 후 별다른 문제가 없을 대만 드물게 허용해야 한다. 기본 원칙은 ‘복제 기능을 생성자와 팩터리를 이용하는 게 최고’ 라는 것이다. 단, 배열만은 clone 메서드 방식이 가장 깔끔한, 이 규칙의 합당한 예외라 할 수 있다.
'Java' 카테고리의 다른 글
이펙티브 자바(Effective Java) 2장 - 아이템 14 Comparable을 구현할지 고려하라 (0) | 2024.12.03 |
---|---|
이펙티브 자바(Effective Java) 2장 - 아이템 12 toString을 항상 재정의하라 (0) | 2024.12.03 |
이펙티브 자바(Effective Java) 2장 - 아이템 11 equals를 재정의하려거든 hashCode도 재정의하라 (0) | 2024.12.03 |
이펙티브 자바(Effective Java) 2장 - 아이템 10 equals는 일반 규약을 지켜 재정의하라 (0) | 2024.12.03 |
이펙티브 자바(Effective Java) 2장 - 아이템 9 try-finally보다는 try-with-resources를 사용하라 (0) | 2024.12.03 |
아이템 13 clone 재정의는 주의해서 진행하라
Cloneable 인터페이스는 객체 복제를 허용하는 클래스임을 표시하는 용도다. 하지만 clone 메서드는 Cloneable이 아닌 Object 클래스에 정의되어 있으며, 기본적으로 protected로 선언되어 있어 상속을 통해 접근해야 한다. Cloneable 인터페이스에는 실제로 메서드가 존재하지 않지만, 이 인터페이스를 구현하는 클래스에서 clone을 호출할 수 있게 된다.
1. clone의 명세는 허술하다
clone의 명세는 매우 허술하며, 이를 제대로 구현하는 책임은 개발자에게 전가된다. 예를 들어, 상위 클래스에서 super.clone() 대신 생성자를 호출해 반환해도 컴파일러는 이를 문제없이 통과시킨다. 이는 clone 메서드의 동작이 일관되지 않음을 보여준다.
public class FirstRedboy implements Cloneable { @Override protected FirstRedboy clone() { return new FirstRedboy(); } } public class SecondRedboy extends FirstRedboy { @Override protected FirstRedboy clone() { return super.clone(); } } // 메인메소드 SecondRedboy second = new SecondRedboy(); System.out.println(second.clone() instanceof FirstRedboy); // true System.out.println(second.clone() instanceof SecondRedboy); // false
위 예시에서 상위 클래스의 clone을 호출하면, 복제된 객체가 상위 타입으로 반환되어 하위 클래스에서는 형변환이 필요해진다.
또한, super.clone()은 CloneNotSupportedException을 던지기 때문에 이를 try-catch로 감싸야 한다. 이로 인해, clone 메서드를 사용하려면 불필요한 checked exception 처리와 형변환이 발생하는 문제가 있다.
2. clone 재정의 시 참고사항
- clone은 사실상 생성자와 같은 역할을 한다. 즉, 원본 객체를 손상하지 않으면서 복제된 객체의 불변성을 보장해야 한다.
- **Cloneable**을 구현하는 클래스에서는 clone을 재정의할 때, 접근제어자를 public으로 변경하고, 반환형을 자기 타입으로 설정하며, throws 절을 없애는 것이 권장된다. 이를 통해 clone 메서드를 더욱 사용하기 쉽게 만들 수 있다.
- 상속을 고려한 클래스는 implements Cloneable을 피해야 한다. 하위 클래스에서 스스로 복제 여부를 결정할 수 있도록 하는 것이 좋다.
@Override public FirstRedboy clone() { try { return (FirstRedboy) super.clone(); } catch (CloneNotSupportedException e) { throw new AssertionError(); // 이 오류는 절대 발생하지 않아야 한다. } }
3. clone 방식보다 복사 생성자와 복사 팩토리를 사용해라
clone 메서드는 여러 문제점을 안고 있기 때문에, 이를 사용하는 대신 복사 생성자와 복사 팩토리를 사용하는 것이 더 바람직하다.
- 복사 생성자는 동일한 타입의 객체를 인수로 받아 그 객체의 복사본을 만든다.
- 복사 팩토리는 동일한 목적을 위해 정적 팩토리 메서드로 구현될 수 있다.
public Redboy(Redboy redboy) { this.name = redboy.name; this.age = redboy.age; } public static Redboy newInstance(Redboy redboy) { return new Redboy(redboy); }
이 방식을 사용하면 형변환 문제, 불필요한 checked exception, 언어 모순, final 필드와의 충돌 등을 피할 수 있으며, 명확한 복사 방법을 제공하게 된다.
핵심 정리
Cloneable이 몰고 온 모든 문제를 되짚어봤을 때, 새로운 인터페이스를 만들 때는 절대 Cloneable을 확장해서는 안 되며, 새로운 클래스도 이를 구현해서는 안 된다. final 클래스라면 Cloneable을 구현해도 위험이 크지 않지만, 성능 최적화 과나점에서 검토한 후 별다른 문제가 없을 대만 드물게 허용해야 한다. 기본 원칙은 ‘복제 기능을 생성자와 팩터리를 이용하는 게 최고’ 라는 것이다. 단, 배열만은 clone 메서드 방식이 가장 깔끔한, 이 규칙의 합당한 예외라 할 수 있다.
'Java' 카테고리의 다른 글
이펙티브 자바(Effective Java) 2장 - 아이템 14 Comparable을 구현할지 고려하라 (0) | 2024.12.03 |
---|---|
이펙티브 자바(Effective Java) 2장 - 아이템 12 toString을 항상 재정의하라 (0) | 2024.12.03 |
이펙티브 자바(Effective Java) 2장 - 아이템 11 equals를 재정의하려거든 hashCode도 재정의하라 (0) | 2024.12.03 |
이펙티브 자바(Effective Java) 2장 - 아이템 10 equals는 일반 규약을 지켜 재정의하라 (0) | 2024.12.03 |
이펙티브 자바(Effective Java) 2장 - 아이템 9 try-finally보다는 try-with-resources를 사용하라 (0) | 2024.12.03 |