티스토리 뷰

* 이펙티브 자바 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 등 메서드를 이용한 깊은 복사가 되지 않는다. 이 역시 원소가 배열의 주솟값을 갖고있기 때문,,

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함