티스토리 뷰
* 이펙티브 자바 2/E를 읽고 공부하기 위해 기록한 게시글입니다.
11. clone을 재정의할 때는 신중하라
Clonable 인터페이스는 어떤 객체가 복제(clone)을 허용한다는 사실을 알리기 위해 만들어진 믹스인(mixin) 인터페이스.
public interface Cloneable {
}
실제로 Cloneable 인터페이스를 보면 아무 내용도 없다는 것을 확인할 수 있다.
- Object 클래스 내 protected clone() 메서드가 어떻게 동작할 지 규정하는 용도로 사용되어진다.
어떤 클래스가 Cloneable을 구현한다면, clone 메서드는 해당 객체를 필드 단위로 복사한 객체를 반환한다.
<-> Cloneable 구현을 하지 않았다면 clone 메서드 사용 시 CloneNotSupportedException 예외를 반환한다.
=> 인터페이스의 사용 목적과는 부합하지 않는 굉장히 묘한 방법으로 사용되고있는 인터페이스.
(인터페이스를 구현한다는 것은 클래스가 무슨 일을 할 수 있는지를 클라이언트에게 알려주는 것이다. 그런데 Cloneable의 경우는 상위 클래스의 protected 멤버가 어떻게 동작할지를 규정하고 있다.)
@HotSpotIntrinsicCandidate
protected native Object clone() throws CloneNotSupportedException;
Object 클래스에 정의된 clone() 메서드는 보다시피 접근 제한자로 protected가 걸려있다. 그렇기에 Object 클래스 위치인 java.lang 패키지 내 클래스 혹은 Object와 상속관계에 있는 클래스들만 접근할 수 있다.
- Object 클래스는 모든 클래스의 부모이며 상속관계에 있다. 그렇기 때문에 자식 클래스에서 접근할 수 있음.
Cloneable 인터페이스를 구현하는 클래스는 제대로 동작하는 public clone 메서드를 제공해야 한다.
- Object 클래스의 clone 메서드를 오버라이딩 하면서 접근제한자를 public으로 넓혀주면 된다.
* 오버라이딩 시, 부모 클래스에서 선언한 접근제한자와 동일하거나, 더 범위가 넓은 제한자를 사용할 수 있다.
(더 좁힐 수는 없다!)
public class User implements Cloneable {
private String name;
private String birth;
private String tel;
@Override
public User clone() {
try {
return (User) super.clone();
} catch (CloneNotSupportedException e) {
throw new AssertionError();
}
}
}
- final이 아닌 클래스에 clone을 재정의할 때는 반드시 super.clone을 호출해 얻은 객체를 반환해줘야 한다.
- 어떤 클래스의 모든 상위 클래스가 이 규칙을 따른다면 super.clone은 결국 Object의 clone 메서드를 호출하게 되고 원하는 클래스의 객체가 만들어지게 된다.
- 오버라이딩 된 clone 메서드의 리턴타입을 보면 Object가 아니라 하위 클래스인 User 클래스로 형변환을 해서 리턴하는 것을 확인할 수 있다.
-> 공변 반환형(Covariant return type)의 도입으로, 재정의된 메서드의 반환값 자료형은 재정의되는 메서드의 반환값 자료형의 하위 클래스가 될 수 있다. 덕분에 클라이언트는 별도로 형변환을 해주지 않아도 되고, 객체에 대한 더 많은 정보를 제공해 줄 수 있게 되었다. (라이브러리가 할 수 있는 일을 클라이언트에게 미루지 말라)
====
<java의 접근제한자>
private
- 해당 클래스에서만 접근 가능
default
- 접근 제한자를 붙이지 않는 경우 해당
- 해당 패키지 내에서만 접근 가능
protected
- 해당 패키지의 클래스
- 해당 클래스를 상속받은 다른 패키지의 클래스
public
- 어디든지 가능
====
clone 메서드의 일반 규약은 다음과 같다.
1. x.clone() != x
= 복사된 객체와 원본 객체는 서로 다르다 (동일성 비교)
2. x.clone().getClass() == x.getClass()
- 일반적으로 참이지만, 반드시 그래야만 하는 것은 아니다.
3. x.clone().equals(x)
- 일반적으로 참이지만, 반드시 그래야만 하는 것은 아니다.
* 변경 불가능 클래스라면 clone 메서드를 지원할 필요가 하나도 없다. (복사본과 원본이 차이가 없기 때문)
객체 복제를 지원하는 가장 좋은 방법은 clone 메서드보다는 복사 생성자 혹은 복사 팩토리를 제공하는 것.
- Clonable/clone 메서드를 사용하는 것은 문제 투성이이다.. 계승 목적으로 설계되는 클래스는 Cloneable을 구현하면 안된다. 단점이 매우 많다.
- 반면 복사 생성자/팩토리를 사용하면 불필요한 예외처리도 필요없고, 형변환도 필요없고, 문서화 되어있지도 않고 어려운 규약에 얽매이지도 않는다.
public static User newInstance(User user) {
User copy = new User();
....
return copy;
}
- 위와 같이 메서드를 하나 만들어서 사용하라는 것. 굳이 요상한 인터페이스 상속도 필요없고 깔끔하다.
결론. 객체 복제가 필요하다면 복사 생성자/팩토리를 사용하자.
(+) clone 메서드의 얉은 복사와 깊은 복사
얉은 복사(Shallow copy)
- 주소 값을 복사한다
- 복사된 객체가 변경된다면 원본 객체도 영향을 받는다.
깊은 복사(Deep copy) - 기본
- 실제 값을 복사한다
- 복사된 객체가 변경되어도 원본 객체는 영향이 없다.
- Object.clone 메서드는 깊은 복사를 지원한다. (기본 자료형에 한함)
++) 배열을 복제하는 여러가지 방법들
1. Object.clone()
2. Arrays.copyOf()
3. Arrays.copyOfRange()
4. System.arraycopy()
* 객체 배열은 Object.clone, Arrays.copyOf 를 통한 깊은 복사가 되지 않는다. 원본과 복사본 모두 똑같은 객체를 원소로 갖는다. -> 반복문을 돌면서 직접 new 키워드를 이용해 객체를 새로 넣어줘야 한다.
* 2차원 이상의 배열 역시 clone 등 메서드를 이용한 깊은 복사가 되지 않는다. 이 역시 원소가 배열의 주솟값을 갖고있기 때문,,
'JAVA' 카테고리의 다른 글
클래스와 멤버의 접근 권한을 최소화하자 (0) | 2022.07.22 |
---|---|
자연적 순서가 있는 객체는 Comparable 구현 (0) | 2022.07.22 |
toString은 재정의해서 사용하자 (0) | 2022.07.21 |
equals를 재정의할 때는 hashCode도 같이 재정의하자 (0) | 2022.07.18 |
equals 메서드 정의는 일반 규약을 따르자 (0) | 2022.07.18 |