tonykang22 / study

0 stars 0 forks source link

[Effective Java] 아이템 13. clone 재정의는 주의해서 진행하라. #53

Open tonykang22 opened 2 years ago

tonykang22 commented 2 years ago

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

핵심 정리 : 애매모호한 clone 규약


예시 코드

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

    public PhoneNumber(int areaCode, int prefix, int lineNum) {
        this.areaCode = rangeCheck(areaCode, 999, "지역코드");
        this.prefix   = rangeCheck(prefix,   999, "프리픽스");
        this.lineNum  = rangeCheck(lineNum, 9999, "가입자 번호");
        System.out.println("constructor is called");
    }

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

    public static void main(String[] args) {
        PhoneNumber pn = new PhoneNumber(707, 867, 5309);
        Map<PhoneNumber, String> m = new HashMap<>();
        m.put(pn, "제니");
        PhoneNumber clone = pn.clone();
        System.out.println(m.get(clone));

        System.out.println(clone != pn); // 반드시 true
        System.out.println(clone.getClass() == pn.getClass()); // 반드시 true
        System.out.println(clone.equals(pn)); // true가 아닐 수도 있다.
    }



예시 코드

하위 클래스의 clone이 깨질 수 있는 경우


올바르게 구현한 경우 (super.clone() 사용하기)

생성자 호출하지 않는 방법

핵심 정리: 가변 객체의 clone 구현하는 방법


예시 코드

가변 상태를 참조하는 클래스용 clone 메서드

stack과 copy는 같은 element를 보고 있다.

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;
    }

    public boolean isEmpty() {
        return size ==0;
    }

    @Override public Stack clone() {
        try {
            Stack result = (Stack) super.clone();
            result.elements = elements.clone();
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

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

    public static void main(String[] args) {
        Object[] values = new Object[2];
        values[0] = new PhoneNumber(123, 456, 7890);
        values[1] = new PhoneNumber(321, 764, 2341);

        Stack stack = new Stack();
        for (Object arg : values)
            stack.push(arg);

        Stack copy = stack.clone();

        System.out.println("pop from stack");
        while (!stack.isEmpty())
            System.out.println(stack.pop() + " ");

        System.out.println("pop from copy");
        while (!copy.isEmpty())
            System.out.println(copy.pop() + " ");

        System.out.println(stack.elements[0] == copy.elements[0]);
    }
}


deep 복제

// public Entry deepCopy() { // return new Entry(key, value, next == null ? null : next.deepCopy()); // }

    public Entry deepCopy() {
        Entry result = new Entry(key, value, next);
        for (Entry p = result ; p.next != null ; p = p.next) {
            p.next = new Entry(p.next.key, p.next.value, p.next.next);
        }
        return result;
    }
}
@Override
public HashTable clone() {
    HashTable result = null;
    try {
        result = (HashTable)super.clone();
        result.buckets = new Entry[this.buckets.length];

        for (int i = 0 ; i < this.buckets.length; i++) {
            if (buckets[i] != null) {
                result.buckets[i] = this.buckets[i].deepCopy(); // p83, deep copy
            }
        }
        return result;
    } catch (CloneNotSupportedException e) {
        throw  new AssertionError();
    }
}

public static void main(String[] args) {
    HashTable hashTable = new HashTable();
    Entry entry = new Entry(new Object(), new Object(), null);
    hashTable.buckets[0] = entry;
    HashTable clone = hashTable.clone();
    System.out.println(hashTable.buckets[0] == entry);
    System.out.println(hashTable.buckets[0] == clone.buckets[0]);
}

}


<br><br>

## 핵심 정리: clone 대신 권장하는 방법
규약을 모두 지키며 clone을 재정의하기에는 많은 노력이 필요하기 때문에...
- “복사 생성자” 또는 변환 생성자, “복사 팩터리” 또는 변환 팩터리.
    * 복사 생성자를 사용했을 때의 장점
        * 생성자를 통해 복제하면 명확하다.
        * 기존의 생성자에 검증 작업을 생략할 수 있다.
        * final을 쓸 수 있다.
            * 복사 생성자를 사용하지 않는 경우, 굳이 clone()을 구현하기 위해 final을 떼어내야 하기 때문에 고민해볼 필요가 있다.
- 또 다른 큰 장점 중 하나로 인터페이스 타입의 인스턴스를 인수로 받을 수 있다.
    * 클라이언트가 복제본의 타입을 결정할 수 있다.
- 읽어볼 것) Josh Bloch on Design, “Copy Constructor versus Cloning”

<br>

## 예시 코드
### 복사 생성자
``` java
public final class PhoneNumber implements Cloneable {
    private final short areaCode, prefix, lineNum;

    public PhoneNumber(int areaCode, int prefix, int lineNum) {
        this.areaCode = rangeCheck(areaCode, 999, "지역코드");
        this.prefix   = rangeCheck(prefix,   999, "프리픽스");
        this.lineNum  = rangeCheck(lineNum, 9999, "가입자 번호");
        System.out.println("constructor is called");
    }

   // Copy Constructor
    public PhoneNumber(PhoneNumber phoneNumber) {
        this(phoneNumber.areaCode, phoneNumber.prefix, phoneNumber.lineNum);
    }
}


완벽 공략


완벽 공략 29. UncheckedException

왜 우리는 unchecked exception를 선호하는가?


그렇다면 우리는 비검사 예외만 쓰면 되는걸까?

추가

언제 어떤 예외를 써야 하는가?


완벽 공략 30. TreeSet

AbstractSet을 확장한 정렬된 컬렉션


예시 코드

natural order가 존재하지 않는 경우


Comparator만 넘겨준다.


동기화가 적용된 TreeSet을 사용하고 싶다면