제 홈페이지의 모든 글은 anti-nhn license에 따릅니다.



java.lang.Object 메쏘드 분석 4 - clone

clone 메쏘드는 객체를 복사하는 것입니다. 레퍼런스가 같은 것이 아니라 그 내용을 복사한다고 보시면 됩니다. 
equals 메쏘드를 개발자가 잘못 정의하기 쉬운 것처럼 clone 메쏘드도 잘못 정의하기가 쉽습니다.

clone의 구현 명세를 보고 넘어갑시다.
1. x.clone() != x
레퍼런스를 그대로 가져오는 것이 아니라, 객체를 복사해야 한다는 뜻입니다.

2. x.clone().getClass() == x.getClass()
clone은 객체를 복사하는 것입니다. 당연히 원본 인스턴스의 클래스와 같은 클래스의 인스턴스여야 합니다.

3. x.clone().equals(x) == true
내용을 복사해와야하기 때문에 당연히 equals 는 true를 리턴해야 합니다. 규약에는 없지만, 당연히 x.clone().hashCode() == x.hashCode()가 되어야 할 것입니다. 이건 hashCode()의 규약을 보시기 바랍니다.

4. 어떠한 생성자도 호출하면 안 된다.
new 로 만들어내면 안 됩니다. 메쏘드를 타고타고 가도 절대로 new 가 나오면 안 된답니다. 일단 super.clone()을 이용해서 객체를 만들어내야 합니다. 그러나 복사된 객체의 멤버 변수는 기존의 객체의 멤버변수에 대한 레퍼런스만을 가집니다. 멤버변수는 복사가 되지 않습니다. 그러므로, 각각의 멤버 변수에 대한 복사 작업을 해주어야 합니다.
이 부분은 논란이 좀 있습니다. java.util.HashMap을 보면, clone 메쏘드를 타고 타고 가다가 보면, new Entry가 호출됩니다.(HashMap을 짠 사람 중 하나인 Josh Bloch도 이 규약을 좀 까댑니다.)


java.lang.Object에 있는 clone은 protected로 선언이 되어 있습니다. 따라서 public으로 overide하지 않았을 경우는 외부에서 호출할 수 없습니다.

clone을 구현할 때는 반드시 java.lang.Cloneable 인터페이스를 implements해야 합니다.
Cloneable 인터페이스에는 아무런 메쏘드도 정의되어 있지 않지만 Cloneable을 구현하지 않는다면 clone 메쏘드를 호출할 때 CloneNotSupportedException를 발생시킵니다.

예제를 보여드리죠.

package objectMethod.clone;
import java.util.Date;
public class Person implements Cloneable{
    private String name;
    private Gender gender;
    private Date birth;
    public static enum Gender{
        Male,
        Female;
    }
    public Person(String name, Gender gender, Date birth) {
        this.name = name;
        this.gender = gender;
        this.birth = new Date(birth.getTime());
    }
    @Override
    public int hashCode() {
        final int PRIME = 31;
        int result = 1;
        result = PRIME * result + ((birth == null) ? 0 : birth.hashCode());
        result = PRIME * result + ((gender == null) ? 0 : gender.ordinal());
        result = PRIME * result + ((name == null) ? 0 : name.hashCode());
        return result;
    }
    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final Person other = (Person) obj;
        if (birth == null) {
            if (other.birth != null)
                return false;
        } else if (!birth.equals(other.birth))
            return false;
        if (gender == null) {
            if (other.gender != null)
                return false;
        } else if (!gender.equals(other.gender))
            return false;
        if (name == null) {
            if (other.name != null)
                return false;
        } else if (!name.equals(other.name))
            return false;
        return true;
    }
    @Override
    public Object clone() throws CloneNotSupportedException {
        Person p =  (Person)super.clone();
        p.name = name;
        p.gender = gender;
        p.birth = (Date) birth.clone();

        return p;
    }
}

일단 녹색 부분이 복사된 객체를 생성하는 부분입니다. 새로운 Person 객체를 만드며, 각 멤버 변수는 원본 객체의 멤버 변수 레퍼런스를 유지합니다. 이때, Person 클래스가 Cloneable을 implements하지 않는다면, 녹색부분에서 CloneNotSupportedException이 발생합니다. 또 보라색 마지막 부분에서 Date가 Cloneable을 implements하지 않았을 경우에도 발생할 수는 있지만, java.util.Date는 Cloneable을 implements하고 있습니다.

보라색부분은 원래 가지고 있던 멤버 변수 값을 복사해서 새로 만든 객체에 넣는 과정입니다. name은 Immutable인 String 이므로 직접 대입하면 됩니다. 또 gender는 Person.Gender가 class가 아니라 enum이기 때문에 그냥 가져와도 됩니다.(사실 String과 enum 같은 것들은 대입 안 해도 되지만 명시적으로 이런 것들이 멤버 변수로 있다는 것을 보여주기 위해서 한 겁니다.) 그러나 birth는 Date 객체이므로 복사를 해야합니다.

여기서 문제가 될 수 있는 것이 하나 있는데, birth가 Date 객체가 아니라, Date를 상속 받은 객체라면 문제가 생길 수 있습니다. 상속 받은 그 객체가 clone을 제대로 지원하지 않으면, 보라색 마지막 부분에서 CloneNotSupportedException이 발생할 수 있습니다. 그래서 생성자에서 this.birth = new Date(birth.getTime())과 같이 인자로 받은 birth를 직접 사용하지 않았습니다.(황토색 부분) 즉, Person 클래스에서 가지고 있는 birth는 Date의 하위 객체일 수 없습니다. 그러므로, java.util.Date에서 제공하는 clone을 사용할 수 있다는 것이 확실합니다.

또 중요한 사항 중 하나는 final 필드가 있을 경우 clone을 제대로 구현하기 어렵습니다. 복사해서 다시 대입할 수가 없기 때문입니다. 아래 코드를 보시면 이해가 금방 갈 겁니다.

public class Test implement Cloneable{
    private final String errorString;
    ....
    public Object clone() throws CloneNotSupportedException {
        Test test = super.clone();
        test.errorString = errorString; // <= 컴파일 에러!
    }
}

clone에서 멤버변수의 값을 반드시 새로 만들어서 대입해야 하는가는 다소 논란의 소지가 있습니다. 예를 들어, Singleton 클래스의 경우 clone을 제공하면 안 됩니다. 이런 경우는 그냥 레퍼런스를 유지하고 있는 것이 옳을 것입니다. (Singleton 클래스를 멤버 변수로 가진다는 것은 별로 바람직한 발상은 아닙니다.)

다시 정리를 하겠습니다. clone 메쏘드를 오버라이딩할 때는 다음 사항을 꼭 확인해야합니다.

1. Cloneable인터페이스를 반드시 implements한다.
2. 멤버 변수는 원시 변수 , Immutable Class 또는 enum 형식일 때는 원본의 값을 바로 대입해도 되지만, 그렇지 않을 때는 멤버변수의 clone을 호출하여 복사해야 한다. 여기서 멤버변수가 clone을 제대로 지원하지 않는다면, 그 멤버변수를 가지는 클래스도 clone을 제대로 구현할 수 없다.
3. equals 와 hashCode를 정확히 구현해야 한다. HashSet 등에 복사한 값을 넣을 때 충돌이 생기지 않도록 한다.
4. final 멤버 변수의 사용이 없는 지 확인한다. 있다면, clone에서 그 멤버 변수에 대한 값을 복사하지 않고, 레퍼런스를 유지하는 것이 올바른 구현인지 고려해야 한다.

마지막으로....
clone()이 Cloneable에 정의되지 않고 Object에 정의된 이유가 뭘까요? Cloneable에 정의가 되었더라면, CloneNotSupportedException 이라는 게 필요가 없었을텐데 말이죠. 복사해야 할 객체만 Cloneable을 구현하면 됐겠죠.
정답은 Object에 정의된 clone()이 native method라는 것입니다. 순수 자바로 clone을 정의한다는 것이 불가능하기 때문입니다. 또한 Object에 clone()이 protected 로 정의된 것은 Object에 정의된 clone()이 외부에서 직접호출 되는 일은 없을 것이라는 겁니다. 즉, clone을 지원할 클래스는 clone()을 public으로 상속을 받아서 쓰라는 것입니다. 다만 문제가 되는 것은 clone()을 구현한 클래스를 상속받은 하위 클래스가 clone을 지원하지 않는 경우에는 문제가 발생합니다. 이러한 문제가 발생하는 것은 전적으로 하위클래스를 구현때문입니다. 상위 클래스가 clone을 지원한다면, 하위 클래스는 반드시 그 부분을 고려해야 합니다.

이 글과 관련있는 글을 자동검색한 결과입니다 [?]

by 삼실청년 | 2007/12/20 20:37 | 컴터질~ | 트랙백 | 핑백(2) | 덧글(1)

트랙백 주소 : http://iilii.egloos.com/tb/4022941
☞ 내 이글루에 이 글과 관련된 글 쓰기 (트랙백 보내기) [도움말]
Linked at 건실성실착실 3실 청년! : .. at 2008/01/05 22:32

... 하는 것은 cloned1, cloned2 입니다. com은 cloned1,cloned2를 찍어내기 위한 원형일 뿐입니다.clone 메쏘드에 대한 구현은 http://iilii.egloos.com/4022941 를 참조하세요. 위의 예제에서는 코드가 너무 길어져서 hashCode()나 equals는 별도로 구현하지 않았습니다. 또 Date의 ... more

Linked at 건실성실착실 3실 청년! : .. at 2008/12/11 19:59

... 이 아예 없어도 됩니다.java.lang.Object에 clone() throws CloneNotSupportedException 이 정의 되어있습니다. (clone()에 대한 설명은 여기!) 따라서 사용자가 clone()을 정의할 때는 다음과 같이 해도 됩니다.@overridepublic Object clone(){ Ob ... more

Commented by 이현주 at 2008/03/12 01:57
정말 속 시원한 설명 감사합니다.
유용한 정보 정말 감사합니다,

:         :

:

비공개 덧글

◀ 이전 페이지          다음 페이지 ▶