Java

[Java] - κΉŠμ€ 볡사(Deep Copy) vs 얕은 볡사(Shallow Copy)

주발2 2021. 7. 30. 16:40
λ°˜μ‘ν˜•

πŸ“Ž  Java κΉŠμ€ 볡사(Deep Copy)와 얕은 볡사(Shallow Copy)

 

μ•ˆλ…•ν•˜μ„Έμš”! μ΄λ²ˆμ— 정리할 λ‚΄μš©μ€ μžλ°”μ—μ„œμ˜ κΉŠμ€ 볡사와 얕은 볡사 μž…λ‹ˆλ‹€.

 

κΉŠμ€ 볡사와 얕은 λ³΅μ‚¬λΌλŠ” κ°œλ…μ€ ν‰μ†Œμ— μ ‘ν•œμ μ΄ κ½€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

ν•˜μ§€λ§Œ 였늘 μ•Œκ³ λ¦¬μ¦˜ 문제λ₯Ό ν’€λ©΄μ„œ μ•„λ¬΄λŸ° μ˜μ‹¬μ—†μ΄(?) λ‹€μŒκ³Ό 같이 μ»¬λ ‰μ…˜ Listλ₯Ό 얕은 λ³΅μ‚¬ν•˜λŠ” μ½”λ“œλ₯Ό μž‘μ„±ν–ˆμ—ˆκ³ , 이에 따라 μ°Έμ‘°ν•˜κ³  μžˆλŠ” 두 λ¦¬μŠ€νŠΈκ°€ λͺ¨λ‘ 값이 λ³€κ²½λ˜μ–΄ μƒκ°ν–ˆλ˜ 아웃풋과 λ‹€λ₯΄κ²Œ λ‚˜μ™€μ„œ μ•½κ°„ 어리λ‘₯μ ˆν•œ μƒνƒœμ˜€μŠ΅λ‹ˆλ‹€. πŸ€”

List<String> list = new ArrayList<>();

...

List<String> temp = list; // shallow copy

 

ν•΄λ‹Ή λ¬Έμ œμ μ€ 디버깅을 톡해 νŒŒμ•…ν•  수 μžˆμ—ˆλŠ”λ°μš”, 기본적인 λ‚΄μš©μ΄μ§€λ§Œ ν™•μ‹€ν•˜κ²Œ μ •λ¦¬ν•˜κ³  λ„˜μ–΄κ°€λ„λ‘ ν•˜κ² μŠ΅λ‹ˆλ‹€ πŸ˜ƒ

 

 

κΉŠμ€ 볡사(Deep Copy)λŠ” 'μ‹€μ œ κ°’'을 μƒˆλ‘œμš΄ λ©”λͺ¨λ¦¬ 곡간에 λ³΅μ‚¬ν•˜λŠ” 것을 μ˜λ―Έν•˜λ©°,

얕은 볡사(Shallow Copy)λŠ” 'μ£Όμ†Œ κ°’'을 λ³΅μ‚¬ν•œλ‹€λŠ” μ˜λ―Έμž…λ‹ˆλ‹€.

 

얕은 λ³΅μ‚¬μ˜ 경우 μ£Όμ†Œ 값을 λ³΅μ‚¬ν•˜κΈ° λ•Œλ¬Έμ—, μ°Έμ‘°ν•˜κ³  μžˆλŠ” μ‹€μ œκ°’μ€ κ°™μŠ΅λ‹ˆλ‹€.

μ˜ˆμ œμ™€ μ„€λͺ…을 톡해 ν™•μΈν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

 

 

 

🎯  얕은 볡사(Shallow Copy)

public class CopyObject {

    private String name;
    private int age;
    
    public CopyObject() {
    }

    public CopyObject(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}



import org.junit.jupiter.api.Test;

import static org.junit.jupiter.api.Assertions.*;

class CopyObjectTest {

    @Test
    void shallowCopy() {
        CopyObject original = new CopyObject("JuHyun", 20);
        CopyObject copy = original; // 얕은 볡사

        copy.setName("JuBal");

        System.out.println(original.getName());
        System.out.println(copy.getName());
    }
}

μœ„ μ½”λ“œμ—μ„œλŠ” copy 객체에 setλ©”μ†Œλ“œλ₯Ό 톡해 이름을 λ³€κ²½ν–ˆλŠ”λ°,

μ‹€μ œ κ²°κ³ΌλŠ” original 객체와 copy κ°μ²΄ λͺ¨λ‘ 값이 변경이 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

 

CopyObject copy = original 의 μ½”λ“œμ—μ„œ 객체의 얕은 볡사λ₯Ό 톡해 'μ£Όμ†Œ κ°’'을 λ³€κ²½ν–ˆκΈ° λ•Œλ¬Έμ—

μ°Έμ‘°ν•˜κ³  μžˆλŠ” μ‹€μ œ 값은 λ™μΌν•˜κ³ , λ³΅μ‚¬ν•œ 객체가 λ³€κ²½λœλ‹€λ©΄ 기쑴의 객체도 변경이 λ˜λŠ” κ²ƒμž…λ‹ˆλ‹€.

 

 

μœ„ μƒνƒœμ— λŒ€ν•œ λ©”λͺ¨λ¦¬ ꡬ쑰λ₯Ό λ‚˜νƒ€λ‚΄λ©΄ λ‹€μŒκ³Ό 같이 λ©λ‹ˆλ‹€.

CopyObject original = new CopyObject("JuHyun", 20);
CopyObject copy = original;

μŠ€νƒμ΄ 슀팸으둜 λ³΄μ΄λŠ”κ±΄ λ°°κ°€ κ³ νŒŒμ„œμΌκΉŒμš”..

original μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜λ©΄ Stack μ˜μ—­μ— 참쑰값이, Heap μ˜μ—­μ— μ‹€μ œκ°’μ΄ μ €μž₯이 λ©λ‹ˆλ‹€.

그리고 얕은 볡사λ₯Ό 톡해 객체λ₯Ό λ³΅μ‚¬ν–ˆκΈ° λ•Œλ¬Έμ— copy μΈμŠ€ν„΄μŠ€λŠ” original μΈμŠ€ν„΄μŠ€κ°€ μ°Έμ‘°ν•˜κ³  μžˆλŠ”

Heap μ˜μ—­μ˜ 참쑰값을 λ™μΌν•˜κ²Œ 바라보고 μžˆλŠ” μƒνƒœκ°€ λ©λ‹ˆλ‹€.

 

κ·Έ ν›„ set λ©”μ†Œλ“œλ₯Ό 톡해 값을 λ³€κ²½ν•˜λ©΄ λ™μΌν•œ μ£Όμ†Œλ₯Ό μ°Έμ‘°ν•˜κ³  있기 λ•Œλ¬Έμ— μ•„λž˜μ™€ 같이 λ©λ‹ˆλ‹€.

λ”°λΌμ„œ μ½”λ“œλ‘œλŠ” copy 객체의 name만 λ³€κ²½ν–ˆμ§€λ§Œ,

λ™μΌν•œ μ£Όμ†Œλ₯Ό μ°Έμ‘°ν•˜κ³  있기 λ•Œλ¬Έμ— original의 객체에도 영ν–₯을 끼치게 λ©λ‹ˆλ‹€.

 

κ·Έλž˜μ„œ 객체λ₯Ό 좜λ ₯해보면 μ•„λž˜μ™€ 같이 λ™μΌν•œ μ£Όμ†Œκ°€ 좜λ ₯이 λ©λ‹ˆλ‹€.

CopyObject original = new CopyObject("JuHyun", 20);
CopyObject copy = original;

System.out.println(original);
System.out.println(copy);

 

 

 

 

🎯  κΉŠμ€ 볡사(Deep Copy)

κΉŠμ€ 볡사λ₯Ό κ΅¬ν˜„ν•˜λŠ” 방법은 μ—¬λŸ¬κ°€μ§€κ°€ μžˆμŠ΅λ‹ˆλ‹€.

  • Cloneable μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„
  • 볡사 μƒμ„±μž
  • 볡사 νŒ©ν„°λ¦¬ λ“±λ“±....

 

 

β—Ž Cloneable μΈν„°νŽ˜μ΄μŠ€ κ΅¬ν˜„

Cloneable μΈν„°νŽ˜μ΄μŠ€λŠ” μœ„μ™€ 같이 빈 껍데기의 μΈν„°νŽ˜μ΄μŠ€μ§€λ§Œ

주석을 μ‚΄νŽ΄λ³΄λ©΄ Object 클래슀의 clone() λ©”μ†Œλ“œλ₯Ό λ°˜λ“œμ‹œ κ΅¬ν˜„ν•˜λΌκ³  μ„€λͺ…이 λ˜μ–΄μžˆμŠ΅λ‹ˆλ‹€.

 

 

public class CopyObject implements Cloneable {

    private String name;
    private int age;

    public CopyObject() {
    }

    public CopyObject(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    protected CopyObject clone() throws CloneNotSupportedException {
        return (CopyObject) super.clone();
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


    @Test
    void shallowCopy() throws CloneNotSupportedException {
        CopyObject original = new CopyObject("JuHyun", 20);
        CopyObject copy = original.clone();

        copy.setName("JuBal");

        System.out.println(original.getName());
        System.out.println(copy.getName());
    }

κΉŠμ€ 볡사λ₯Ό 톡해 ν…ŒμŠ€νŠΈλ₯Ό 진행해보면 얕은 λ³΅μ‚¬μ™€λŠ” 달리 original μΈμŠ€ν„΄μŠ€μ˜ 값은 변경이 λ˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.

 

 

β€» Effective Java 13μž₯μ—μ„œλŠ” clone μž¬μ •μ˜λŠ” μ£Όμ˜ν•΄μ„œ μ§„ν–‰ν•˜λΌ λΌλŠ” μ•„μ΄ν…œμ΄ μžˆμŠ΅λ‹ˆλ‹€.

ν•΄λ‹Ή μ±…μ˜ λ‚΄μš©μ„ λŒ€λž΅μ μœΌλ‘œ μ‚΄νŽ΄λ³΄λ©΄ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

Cloneable μΈν„°νŽ˜μ΄μŠ€λŠ” λ³΅μ œν•΄λ„ λ˜λŠ” ν΄λž˜μŠ€μž„μ„ λͺ…μ‹œν•˜λŠ” μš©λ„μ˜ 믹슀인 μΈν„°νŽ˜μ΄μŠ€μ§€λ§Œ, μ•„μ‰½κ²Œλ„ μ˜λ„ν•œ λͺ©μ μ„ μ œλŒ€λ‘œ 이루지 λͺ»ν–ˆλ‹€. μ—¬κΈ°μ„œ 큰 λ¬Έμ œμ μ€ clone λ©”μ„œλ“œκ°€ μ„ μ–Έλœ 곳이 Cloneable이 μ•„λ‹Œ OBject이고, κ·Έ λ§ˆμ €λ„ protected이닀. κ·Έλž˜μ„œ Cloneable을 κ΅¬ν˜„ν•˜λŠ” κ²ƒλ§ŒμœΌλ‘œλŠ” μ™ΈλΆ€ κ°μ²΄μ—μ„œ clone λ©”μ†Œλ“œλ₯Ό ν˜ΈμΆœν•  수 μ—†λ‹€. λ¦¬ν”Œλ ‰μ…˜μ„ μ‚¬μš©ν•˜λ©΄ κ°€λŠ₯ν•˜μ§€λ§Œ, 100% μ„±κ³΅ν•˜λŠ” 것도 μ•„λ‹ˆλ‹€. 

μ΄λŸ¬ν•œ μ—¬λŸ¬ λ¬Έμ œμ μ„ 가진 μΈν„°νŽ˜μ΄μŠ€μ΄μ§€λ§Œ, Cloneable 방식은 널리 쓰이고 μžˆμ–΄μ„œ 잘 μ•Œμ•„λ‘λŠ” 것이 μ’‹λ‹€.

 

Cloneable이 λͺ°κ³  온 λͺ¨λ“  문제λ₯Ό λ˜μ§šμ–΄λ΄€μ„ λ•Œ, μƒˆλ‘œμš΄ μΈν„°νŽ˜μ΄μŠ€λ₯Ό λ§Œλ“€ λ•ŒλŠ” μ ˆλŒ€ Cloneable을 ν™•μž₯ν•΄μ„œλŠ” μ•ˆ 되며, μƒˆλ‘œμš΄ ν΄λž˜μŠ€λ„ 이λ₯Ό κ΅¬ν˜„ν•΄μ„œλŠ” μ•ˆλœλ‹€. final 클래슀라면 Cloneable을 κ΅¬ν˜„ν•΄λ„ μœ„ν—˜μ΄ ν¬μ§€λŠ” μ•Šμ§€λ§Œ, μ„±λŠ₯ μ΅œμ ν™” κ΄€μ μ—μ„œ κ²€ν† ν•œ ν›„ 별닀λ₯Έ λ¬Έμ œκ°€ 없을 λ•Œλ§Œ λ“œλ¬Όκ²Œ ν—ˆμš©ν•΄μ•Ό ν•œλ‹€.

 

κΈ°λ³Έ 원칙은 '볡제 κΈ°λŠ₯은 μƒμ„±μžμ™€ νŒ©ν„°λ¦¬λ₯Ό μ΄μš©ν•˜λŠ”κ²Œ 졜고' λΌλŠ” 것이닀.

단, λ°°μ—΄λ§Œμ€ clone λ©”μ†Œλ“œ 방식이 κ°€μž₯ κΉ”λ”ν•œ, 이 κ·œμΉ™μ˜ ν•©λ‹Ήν•œ μ˜ˆμ™ΈλΌ ν•  수 μžˆλ‹€.

 

http://www.yes24.com/Product/Goods/65551284

 

 

 

β—Ž 볡사 μƒμ„±μž, 볡사 νŒ©ν„°λ¦¬

public class CopyObject {

    private String name;
    private int age;

    public CopyObject() {
    }

    /* 볡사 μƒμ„±μž */
    public CopyObject(CopyObject original) {
        this.name = original.name;
        this.age = original.age;
    }

    /* 볡사 νŒ©ν„°λ¦¬ */
    public static CopyObject copy(CopyObject original) {
        CopyObject copy = new CopyObject();
        copy.name = original.name;
        copy.age = original.age;
        return copy;
    }

    public CopyObject(String name, int age) {
        this.name = name;
        this.age = age;
    }


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}


    @Test
    void shallowCopy() {
        CopyObject original = new CopyObject("JuHyun", 20);
        CopyObject copyConstructor = new CopyObject(original);
        CopyObject copyFactory = CopyObject.copy(original);

        copyConstructor.setName("JuBal");
        copyFactory.setName("BalJu");

        System.out.println(original.getName());
        System.out.println(copyConstructor.getName());
        System.out.println(copyFactory.getName());
    }

볡사 μƒμ„±μžμ™€ 볡사 νŒ©ν„°λ¦¬λ₯Ό 톡해 객체λ₯Ό λ³΅μ‚¬ν•˜λŠ” 과정도 κΉŠμ€ λ³΅μ‚¬μž„μ„ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.

 

 

κΉŠμ€ 볡사λ₯Ό 그림으둜 λ‚˜νƒ€λ‚΄λ©΄ λ‹€μŒκ³Ό κ°™μŠ΅λ‹ˆλ‹€.

얕은 λ³΅μ‚¬μ™€λŠ” λ‹€λ₯΄κ²Œ Heap μ˜μ—­μ— μƒˆλ‘œμš΄ λ©”λͺ¨λ¦¬ 곡간을 μƒμ„±ν•˜μ—¬ μ‹€μ œ 값을 λ³΅μ‚¬ν•©λ‹ˆλ‹€.

 

Collectionsλ‚˜ Map의 경우 이미 볡사 νŒ©ν„°λ¦¬μΈ copy() λ©”μ†Œλ“œλ₯Ό κ΅¬ν˜„ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€.

    /**
     * Copies all of the elements from one list into another.  After the
     * operation, the index of each copied element in the destination list
     * will be identical to its index in the source list.  The destination
     * list's size must be greater than or equal to the source list's size.
     * If it is greater, the remaining elements in the destination list are
     * unaffected. <p>
     *
     * This method runs in linear time.
     *
     * @param  <T> the class of the objects in the lists
     * @param  dest The destination list.
     * @param  src The source list.
     * @throws IndexOutOfBoundsException if the destination list is too small
     *         to contain the entire source List.
     * @throws UnsupportedOperationException if the destination list's
     *         list-iterator does not support the {@code set} operation.
     */
    public static <T> void copy(List<? super T> dest, List<? extends T> src) {
        int srcSize = src.size();
        if (srcSize > dest.size())
            throw new IndexOutOfBoundsException("Source does not fit in dest");

        if (srcSize < COPY_THRESHOLD ||
            (src instanceof RandomAccess && dest instanceof RandomAccess)) {
            for (int i=0; i<srcSize; i++)
                dest.set(i, src.get(i));
        } else {
            ListIterator<? super T> di=dest.listIterator();
            ListIterator<? extends T> si=src.listIterator();
            for (int i=0; i<srcSize; i++) {
                di.next();
                di.set(si.next());
            }
        }
    }

 

 

 

 

 

λ°˜μ‘ν˜•