티스토리 뷰
Cloneable 인터페이스의 문제점
Cloneable 인터페이스는 클래스가 '복제가능한' 특성을 표현하기 위해 정의되었다. 이 인터페이스는 이렇게 정의되어 있다
public interface Cloneable {
}
?????
그렇다 문제는 Cloneable 인터페이스에 동작을 표현하는 메서드가 정의되어 있지 않다는 것이다. 그렇다면 Cloneable 인터페이스의 동작은 어디에 정의되어 있을까?
public class Object {
@IntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
...
}
clone 메서드는 Object 클래스에 정의되어 있다. 심지어 protected 형태로 존재한다. 그래서 Cloneable 인터페이스를 구현한 구현체는 그 구현만으로는 clone 메서드를 호출할 수 없다. Cloneable 인터페이스는 Object 클래스에 정의된 clone 메서드의 동작방식을 결정하는 역할을 수행한다. Cloneable 인터페이스를 구현한 클래스의 인스턴스에서 clone 메서드를 호출하면 그 객체의 필드들을 하나씩 복사한 인스턴스를 반환하며, 그렇지 않은 클래스의 인스턴스에서 호출하면 CloneNotSupportedException 을 던진다.
Cloneable 인터페이스의 모순
인터페이스를 구현한다는 것은 일반적으로 해당 클래스가 그 인터페이스에서 정의한 기능을 제공한다고 선언하는 행위를 의미한다. 그런데 Cloneable 인터페이스의 경우에는 상위클래스에 정의된 protected 메서드의 동작방식을 변경하는 행위로 정의되었다.
일반적인 인터페이스 규약을 생각한다면, 사용자는 Cloneable 인터페이스를 구현한 클래스가 clone 메서드를 public 가시성으로 제공하면서 인스턴스의 복제기능을 제공할 것이라고 기대할 것이다. 그런데 이 규약을 지키면 자바 언어 차원에서의 큰 모순이 하나 생긴다
생성자를 호출하지 않고도 객체를 생성할 수 있게 된다
Cloneable 인터페이스를 구현할 때 유의해야 할 점
제대로 동작하는 clone 메서드를 구현할 때에는 해당 클래스가 가변 상태를 참조하는 지에 유의해야 한다. 반대로 생각하면 불변 클래스는 굳이 clone 메서드를 제공하지 않는게 좋다는 의미이기도 하다. 가령 휴대폰을 소유한 사람 클래스가 존재한다고 가정해보자.
public class Person {
private Phone phone;
}
위 Person 클래스의 clone 메서드는 내부에 Phone 클래스 인스턴스의 복제로직을 반드시 호출해 주어야 한다. 그렇지 않으면 사람을 복제했을 때, 두 사람의 인스턴스가 동일한 휴대폰을 소유한 형태가 만들어질 수 있다. 이 때 phone 필드가 final 로 선언되어 있다면 다음과 같은 방식의 clone 재정의는 동작하지 않게 된다. 복제할 수 있는 클래스를 만들기 위해 final 한정자를 제거해야 할 수도 있다.
public class Person {
private final Phone phone;
public Person clone() throws CloneNotSupportedException {
Person person = (Person) super.clone();
person.phone = phone.clone(); // final 선언으로 재할당 불가!
return person;
}
}
멤버 필드가 단일 인스턴스가 아닌 배열과 같은 형태의 자료구조로 구성되어 있다면, 더더욱 조심해야 한다. 자료구조를 복사했다고 생각했을 수 있으나, 자료구조를 구성하는 인스턴스들이 모두 복사되지 않았을 수 있다.
Cloneable 인터페이스와 생성자
Cloneable 인터페이스는 클래스의 새로운 인스턴스를 생성하는 기능을 제공하므로, 생성자 또는 팩토리 메서드와 비슷한 특성을 갖는다. 생성자를 작성할 때에는 주의해야 할 점이 있다. 바로 생성자에서 재정의될 수 있는 메서드를 호출하지 말아야 한다는 것이다. (아이템 19) 간략하게 설명하자면 생성자의 로직은 인스턴스를 만드는 과정에 있기 때문에 하위 인스턴스의 동작이 기술될 수 있는 overridable 한 메서드를 호출해서는 안된다. 클래스의 인스턴스화가 완료되지 않은 상태에서 로직을 호출하는 것은 인스턴스가 불완전한 상태에서 상태변경을 시도하는 것이므로 매우 위험하다.
따라서 상속용 클래스는 Cloneable 인터페이스를 구현해서는 안된다. 명시적으로 ClassNotSupportedException throwing 로직을 작성해서 실수를 막는 것도 좋은 방법이다. 불가피하게 Cloneable 인터페이스를 구현한 클래스를 확장해야 한다면 어쩔 수 없이 clone 메서드가 잘 작동하도록 유의해서 구현해야 한다.
정리
새로운 인터페이스를 만들때는 Cloneable 인터페이스를 확장해서는 안된다. 또한 클래스에서 Cloneable 인터페이스를 작성할 때에는 매우 신중해야 한다. 복제기능이 필요하다면 우선 생성자와 팩토리를 활용할 방법을 생각해보자. 배열에 대해서는 clone 메서드 방식이 깔끔하므로 메서드 사용이 권장된다.
'프로그래밍 > Effective Java' 카테고리의 다른 글
아이템 15. 클래스와 멤버의 접근 권한을 최소화하라 (0) | 2024.08.25 |
---|---|
아이템 14. Comparable 을 구현할 지 고려하라 (1) | 2024.08.18 |
아이템 12. toString을 항상 재정의하라 (1) | 2024.07.14 |
아이템 11. equals를 재정의하려거든 hashCode도 재정의하라 (1) | 2024.07.14 |
아이템 10. equals는 일반 규약을 지켜 재정의하라 (0) | 2024.07.07 |
- Total
- Today
- Yesterday