이번에 정리할 내용은 자바에서의 깊은 복사와 얕은 복사 입니다.
int[] arrData = {1,2,3};
int[] arrData2 = arrData;
arrData2[0] = 10;
System.out.println(arrData[0]); -> 10
원한 값은 1이 나오길 바랬지만 원하지 않는 값이 나온 이유를 살펴 봅시다.
깊은 복사(Deep Copy)
는 '실제 값'을 새로운 메모리 공간에 복사하는 것을 의미하며,
얕은 복사(Shallow Copy)
는 '주소 값'을 복사한다는 의미입니다.
복사하려는 원본 객체에 대해서 새로운 단일 객체 또는 새로운 복합 객체를 만들고 원본 객체를 참조한다. 즉, 다양한 복합 멤버 변수를 갖고 있는 객체가 인스턴스가 생성될 때 인스턴스화 되면서 메모리에 할당된 주소의 값을 참조한다. 또한 얕은 복사는 복사 객체가 원본 객체에 종속적이다.
얕은 복사는 주소에 의한 참조 즉, call-by-reference와 유사한 개념이 된다.
장점
- 빠르고 간결하다.
단점
- 바로 위의 다형성의 장점이 존재하지만 값을 참조하는 것이므로 원본 객체에 종속적인 단점도 존재한다.
원본 객체가 수정되는 경우 복사 객체가 원본 객체와 동일하게 변동이 생긴다.
int[] arrData = {1,2,3};
int[] arrData2 = arrData.clone();
arrData2[0] = 10;
System.out.println(arrData[0]); -> 1
이제서야 원하는 값이 나온다.
복사하려는 원본 객체에 대해서 새로운 단일 객체 또는 새로운 복합 객체를 만들고 원본 객체를 대상으로 인스턴스화할 수 있는 클래스 내부의 클래스 변수(static)와 메서드(static)뿐 아니라 인스턴스 값 모두를 복사하여 원본 객체로부터 독립적인 객체를 생성한다.
깊은 복사는 새로운 객체가 원본 객체 자체를 Copy 하는 것이다. 즉, call-by-values와 유사한 개념이 된다.
장점
- 객체 자체를 복사하기 때문에 독립된 새로운 객체로 다형성을 부여하여 사용하거나 재정의할 수 있다.
단점
- 모든 인스턴스 값을 갖고 오기 때문에 얕은 복사에 비해서 상대적으로 느리고 복잡하다.
깊은 복사를 위해 clone() 메서드 대신 객체 직렬화를 이용하거나 복사 생성자를 이용하는 방법이 있습니다. 이 글에서는 복사 생성자를 통한 방법에 대해 알아보겠습니다.
복사 생성자는 생성자의 매개변수로 자기 자신과 같은 타입의 객체를 받아서 작성합니다. 이렇게 작성된 생성자 내에서 새로운 객체의 속성에 원본 객체의 속성을 할당합니다.
이 때 중요한 점은 복사 생성자 내의 레퍼런스들도 복사 생성자의 체인을 통해서 복사해주어야한다는 점입니다.
class Apple {
int size;
public Apple(Apple a){
this.size = a.size;
}
}
class Tree {
int height;
Apple apple;
public Tree(Tree t){
this.height = t.height;
this.apple = new Apple(t.apple);
}
}
Object.clone() 에 비해 복사 생성자는 다음과 같은 장점을 갖습니다.
인터페이스 구현 및 예외 처리 불필요
- Cloneable 및 CloneNotSupportedException
타입 다운캐스팅 불필요
- 오버라이딩 및 상위 타입의 객체의 타입캐스팅 불필요
객체 생성자 제어
- 오버라이딩 및 상위 타입의 객체의 타입캐스팅 불필요
타입 다운캐스팅 불필요
- clone() 메서드 호출 시 생성자를 동작시킬 수 없으나, 복사와 동시에 임의의 생성자를 호출가능따라서 final 멤버도 복사하거나 제어 가능
ex) LinkedList 의 복사 생성자
public LinkedList(Collection<? extends E> c) {
this();
addAll(c);
}
참고
깊은 복사(Deep Copy) vs 얕은 복사(Shallow Copy)
Object.clone(), 얕은 복사, 깊은 복사, 복사 생성자
얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)