얕은복사 VS 깊은복사

728x90

자바로 개발을 하다보면 객체를 복사할 일이 있다. 이럴 때 나오는 개념이 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy) 개념인데, 두 개념의 차이를 간단하게 말하면 얕은 복사객체의 참조값(주소값)을 복사하고, 깊은 복사객체의 실제 값(value)를 복사한다.

1. 얕은 복사(Shallow Copy)

  • 객체를 복사할 때, 해당 객체만 복사하여 새 객체를 생성한다.
  • 복사된 객체의 인스턴스 변수는 원본 객체의 인스턴스 변수와 같은 메모리 주소를 참조한다.
  • 따라서, 해당 메모리 주소의 값이 변경되면 원본 객체 및 복사 객체의 인스턴스 변수 값은 같이 변경된다.

2. 깊은 복사(Deep Copy)

  • 객체를 복사 할 때, 해당 객체와 인스턴스 변수까지 복사하는 방식.
  • 전부를 복사하여 새 주소에 담기 때문에 참조를 공유하지 않는다.

깊은 복사를 할 때 사용하는 CloneUtils 인터페이스 사용법에 대해 정리하고자 한다.

(특히 주의해야할 점이 있기에 정확히 짚고 넘어가자)

참고한 두 포스팅 중에 '학교에서 학생 신장정보를 관리하는 예제'가 더 눈에 들어오는 것 같아서 따라서 코드를 짜봤다.

public class PhysicalInformation {
    int height;
    int weight;
}
public class Family {
    String name;
    int age;
    boolean isOfficeWorkers; // 직장인 여부
}
public class Student {
    String name;
    int age;
    Family family;
}

깊은복사를 하기 위해서 객체는 Cloneable Interface를 Implement 해야하고 clone 메서드를 오버라이드해야한다.

public class PhysicalInformation implements Cloneable{
    int height;
    int weight;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

이제 테스트를 해보자! 테스트는 깊은복사, 얕은복사 모두 진행한다.

        //test code
        PhysicalInformation physicalInformation = new PhysicalInformation();
        physicalInformation.height = 180;
        physicalInformation.weight = 70;

        PhysicalInformation physicalInformationShalldowCopy = physicalInformation;
        PhysicalInformation physicalInformationDeepCopy = null;

        try {
            physicalInformationDeepCopy = (PhysicalInformation) physicalInformation.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }

        // 값 변경
        physicalInformation.weight = 80;
        physicalInformation.height = 170;

        // 참조값, 값(height, weight) 출력
        System.out.println(physicalInformation.toString() 
                + " height:" + physicalInformation.height 
                + ", weight:" + physicalInformation.weight);

        System.out.println(physicalInformationShalldowCopy.toString() 
                + " height:" + physicalInformationShalldowCopy.height 
                + ", weight:" + physicalInformationShalldowCopy.weight);

        System.out.println(physicalInformationDeepCopy.toString() 
                + " height:" + physicalInformationDeepCopy.height 
                + ", weight:" + physicalInformationDeepCopy.weight);

출력결과는 아래와 같다.

얕은복사를 한 객체의 값은 변경을 한 적이 없지만, 복사를 당한(?) 객체와 같은 참조값을 가지므로 값이 동일하게 변경되는 것을 알 수 있다.

그리고 clone()메서드로 깊은 복사를 진행한 physicalInformationDeepCopy 는 얕은 복사를 한 객체와는 달리 참조값도 다르고 그에 따라 값도 변경되지 않았음을 알 수 있다.

여기까지보면 별로 어렵지 않다는 것을 알 수 있는데, 이것은 기본 자료형, Enum, Immutable과 같이 clone을 지원하는 객체가 아닐 경우에는 별로도 clone이 되기위한 설정이 필요하다.

예로들어 Student의 필드로 Family라는 Wrapping Class가 있을 경우, 이 Wrapping Class에도 Cloneable 인터페이스를 implements하고 clone 메서드를 오버라이드 해준 다음 최상위 객체였던 Student의 clone 메서드를 아래처럼 수정해주면 깊은 복사를 할 수 있다.

public class Family implements Clonealbe {
    String name;
    int age;
    boolean isOfficeWorkers;

    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

// 1차적으로 복사를 진행하고, 맴버 객체 자체를 복사한 다음 대입
public class Student implements Cloneable {
    String name;
    int age;
    Family family;

    @Override
    public Object clone() throws CloneNotSupportedException {
        Student student = (Student)super.clone();
        student.family = (Family)family.clone();
        return student;
    }
}

아직까지 어떤 프로젝트를 진행하면서 이런 사항으로 문제점이 발생했던 적은 없다. (아직 개린이라서 그렇겠지..) 하지만 코딩테스트를 풀 때, 이런 복사가 필요한 경우가 있었고, 이 개념이 익숙하지 않아서 나름 복사랍시고 복사를 했는데 값이 같이 변해서 for문을 반복해가며 새로운 객체를 만들었던 기억이 있다. 이번 포스팅으로 '얕은 복사', '깊은 복사'의 개념을 확립할 수 있었고, 다음에는 이 개념을 꼭 써야겠다는 생각이든다.

자바(Java)로 개발을 하다보면 한번쯤 객체를 복사하는 로직을 작성할때가 있다. 그때마다 나오는 이야기인 Shalldow Copy 와 Deep Copy. 한국어로 표현하면 얕은 복사와 깊은 복사라고 이야기를 하는데 이 두 개념의 차이는 아주 간단하다. 객체의 주소값을 복사하는지, 아니면 객체의 실제 값(value)를 복사하는지.

참조: [JAVA] 깊은 복사(Deep Copy) & 얕은 복사(Shallow Copy)

728x90