반응형
안녕하세요 돼지왕 왕돼지입니다.
오늘은 "clone 메소드는 신중하게 오버라이드 하자" 라는 주제로 이야기하고자 합니다.
이 글은 "Effective Java" 를 정리한 내용입니다.
- Cloneable 인터페이스는 복제를 허용하는 객체라는 것을 알리는 목적으로 사용하는 믹스인 인터페이스( Mixin interface ) 이다.
Cloneable 이놈은 뭐하는 놈이야?
- Clonable interface 는 아무런 추상 메소드도 가지고 있지 않다.
- Object 클래스의 protected 메소드인 clone을 사용할 것인지의 여부를 결정한다. 어떤 클래스에서 Cloneable 인터페이스 implement 하고, clone 메소드를 호출하면, 그 클래스 객체의 복제본을 만들어 반환한다. 이 때 복제 객체는 원본 객체와 같은 필드를 가지며, 필드의 값도 복사된다.
- Cloneable interface 를 implement 하지 않고, clone 을 호출하면 CloneNotSupportedException 이 발생한다. ( 정상적인 인터페이스 사용 방법과 다르다. ), 즉 이 녀석은 슈퍼 클래스의 protected clone 메소드 동작 여부를 결정한다.
- clone 은 생성자를 호출하지 않고 객체를 생성, 복제한다.
clone 도 그럼 보편적 계약이 있어?
- clone 의 보편적 계약은 내용이 빈약하고, 강제성도 낮아. ( 강제되어야 할 것들도 강제되지 않은 느낌? )
1. x.clone() != x
2. x.clone().getClass() == x.getClass()
3. x.clone().equals( x )
4. 복제를 할 때는 어떤 생성자도 호출되지 않는다.
1.2.3 요것들이 보편적 계약 내용인데 "필수 요건이 아님" 이라는 게 웃기지.
게다가 4 는 너무나도 엄격한 계약이지.
clone method 는 그럼 어떻게 override 해?
- final 이 아닌 슈퍼 클래스의 clone 메소드를 오버라이드 할 경우, 서브 클래스의 clone에서는 반드시 super.clone을 호출하여 얻은 객체를 반환해야 한다. ( 이 때 슈퍼 클래스들도 clone 을 override 했다면, 마찬가지로 super.clone 을 return 하는 형식 )
- return type 을 override 하고 있는 class 명으로 바꾸어서 return 한다. - Client 에서의 형 변환이 필요가 없다.
( 자바 1.4 이후부터 covariant return type ( 공변 반환 타입 ) 이 가능해졌다. )
( "라이브러리에서 할 수 있는 것을 클라이언트가 하도록 하지 말자!!" )
[Covariant Return Type이란?]
- 기본 object.clone() 에서는 참조하는 필드에 대해서, 그 참조값 ( 주소값 ) 을 그대로 전달하기 때문에, 이럴 경우 "복제"가 필요하다. 참조필드가 있는데 기본 clone() 으로 퉁치려 한다면, 원본 또는 복제본 중 어느 것에 변화를 주었을 때, 엉뚱한 결과를 초래하거나 NullPointException 을 초래하기 쉽다. 사실상 clone 메소드는 또 다른 생성자인 셈이다. 따라서 clone 메소드가 원본 객체에 손상을 주지 않으면서 원본과 복제 객체 간의 상호 영향도 없도록 해야 한다. 가장 쉬운 방법은 재귀적으로 clone 메소드를 호출하는 것이다. ( 참조객체에 대해 clone 을 수행해준다. )
// example code
@Override
@Override
public PhoneNumber clone(){
try{
return (PhoneNumber) super.clone();
}
catch( CloneNotSupportedException e ){
throw new AssertionError();
}
}
[Covariant Return Type이란?]
- 기본 object.clone() 에서는 참조하는 필드에 대해서, 그 참조값 ( 주소값 ) 을 그대로 전달하기 때문에, 이럴 경우 "복제"가 필요하다. 참조필드가 있는데 기본 clone() 으로 퉁치려 한다면, 원본 또는 복제본 중 어느 것에 변화를 주었을 때, 엉뚱한 결과를 초래하거나 NullPointException 을 초래하기 쉽다. 사실상 clone 메소드는 또 다른 생성자인 셈이다. 따라서 clone 메소드가 원본 객체에 손상을 주지 않으면서 원본과 복제 객체 간의 상호 영향도 없도록 해야 한다. 가장 쉬운 방법은 재귀적으로 clone 메소드를 호출하는 것이다. ( 참조객체에 대해 clone 을 수행해준다. )
// example code
@Override
@Override
public Stack clone(){
try{
Stack result = (Stack) super.clone();
result.elements = elements.clone();
return result;
}
catch( CloneNotSupportedException e ){
throw new AssertionError();
}
}
- 위와 같은 참조값 복제를 수행 할 때, 참조 변수가 final 로 지정되어 있을때는 추가적인 문제가 발생할 수 있다.
가변 객체를 참조하는 final 상수 필드의 일반적인 사용법과 달라진다는 것!!.
따라서 경우에 따라 final 키워드를 제거해야할 필요가 생긴다.
따라서 경우에 따라 final 키워드를 제거해야할 필요가 생긴다.
- super 의 clone 이 제대로 구현되어 있지 않은 경우는, 참조값이 또 다시 참조하는 것들에 대한 재귀적인 복제도 필요하다.
( deepcopy() - deepcopy 를 할 때는 재귀적 호출보다는 loop 반복처리가 권장된다.
재귀적 호출은 stack overflow 를 가져올 수 있기 때문!)
- 생성자처럼, clone 메소드에서는 복제 중인 객체의 final 이 아닌 ( 오버라이드 가능한 ) 어떤 메소드도 호출하면 안된다. clone 에서 해당 메소드를 호출하면, 자신이 정의된 서브 클래스의 상태가 복제 객체에 정착되기 전에 실행될 것이므로, 복제본과 원본 객체 모두 정상이 아닌 상태가 될 가능성이 많다.
- clone 을 public 으로 오버라이딩 할 때는 CloneNotSupportedException 을 던지지 않도록 하는 것이 좋다.
- clone()메소드는 동기화를 지원하지 않는다. 따라서 필요하다면 동기화되는 clone 메소드를 구현해주어야 한다.
- clone 을 통해 완벽히 모든 것을 다 복사하는 것이 아닌 경우도 있다.
일련 번호나 고유 번호( ID ), 객체 생성시간 등을 나타내는 필드의 경우는 ( 의도적이지 않다면 )
복제하기보다는 clone 메소드에서 따로 설정해주는 것이 좋다. clone 은 엄청 복잡하네?? clone 꼭 override 해줘야 해?
- 앞서 말했다시피, 객체 복사라던지, 특수 목적이 있을 때 외에는 안 하는 것이 좋고, 실제 현장(?) 에서도 하는 경우는 매우 드물다.
- 객체를 복제하는 다른 방법을 제공하거나, 또는 복제할 수 없도록 하는 것이 더 좋다.
특히 불변 클래스의 객체 복제를 지원하는 것은 바람직하지 않다. ( 복제본 = 원본 이기 때문. )
그럼 객체를 복제하는 다른 방법은 뭔데?
- 복제 생성자나 복제 팩토리 메소드를 제공하는 거지.
( so called Conversion Constructor, Conversion Factory Method )
( so called Conversion Constructor, Conversion Factory Method )
복제 생성자는 그냥 생성자인데, 해당 클래스 instance 를 parameter 로 받는 생성자야.
ex ) public Foo( Foo foo ){ ... }
ex ) public Foo( Foo foo ){ ... }
복제 팩토리 메소드는 복제 생성자와 유사한 static 팩토리 메소드지.
ex ) public static Foo newInstance( Foo foo ){ ... }
ex ) public static Foo newInstance( Foo foo ){ ... }
- 복제 생성자나 복제 팩토리 메소드의 장점
1. final 필드를 올바르게 사용하는지에 대한 신경 쓸 필요가 없다.
2. checked exception 을 불필요하게 발생시키지 않음.
3. 복제된 객체의 타입 변환이 필요 없음.
- Cloneable 의 단점이 많기 때문에, 배열 복제 정도로 사용하면 모를까, 일부 숙련 프로그래머들은 clone 메소드를 전혀 오버라이드 하지 않고 호출하지도 않는다. 상속을 해주기 위한 클래스를 설계할 때, 잘 동작하는 protected clone 메소드를 그 클래스에 두지 않는다면 서브 클래스에서 Cloneable 인터페이스를 제대로 구현할 수 없다는 것에 명심.!
- Cloneable 의 단점이 많기 때문에, 배열 복제 정도로 사용하면 모를까, 일부 숙련 프로그래머들은 clone 메소드를 전혀 오버라이드 하지 않고 호출하지도 않는다. 상속을 해주기 위한 클래스를 설계할 때, 잘 동작하는 protected clone 메소드를 그 클래스에 두지 않는다면 서브 클래스에서 Cloneable 인터페이스를 제대로 구현할 수 없다는 것에 명심.!
도움이 되셨다면 손가락 꾸욱~
반응형
'프로그래밍 놀이터 > 디자인 패턴, 리펙토링' 카테고리의 다른 글
[Design Pattern/Java] 클래스와 그 멤버의 접근성을 최소화하자. (2) | 2012.03.13 |
---|---|
[Design Pattern/Java] Comparable 인터페이스의 구현을 고려하자. (0) | 2012.03.13 |
[Design Pattern/Java] toString 메소드는 항상 오버라이드 하자. (0) | 2012.03.12 |
[Clean Code] 냄새와 발견법 (4) | 2012.02.28 |
[Clean Code] 클래스 (0) | 2012.02.28 |
댓글