티스토리 뷰

13. clone 재정의는 주의해서 진행하라

Object는 protected 메소드인 clone을 가지고 있다.

이 메소드를 재정의 하기 위해서는 Cloneable interface를 구현해야한다.

Cloneable interface에는 어떠한 메소드도 정의 되어 있지 않지만,

이를 구현한 클래스에서 clone을 호출하면 해당 클래스의 필드들을

하나하나 복사한 인스턴스를 새로 반환해 준다.

필드들이 primitive 타입만 존재한다면 Cloneable이 만들어 준 clone을 그대로 사용해도 되지만

reference 타입의 필드가 존재하면 deep copy가 이루어지지 않아 clone을 재정의 해주어야한다.

public final class PhoneNumber implements Cloneable {
   private final short areaCode, prefix, lineNum;

    ...

    @Override public PhoneNumber clone() {
            try {
                return (PhoneNumber) super.clone();
            } catch (CloneNotSupportedException e) {
                throw new AssertionError();  // 일어날 수 없는 일이다.
            }
        }
    ...
}

Cloneable을 구현한 PhoneNumber 클래스는 객체 내부에 primitive 타입만을 가진다.

이는 편하게 clone을 사용하면 된다.

그러나 가변 레퍼런스 타입을 필드로 지니는 클래스에서 사용할 때는 문제가 발생한다.

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

위와 같은 필드를 지니는 클래스에서 super.clone()을 호출하게 되면 Object[]의 레퍼런스를 그대로 복사하여 shallow copy가 발생한다.

        // 코드 13-2 가변 상태를 참조하는 클래스용 clone 메서드
    @Override public Stack clone() {
        try {
            Stack result = (Stack) super.clone();
            result.elements = elements.clone();
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

이를 해결하기 위한 코드가 위와 같은 코드이다.

array의 clone을 실행하게 되면 array의 내부 요소 요소 하나마다 전부 재귀적으로 clone을 호출 해 복사를 하게 된다. 이러한 방식으로 해결할 수 있다.

그러나 이런 방식은 해당 필드가 final value였다면 처음 super.clone이 된 이후에 변경이 불가능하기

때문에 final을 제거해 주어야한다.

clone을 재귀적으로 호출하는것이 충분하지 않을 수도 있다.

HashTable안에 collision이 발생했을 때 저장할 자료구조로 LinkedList를 사용한다고 가정하면,

public class HashTable implements Cloneable{
        private Entry[] buckets = ...;

        private static class Entry{
                final Object key;
                Object value;
                Entry next;
        }
}

이와 같은 클래스에서 super.clone()을 호출하고 buckets에서 다시 clone을 호출한다면

똑같은 LinkedList가 복사가 되어 예기치 않게 동작할 수 있다.

이를 해결하기 위해선 배열 전체를 순회하며 deepcopy를 하는 로직을 작성해 주어야한다.

Object의 clone은 syncronize를 고려하지 않은 구현이다.

따라서 thread safety를 신경 써야하는 클래스라면 clone 메소드를 이에 맞게 재정의 해야한다.

지금의 예시는 clone을 구현하기 위한 복잡한 예시들이다.

clone을 쓰기 위해서 이 많을 수고를 항상 덜어야하느냐?

정답은 clone을 쓰지 않으면 된다.

clone을 사용하지 않고 copy constructor와 copy factory 메소드를 제공하면 된다.

public Yum(Yum yum){...}

public static Yum newInstance(Yum yum) {...};

이런 식으로 클래스에서 정상적인 복사 메소드를 제공한다면 이 객체를 상속 받던 필드로 받던 해당 메소드를 호출하여 손쉽게 복사할 수 있다. 다만 API 문서를 열심히 읽어봐야 한다.

또한 이는 본인의 클래스 뿐 아닌 이 클래스의 상위 인터페이스를 인자로 받아 유연한 복사가 가능해진다.

예시로 HashSet 객체를 TreeSet 타입으로 복제할 수 있다.

  • 요약 - clone 메소드는 array를 복사할 때는 기가막하다고 한다.
    고럴 때 쓰면 좋을 것 같다.

 

 

 

GitHub - ssafystudy/EffectiveJava

Contribute to ssafystudy/EffectiveJava development by creating an account on GitHub.

github.com

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
TAG
more
«   2024/09   »
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
글 보관함