πŸ“‚ JAVA/μ΄νŽ™ν‹°λΈŒ μžλ°”

clone μž¬μ •μ˜λŠ” μ£Όμ˜ν•΄μ„œ μ§„ν–‰ν•˜λΌ(2) - [3μž₯. λͺ¨λ“  객체의 곡톡 λ©”μ„œλ“œ(μ•„μ΄ν…œ13)]

Amenable 2023. 4. 9. 00:29

  이전 글을 톡해 λΆˆλ³€ κ°μ²΄μ—μ„œ clone을 μ‚¬μš©ν•˜λŠ” 방법을 μ•Œμ•„λ³΄μ•˜λ‹€.

  μ΄λ²ˆμ—λŠ” κ°€λ³€ κ°μ²΄μ—μ„œ clone을 μ‚¬μš©ν•˜λŠ” 방법을 μ•Œμ•„λ³΄μž. κ°€λ³€ 객체λ₯Ό λ³΅μ œν•˜λŠ” 3κ°€μ§€ 방법을 μ‚΄νŽ΄λ³΄μž.

 

1. λ°°μ—΄ 볡사λ₯Ό μ΄μš©ν•˜λŠ” 경우 πŸ‘¨‍πŸš€

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);
	}
	
	public boolean isEmpty() {
		return size == 0;
	}
	
	@Override
	public Stack clone() {
		try {
			Stack result = (Stack) super.clone();
			return result;
		} catch (CloneNotSupportedException e) {
			throw new AssertionError();
		}
	}
}

----------

public static void main(String[] args) {
    Object[] values = new Object[2];
    values[0] = new User("amenable", 24);
    values[1] = new User("sunny", 21);

    Stack stackOrigin = new Stack();
    for(Object arg : values)
        stackOrigin.push(arg);
    Stack stackCopy = stackOrigin.clone();
    while(!stackOrigin.isEmpty())
        System.out.println(stackOrigin.pop() + " ");
    // clone.User@15db9742 
    // clone.User@6d06d69c 
    while(!stackCopy.isEmpty())
        System.out.println(stackCopy.pop() + " ");
    // null
    // null
}

  λΆˆλ³€ κ°μ²΄μ—μ„œ clone을 μ •μ˜ν•œ λ°©μ‹μœΌλ‘œ κ°€λ³€ 객체에 μ μš©ν•˜κ²Œ λœλ‹€λ©΄ μœ„μ™€ 같이 copy 된 μΈμŠ€ν„΄μŠ€κ°€ κΈ°μ‘΄ 값에 영ν–₯을 λ°›κ²Œ λœλ‹€. κ·ΈλŸ¬λ―€λ‘œ μœ„μ™€ 같이 배열을 λ³΅μ œν•  λ•ŒλŠ” λ°°μ—΄μ˜ clone λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•΄μ•Ό ν•œλ‹€.

@Override
public Stack clone() {
    try {
        Stack result = (Stack) super.clone();
        result.elements = elements.clone(); // final이면 μ‚¬μš© λΆˆκ°€
        return result;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}

----------

public static void main(String[] args) {
    Object[] values = new Object[2];
    values[0] = new User("amenable", 24);
    values[1] = new User("sunny", 21);

    Stack stackOrigin = new Stack();
    for(Object arg : values)
        stackOrigin.push(arg);
    Stack stackCopy = stackOrigin.clone();
    while(!stackOrigin.isEmpty())
        System.out.println(stackOrigin.pop() + " ");
    // clone.User@15db9742 
    // clone.User@6d06d69c 
    while(!stackCopy.isEmpty())
        System.out.println(stackCopy.pop() + " ");
    // clone.User@15db9742 
    // clone.User@6d06d69c 
}

  μ΄λ ‡κ²Œ ν•˜λ©΄ 배열이 2개 생기긴 ν•˜κ² μ§€λ§Œ, 결ꡭ은 얕은 볡사(Shallow Copy)λ‹€. κ·Έλž˜μ„œ 사싀상 λ°°μ—΄μ—μ„œ λ™μΌν•œ μΈμŠ€ν„΄μŠ€λ₯Ό 보고 있긴 ν•˜λ‹€.

  그리고 elementsκ°€ final이라면 μƒˆλ‘œ ν• λ‹Ήν•  수 μ—†μœΌλ―€λ‘œ final을 μ“Έ 수 μ—†λ‹€λŠ” μ œμ•½λ„ μ‘΄μž¬ν•œλ‹€.

 

2. Deep Copyλ₯Ό μ΄μš©ν•˜λŠ” 경우 πŸ‘©‍πŸ”¬

public class HashTable implements Cloneable {
	
	private Entry[] buckets = new Entry[10];
	
	private static class Entry {
		final Object key;
		Object value;
		Entry next;
		
		Entry(Object key, Object value, Entry next) {
			this.key = key;
			this.value = value;
			this.next = next;
		}
		
		public void add(Object key, Object value) {
			this.next = new Entry(key, value, null);
		}
		
		public Entry deepCopy() {
			return new Entry(key, value, next == null ? null : next.deepCopy());
		}
	}
	
	@Override
	public HashTable clone() {
		HashTable result = null;
		try {
			result = (HashTable)super.clone();
			result.buckets = this.buckets.clone(); // shallow 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); // true
		System.out.println(hashTable.buckets[0] == clone.buckets[0]); // true
	}
}

  λ³΅μ œλ³Έμ€ μžμ‹ λ§Œμ˜ 버킷 배열을 κ°–μ§€λ§Œ, 이 배열은 원본과 같은 μ—°κ²° 리슀트λ₯Ό μ°Έμ‘°ν•˜κ³  μžˆλ‹€. κ·ΈλŸ¬λ―€λ‘œ μ•„λž˜μ™€ 같이 Deep Copyλ₯Ό ν•΄μ€˜μ•Ό ν•œλ‹€.

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

@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(); // 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); // true
    System.out.println(hashTable.buckets[0] == clone.buckets[0]); // false
}

  μœ„μ—μ„œ λ‚˜μ˜¨ DeepCopy 방법은 μž¬κ·€μ μœΌλ‘œ ν˜ΈμΆœν•˜λŠ” 방식이닀. μŠ€νƒ μ˜€λ²„ν”Œλ‘œμš° λ•Œλ¬Έμ— μ—°κ²° 리슀트λ₯Ό λ³΅μ œν•˜λŠ” λ°©λ²•μœΌλ‘œλŠ” κ·Έλ‹€μ§€ μ’‹μ§€ μ•Šλ‹€. κ·Έλ ‡κΈ° λ•Œλ¬Έμ— μ•„λž˜μ™€ 같이 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;
}

  그리고 cloneλ©”μ„œλ“œ μ•ˆμ—μ„œλŠ” μ•„λž˜μ™€ 같이 μž¬μ •μ˜ ν•  수 μžˆλŠ” λ©”μ„œλ“œλ₯Ό λ§Œλ“€μ§€ μ•Šμ•„μ•Ό ν•œλ‹€. κ·Έ μ΄μœ λŠ” ν•˜μœ„ ν΄λž˜μŠ€μ—μ„œ ν•΄λ‹Ή λ©”μ„œλ“œλ₯Ό μž¬μ •μ˜ν•˜λ©΄ λ™μž‘이 λ°”λ€” μˆ˜ μžˆκΈ° λ•Œλ¬Έμ΄λ‹€.

@Override
public HashTable clone() {
    HashTable result = null;
    try {
        result = (HashTable)super.clone();
        // result.buckets = new Entry[this.buckets.length];
        result.buckets = createNewBuckets();
        for(int i = 0; i < this.buckets.length; i++) {
            if(buckets[i] != null) {
                result.buckets[i] = this.buckets[i].deepCopy(); // deep copy
            }
        }
        return result;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}

protected Entry[] createNewBuckets() {
    ...
}

 

3. κ³ μˆ˜μ€€ λ©”μ„œλ“œλ₯Ό μ΄μš©ν•˜λŠ” 경우 🦸‍♂️

// κ³ μˆ˜μ€€ λ©”μ„œλ“œ μ˜ˆμ‹œ
result.get(key);
result.put(key, value);

  super.clone을 ν˜ΈμΆœν•˜μ—¬ 얻은 객체의 λͺ¨λ“  ν•„λ“œλ₯Ό 초기 μƒνƒœλ‘œ μ„€μ •ν•˜κ³  원본 객체의 μƒνƒœλ₯Ό λ‹€μ‹œ μƒμ„±ν•˜λŠ” κ³ μˆ˜μ€€ λ©”μ„œλ“œλ₯Ό μ‚¬μš©ν•  μˆ˜λ„ μžˆλ‹€. 그러면 원본과 λ³΅μ‚¬λ³Έμ˜ λ‚΄μš©μ΄ κ°™κ²Œ λœλ‹€.

  ν•˜μ§€λ§Œ 이 방법은 μ €μˆ˜μ€€μ—μ„œ λ°”λ‘œ μ²˜λ¦¬ν•  λ•Œλ³΄λ‹€ λŠλ¦¬λ‹€. 그리고 Cloneable μ•„ν‚€ν…μ²˜μ˜ κΈ°μ΄ˆκ°€ λ˜λŠ” ν•„λ“œ λ‹¨μœ„ 객체 볡사λ₯Ό μš°νšŒν•˜κΈ° λ•Œλ¬Έμ— 전체 Cloneable μ•„ν‚€ν…μ²˜μ™€λŠ” μ–΄μšΈλ¦¬μ§€ μ•ŠλŠ” 방식이기도 ν•˜λ‹€.

 

4. μΆ”κ°€ 주의 사항 πŸ‘¨‍πŸ’»

1. μƒμ†μš© ν΄λž˜μŠ€λŠ” Cloneable을 κ΅¬ν˜„ν•΄μ„œλŠ” μ•ˆ λœλ‹€.

  κ·Έ μ΄μœ λŠ” ν•΄λ‹Ή 클래슀λ₯Ό ν™•μž₯ν•˜λ €λŠ” ν”„λ‘œκ·Έλž˜λ¨Έμ—κ²Œ clone을 μ˜¬λ°”λ₯΄κ²Œ κ΅¬ν˜„ν•˜λ„λ‘ λ§Žμ€ 뢀담을 μ£ΌκΈ° λ•Œλ¬Έμ΄λ‹€. κ·Έλž˜μ„œ μ•„λž˜μ™€ 같은 2κ°€μ§€ λ°©λ²•μœΌλ‘œ κ·Έ 뢀담을 λœμ–΄μ€„ 수 μžˆλ‹€.

  1. μΆ”μƒν΄λž˜μŠ€μ—μ„œ clone을 κ΅¬ν˜„ν•΄ μ£Όκ³  ν•˜μœ„ ν΄λž˜μŠ€μ—μ„œ κ΅¬ν˜„μ„ ν•˜μ§€ μ•Šλ„λ‘ ν•˜κΈ°.
  2. ν•˜μœ„ν΄λž˜μŠ€μ—μ„œ μ•„μ˜ˆ clone을 κ΅¬ν˜„ν•˜μ§€ λͺ»ν•˜λ„둝 막기
    이 λ°©μ‹μ€ λ§ˆμΉ˜ Objectλ₯Ό λ°”λ‘œ μƒμ†ν•  λ•Œμ²˜λŸΌ Cloneable κ΅¬ν˜„ μ—¬λΆ€λ₯Ό ν•˜μœ„ ν΄λž˜μŠ€μ—μ„œ μ„ νƒν•˜λ„둝 ν•΄μ€€λ‹€.
public abstract class Shape implements Cloneable {

	private int area;
	
	public abstract int getArea();
	
	// 방법 1
	@Override
	protected Object clone() throws CloneNotSupportedException {
		return super.clone();
	}
	
	// 방법 2
	@Override
	protected final Object clone() throws CloneNotSupportedException {
		throw new CloneNotSupportedException();
	}
}

2. μŠ€λ ˆλ“œ μ•ˆμ „ ν΄λž˜μŠ€λ‘œ λ§Œλ“€μ–΄ μ€˜μ•Ό ν•œλ‹€λ©΄ λ™κΈ°ν™”도 μ²˜λ¦¬ν•΄μ€˜μ•Ό ν•œλ‹€.

@Override
public synchronized HashTable clone() {
    ...
}

3. Deep Copy λ°©λ²•μ—μ„œ μ‚΄νŽ΄λ³Έ κ²ƒμ²˜λŸΌ cloneλ©”μ„œλ“œ μ•ˆμ—μ„œλŠ” μž¬μ •μ˜ ν•  μˆ˜ μžˆλŠ” λ©”μ„œλ“œλ₯Ό λ§Œλ“€μ§€ μ•Šμ•„μ•Ό ν•œλ‹€.

 

5. κ²°λ‘  πŸ‘¨‍🏭

  μ§€κΈˆκΉŒμ§€ μ­‰ 읽어왔닀면 λŠκΌˆκ² μ§€λ§Œ, ꡳ이 μ΄λ ‡κ²Œ ν•΄μ•Ό ν• κΉŒ? 맀우 λ³΅μž‘ν•œ κ²½μš°κ°€ λ§Žλ‹€...

  Cloneable을 이미 κ΅¬ν˜„ν•œ 클래슀λ₯Ό ν™•μž₯ν•œλ‹€λ©΄ μ–΄μ©” 수 없이 clone을 잘 μž‘λ™ν•˜λ„λ‘ κ΅¬ν˜„ν•΄μ•Ό ν•œλ‹€. κ·Έλ ‡μ§€ μ•Šμ€ μƒν™©μ—μ„œλŠ” μ•„λž˜μ™€ 같이 볡사 μƒμ„±μž(= λ³€ν™˜ μƒμ„±μž)와 볡사 νŒ©ν„°λ¦¬(= λ³€ν™˜ νŒ©ν„°λ¦¬)λΌλŠ” 더 λ‚˜μ€ 객체 볡사 방식을 μ‚¬μš©ν•˜λ„λ‘ ν•˜μž.

public class User {
	private final String name;
	private final int age;
	
	public User(String name, int age) {
		super();
		this.name = name;
		this.age = age;
	}
	
	// 볡사 μƒμ„±μž
	public User(User user) {
		this.name = user.name;
		this.age = user.age;
	}
	
	// 볡사 νŒ©ν„°λ¦¬
	public static User copyFactory(User user) {
		User copyUser = new User(user.name, user.age);
		return copyUser;
	}
}

  볡사 μƒμ„±μžμ™€ 볡사 νŒ©ν„°λ¦¬λ₯Ό μ΄μš©ν•œλ‹€λ©΄ λ‹€μŒκ³Ό κ°™μ€ μž₯점이 μžˆλ‹€. (μ§€κΈˆκΉŒμ§€ κ³ λ €ν•΄ 쀬던 κ²ƒλ“€μ˜ λ°˜λŒ€λΌκ³  μƒκ°ν•˜λ©΄ μ΄ν•΄ν•˜κΈ° μ‰¬μšΈ κ²ƒμ΄λ‹€.)

  1. μ–Έμ–΄ λͺ¨μˆœμ μ΄κ³  μœ„ν—˜μ²œλ§Œν•œ κ°μ²΄ μƒμ„± λ©”μ»€λ‹ˆμ¦˜(μƒμ„±μžλ₯Ό μ“°μ§€ μ•ŠλŠ” λ°©μ‹)을 μ‚¬μš©ν•˜μ§€ μ•Šμ•„도 λœλ‹€.
  2. μ—‰μ„±ν•˜κ²Œ λ¬Έμ„œν™”λœ κ·œμ•½μ— κΈ°λŒ€μ§€ μ•Šμ•„도 λœλ‹€. (clone κ·œμ•½)
  3. 정상적인 final ν•„λ“œ μš©λ²•κ³Ό μΆ©λŒν•˜μ§€ μ•ŠλŠ”λ‹€.
  4. λΆˆν•„μš”ν•œ κ²€μ‚¬ μ˜ˆμ™Έλ₯Ό λ˜μ§€μ§€ μ•ŠλŠ”λ‹€.
  5. ν˜•λ³€ν™˜λ„ ν•„μš”ν•˜μ§€ μ•ŠλŠ”λ‹€.
  6. 'μΈν„°νŽ˜μ΄μŠ€' νƒ€μž…μ˜ μΈμŠ€ν„΄μŠ€λ₯Ό μΈμˆ˜λ‘œ λ°›μ„ μˆ˜ μžˆλ‹€.
    μ›λ³Έμ˜ κ΅¬ν˜„ νƒ€μž…에 μ–½λ§€μ΄μ§€ μ•Šκ³  λ³΅μ œλ³Έμ˜ νƒ€μž…을 μ§μ ‘ μ„ νƒν•  μˆ˜ μžˆλ‹€.
public static void main(String[] args) {
    Set<String> hashSet = new HashSet<>();
    Set<String> treeSet = new TreeSet<>(hashSet);
}

----------

// 인수λ₯Ό ν™•μΈν•˜μž.
public TreeSet(Collection<? extends E> c) {
    this();
    addAll(c);
}

 

ν•΄λ‹Ή 글은 λ°±κΈ°μ„  λ‹˜μ˜ 'μ΄νŽ™ν‹°λΈŒ μžλ°” μ™„λ²½ 곡랡'을 μˆ˜κ°•ν•˜κ³  μž‘μ„±ν•œ κ²ƒμž…λ‹ˆλ‹€.