본문 바로가기

JAVA

[JAVA] 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)

 


 

얕은 복사


객체를 복사할 때, 해당 객체만 복사하여 새 객체를 생성한다.

복사된 객체의 인스턴스 변수는 원본 객체의 인스턴스 변수와 같은 메모리 주소를 참조한다.

따라서, 해당 메모리에 저장된 값이 변경되면 원본 객체 및 복사 객체의 인스턴스 변수의 값이 함께 변경된다.

(원본 객체로부터 종속적인 객체)

class Copy{
    private String name;
    public Copy(String name) {
        this.name = name;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
public class Main {
    public static void main(String[] args) {
        Copy original = new Copy("original data");
        Copy copy = original; // 얕은 복사
        System.out.println("original 객체의 name :" + original.getName());
        copy.setName("modified data"); // copy 객체의 변수 수정
        System.out.println("original 객체의 name :" + original.getName());
        System.out.println("copy 객체의 name:" + copy.getName());
    }
}

//출력 결과
original 객체의 name :original data
original 객체의 name :modified data
copy 객체의 name:modified data

위의 코드 예제를 보면 original 객체를 복사한 copy 객체의 변수를 수정하자, original 객체의 변수 또한 함께 수정된 모습을 볼 수 있습니다.

이는, 얕은 복사를 통해 '주소 값'을 복사해 왔기에 참조하고 있는 실제 값은 동일하기에 생긴 일입니다.

 

아래의 메모리 이미지를 함께 보도록 하겠습니다.

original 인스턴스를 생성하면 Stack 영역에는 참조값이, Heap 영역에는 실제 값이 저장됩니다.

그리고 얕은 복사를 통해 객체를 복사하였기 때문에 copy 인스턴스는 original 인스턴스가 참조하고 있는 Heap 영역의

참조값을 동일하게 가집니다.

 

그 후 set 메서드를 사용하여 값을 변경하면 Heap 메모리에 저장된 실제 값이 변경됩니다.

따라서 copy 객체의 name을 변경하였지만, 동일한 주소를 참조하는 original 객체도 name이 변경된 것을 확인할 수 있습니다.

 

마지막으로 두 객체가 같은 참조 변수를 가지고 있는지 확인하기 위해 

객체의 고유한 hash code를 출력하는 System.identyHashCode()를 이용하여 확인해 보도록 하겠습니다

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

// 출력 결과

566034357
566034357

깊은 복사


깊은 복사는 객체를 복사할 때, 해당 객체와 인스턴스 변수까지 복사합니다.

전부를 복사하여 새 주소에 담기 때문에 참조를 공유하지 않습니다. (원본 객체로부터 독립적인 객체)

 

깊은 복사를 구현하는 방법은 여러 가지가 있습니다.

그중 Cloneable 인터페이스를 이용하는 방법과, 복사 생성자, 복사 팩터리를 이용한 깊은 복사를 소개해드리고자 합니다.

 

Cloneable 인터페이스 구현

class Copy implements Cloneable{
    private String name;
    public Copy(String name) {
        this.name = name;
    }
    @Override
    public Copy clone() throws CloneNotSupportedException{
        return (Copy) super.clone();
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
public class Main {
    public static void main(String[] args) throws CloneNotSupportedException {
        Copy original = new Copy("original data");
        Copy copy = original.clone(); // 얕은 복사
        System.out.println("original 객체의 name :" + original.getName());
        copy.setName("modified data"); // copy 객체의 변수 수정
        System.out.println("original 객체의 name :" + original.getName());
        System.out.println("copy 객체의 name:" + copy.getName());

    }
}

//출력 결과
original 객체의 name :original data
original 객체의 name :original data
copy 객체의 name:modified data

위의 예제 코드를 보면 얕은 복사와 달리 original 인스턴스의 값은 변경되지 않은 것을 확인할 수 있습니다.

 

복사 생성자, 복사 팩터리

class Copy {
    private String name;

    public Copy() {
    }

    public Copy(Copy original){  //복사 생성자
        this.name = original.name;
    }

    public static Copy copy(Copy original){ //복사 팩터리
        Copy copy = new Copy();
        copy.name = original.name;
        return copy;
    }

    public Copy(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}
public class Main {
    public static void main(String[] args) {
        Copy original = new Copy("original data");
        Copy copyConstructor = new Copy(original);
        Copy copyFactory = Copy.copy(original);

        System.out.println("original 객체의 name :" + original.getName());
        copyConstructor.setName("modified data1"); // copyConstructor 객체의 변수 수정
        copyFactory.setName("modified data2"); // copyFactory 객체의 변수 수정
        System.out.println("original 객체의 name :" + original.getName());
        System.out.println("copyConstructor 객체의 name:" + copyConstructor.getName());
        System.out.println("copyFactory 객체의 name:" + copyConstructor.getName());

    }
}

//출력 결과
original 객체의 name :original data
original 객체의 name :original data
copyConstructor 객체의 name:modified data1
copyFactory 객체의 name:modified data1

위의 코드 예제를 통해 깊은 복사가 잘 이뤄졌음을 알 수 있습니다.

 

정리


  얕은 복사 깊은 복사
장점 빠르고 간결하다. 객체 자체를 복사하기 때문에 독립된 새로운 객체로 다형성을 부여하여 사용하거나 재정의할 수 있다.
단점 바로 위의 다형성의 장점이 존재하지만 값을 참조하는 것이므로 원본 객체에 종속적인 단점도 존재한다.
원본 객체가 수정되는 경우 복사 객체가 원본 객체와 동일하게 변동이 생긴다.
모든 인스턴스 값을 갖고 오기 때문에 얕은 복사에 비해서 상대적으로 느리고 복잡하다.

 

 


Reference

https://zzang9ha.tistory.com/372

https://okeybox.tistory.com/147