본문 바로가기

이펙티브 자바

[이펙티브 자바] 아이템 13. clone 재정의는 주의해서 진행하라

clone() 메서드 : 객체의 복사본을 생성하기 위해 사용되는 메서드. 객체의 얕은 복사(shallow copy)를 수행한다.

얕은 복사는 객체 내부의 복합 객체에 대한 참조만을 복사하므로, 복제된 객체와 원본 객체가 같은 데이터를 가리키게 될 수 있다. 이로 인해 복제된 객체를 변경할 때 원본 객체도 영향을 받을 수 있다.

 

clone 메서드를 사용할 때 주의할 점은 얕은 복사가 되다는 점이다. 이로 인해  클래스가 가변 객체를 참조하는 순간 오류가 발생할 수 있다.

import java.util.Arrays;
import java.util.EmptyStackException;

public class Stack implements Cloneable {
    private Object[] elements;
    private int size = 0;
    private static final int DEFAULT_INITIAL_CAPACITY = 16;

    public Stack() {
        this.elements = new Object[DEFAULT_INITIAL_CAPACITY];
    }

    public void push(Object e) {
        ensureCapacity();
        elements[size++] = e;
    }

    public Object pop() {
        if (size == 0)
            throw new EmptyStackException();
        Object result = elements[--size];
        elements[size] = null; // 다 쓴 참조 해제
        return result;
    }

    private void ensureCapacity() {
        if (elements.length == size) {
            elements = Arrays.copyOf(elements, 2 * size + 1);
        }
    }

    @Override
    protected Stack clone() {
        try {
            Stack result = (Stack) super.clone();
            result.elements = elements.clone(); // 배열을 깊은 복사한다.
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(); // 이 예외가 발생할 일은 없다.
        }
    }
}

저번 아이템 7에서 사용한 예제에 가변상태를 참조하는 clone 메서드를 추가해주었다. 만약 가변상태를 참조하는 clone 메서드를 없다고 가정하고 clone 메서드가 Stack 클래스를 복제한다면 Stack인스턴스의 size 필드는 올바른 값을 갖겠지만, elements 필드는 원본 Stack 인스턴스와 똑같은 배열을 참조할것이다. 원본이나 복제본 중 하나를 수정하면 다른 하나도 수정되어 불변식이 해친다는 것이다. 이를 해결하기 위해 가변상태를 참조하는 clone 메서드 추가한다면 불변식을 해치지 않는다.

 @Override
    protected Stack clone() {
        try {
            Stack result = (Stack) super.clone();
            result.elements = elements.clone(); // 배열을 깊은 복사한다.
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError(); // 이 예외가 발생할 일은 없다.
        }

clone 메서드는 사실상 생성자와 같은 효과를 낸다. 즉, clone은 원본 객체에 아무런 해를 끼치지 않는 동시에 복제된 객체의 불변식을 보장해야 한다. 이처럼 배열을 복제할 때는 배열의 clone 메서드를 사용하라고 권장한다. 사실, 배열은 clone 기능을 제대로 사용하는 유일한 예라 할 수 있다.

 

새로운 인터페이스를 만들 때는 절대 Cloneable을 확장해서는 안 되며, 새로운 클래스도 이를 구현해서는 안된다. final 클래스라면 Cloneable을 확장해서는 안되며, 새로운 클래스도 이를 구현해서는 안된다. final 클래스라면 위험이 크지는 않지만 권장하지는 않는다.

 

결론 : 복제기능의 기본원칙은 '복제기능은 복사 생성자와 복사 팩터리 메서드 를 이용하는게 BEST ' 라는 것이다. 단, 배열만은 clone 메서드 방식이 가장 깔끔하다.

 

 

추가 설명 - 복사 생성자와 복사 팩토리 메서드란?

복사 생성자 :
복사 생성자는 해당 클래스의 인스턴스를 인자로 받는 생성자다.
이 생성자는 전달받은 인스턴스의 상태를 복사하여 새로운 인스턴스를 초기화한다.
복사 생성자는 객체의 깊은 복사를 명시적으로 제어할 수 있게 해준다.

public class MyClass {
    private int data;

    public MyClass(MyClass source) {
        this.data = source.data;
    }
}

복사 팩토리 메서드 : 
복사 팩토리 메서드는 복사 생성자와 유사한 기능을 제공하지만, 메서드를 통해 구현된다. 
이 방식은 클래스의 인스턴스를 인자로 받아, 그 인스턴스의 상태를 복사하여 새로운 인스턴스를 반환한다.
public class MyClass {
    private int data;

    public static MyClass newInstance(MyClass source) {
        return new MyClass(source.data);
    }
}