HihoBookStudy / EffectiveJava

이펙티브 자바 북스터디입니다.
1 stars 0 forks source link

[Item13] 복사 생성자 또는 복사 팩터리 #21

Closed zpqmdh closed 2 months ago

zpqmdh commented 3 months ago

책 85페이지에서는 복사 생성자와 복사 팩터리에 대해 설명하고 있습니다. Cloneable 을 이미 구현한 클래스를 확장할 때가 아니라면 객체 복사에 복사 생성자 또는 복사 팩터리를 사용할 것을 권장하고 있습니다.

책에서는 스택, HashTable을 clone 메서드를 재정의하는 방식으로 객체 복사를 보여주었습니다. 그렇다면, 같은 예시인 스택과 HashTable을 복사 생성자, 복사 팩터리로는 어떻게 객체 복사를 구현할 수 있는지 궁금합니다.

Limgayoung commented 3 months ago

8시쯤에 깃허브에 해당 코드를 올릴 것이긴 한데 일단 전체 코드를 첨부합니다.

스택의 경우에는 배열 elements를 사용하고 있기 때문에 해당 배열을 clone한 (배열은 clone 사용 가능) 결과를 사용하게 했습니다.

Stack

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

// A cloneable version of Stack (Pages 60-61)
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 Stack(Stack s) {
        this.elements = s.elements.clone();
        this.size = s.size;
    }

    public static Stack newInstance(Stack s) {
        Stack stack = new Stack();
        stack.elements = s.elements.clone();
        stack.size = s.size;
        return stack;
    }

    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; // Eliminate obsolete reference
        return result;
    }

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

    // Clone method for class with references to mutable state
    @Override public Stack clone() {
        try {
            Stack result = (Stack) super.clone();
            result.elements = elements.clone();
            return result;
        } catch (CloneNotSupportedException e) {
            throw new AssertionError();
        }
    }

    // Ensure space for at least one more element.
    private void ensureCapacity() {
        if (elements.length == size)
            elements = Arrays.copyOf(elements, 2 * size + 1);
    }

    // To see that clone works, call with several command line arguments
    public static void main(String[] args) {
        Stack stack = new Stack();
        String[] str = {"1","2","3","4"};
        for (String arg : str)
            stack.push(arg);

        Stack copy = new Stack(stack);
        //Stack copy = stack.clone();
        while (!stack.isEmpty())
            System.out.print(stack.pop() + " ");
        System.out.println();
        while (!copy.isEmpty())
            System.out.print(copy.pop() + " ");
    }
}

HashTable

해시테이블은 까다로웠습니다. java.util의 HashTable을 참고해 작성했고 단순히 복사 생성자만을 확인하는 용도로만 단순하게 구현했습니다.

먼저 생성자에서 putAll 메서드를 호출하고 putAll 메서드에서 해당 버킷의 모든 원소에 대해 put 메서드를 실행합니다. put 메서드는 이미 있는 key라면 value만 바꾸게 했으며 새로 추가한다면 addEntry 메서드를 실행해 새로운 Entry를 버킷에 저장합니다.

이렇게 만든 HashTable의 복사본이 제대로 다른 곳을 참조하는지 확인하기 위해 put 메서드를 이용해 같은 key에 다른 value를 넣어줬고 int와 List 모두 정상적으로 복사된 것을 확인했습니다.

복사 생성자와 복사 팩터리의 동작 원리는 비슷하기 때문에 복사 팩터리는 생략합니다.

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class HashTable{

    private Entry[] buckets;

    public HashTable() {
        buckets = new Entry[10];
    }

    public HashTable(HashTable h) {
        this();
        putAll(h.buckets);
    }

    public HashTable(Entry[] buckets) {
        this();
        putAll(buckets);
    }

    public void putAll(Entry[] buckets) {
        for(Entry e:buckets) {
            if(e == null) continue;
            put(e.key, e.value, e.next);
        }        
    }

    public Object put(Object key, Object value, Entry next) {
        Entry tab[] = buckets;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;

        Entry entry = tab[index];       
        for(; entry != null ; entry = entry.next) {
            if ((entry.hash == hash) && entry.key.equals(key)) {
                //System.out.println("change!");
                Object old = entry.value;
                entry.value = value;
                return old;
            }
        }

        addEntry(hash, key, value, index);
        return null;
    }
    private void addEntry(int hash, Object key, Object value, int index) {
        Entry tab[] = buckets;
        Entry e = tab[index];
        tab[index] = new Entry(hash, key, value, e);
    }   

    public Object get(Object key) {
        Entry tab[] = buckets;
        int hash = key.hashCode();
        int index = (hash & 0x7FFFFFFF) % tab.length;

        return tab[index].value;
    }

    private static class Entry{
        final int hash;
        final Object key;
        Object value;
        Entry next;

        public Entry(int hash,Object key, Object value, Entry next) {
            super();
            this.hash = hash;
            this.key = key;
            this.value = value;
            this.next = next;
        }   

    }

    public static void main(String[] args) {
        List<Integer> arr = List.of(1,2,3,4);
        List<Integer> newArr = List.of(10,11,12,13);

        Entry[] buckets = {new Entry(5,1,1,null),
                new Entry(6,"list",arr,null)};
        HashTable h = new HashTable(buckets);
        HashTable copy = new HashTable(h);

        copy.put(1, 2, null);
        copy.put("list", newArr, null);
        System.out.println("copy "+copy.get(1));
        System.out.println("origin "+h.get(1));

        Object value = copy.get("list");

        System.out.println("copy "+copy.get("list"));
        System.out.println("origin "+h.get("list"));

    }   
}