Effective Java 2/E, Clone을 재정의 할 때는 신중하라

|

모든 클래스의 부모인 Object 클래스는 clone() 메소드를 가지고 있다. clone()은 객체 생성의 다른 방법으로, new 연산자를 사용하지 않고 객체 생성을 하게된다.

clone() 을 재정의 하기 위해서는, Cloneable 이라는 인터페이스를 구현해야 하는데,
Cloneable 인터페이스를 보면 내용이 아무것도 없다.

package java.lang;

public interface Cloneable {
}

만약 Clonable를 구현하지 않는다면, CloneNotSupportedException 예외가 호출된다.

class ListNode implements Cloneable {
    int val;

    ListNode(int x) {
        val = x;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

이렇게 clone을 재정의하면, primitive 원소인 val의 값이 복사되고 복사된 객체가 생긴다.

하지만, clone을 재정의할 때는 주의해야 한다.

class ListNode implements Cloneable {
    int val;
    ListNode next;
    int[] array = {1,2,3};

    ListNode(int x) {
        val = x;
    }

    @Override
    protected Object clone() throws CloneNotSupportedException {

        ListNode object = (ListNode) super.clone();
        object.array = array.clone();
        return object;
    }
}

만약 array라는 필드를 가지고 있는 ListNode가 있다고 하고, clone() 을 진행하게 된다면

    ListNode clonedNode = (ListNode) node.clone();

clone()의 기본 동작은 shallow copy이기 때문에, array의 경우에는 새로 생성이 되며 copy되는 것이 아니고, 주소값을 저장하며 copy되게 된다.
이렇게 된다면 문제점은 다음과 같다.


    public static void main(String[] args) {

        ListNode node = new ListNode(1);
        ListNode node2 = new ListNode(2);
        node.next = node2;

        try {
            ListNode clonedNode = (ListNode) node.clone();

            System.out.println(node.array);
            System.out.println(clonedNode.array);

            clonedNode.array[0] = 10;

            System.out.println(node.array[0]);
            System.out.println(clonedNode.array[0]);

        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
[I@7dc36524
[I@7dc36524
10
10

복사한 노드의 array의 주소값이 같으므로, copy한 객체의 array 값을 바꿔도 원본 객체의 array에 영향을 받는것이다.
이를 해결하는 방법은 다음과 같다.

    @Override
    protected Object clone() throws CloneNotSupportedException {

        ListNode object = (ListNode) super.clone();
        object.array = array.clone();
        return object;
    }

다시 코드를 돌려보면, array의 주소가 달라져 있고 값도 다르게 찍힌다.

[I@7dc36524
[I@35bbe5e8
1
10

객체를 복사해야 하는 경우에는 Cloneable 인터페이스를 구현하는 클래스를 계승한다면 제대로 된 clone 메서드를 구현해야 한다. 하지만, 그렇지 않다면 객체를 복사할 대안을 제공하거나, 아예 복제 기능을 제공하지 않는 것이 낫다. 객체 복제를 지원하는 좋은 방법은, 복사 생성자나 복사 팩터리 메서드를 제공하는 것이 좋다.

    public Yum(Yum yum);
    public static Yum newInstance(Yum yum);