자바에서 객체를 복사하는데 꼭 알아둬야 할 개념이 '깊은복사(Deep Copy)'와 '얕은복사(Shallow Copy)'의 차이점이다. 이 두 개념의 차이를 명확하게 알고 있어야 예기치 못 한 버그를 발생시키기 않을 수 있다.
우선 깊은복사와 얕은복사의 정의는 다음과 같다.
얕은복사(Shallow Copy)
객체를 복사할 때, 객체가 가지고 있는 필드의 값들을 단순히 복사한다. 기본형(Primitive Type) 값들은 복사가 될 것이고, 참조형 변수는 같은 객체를 가리키게 된다.
깊은복사(Deep Copy)
객체를 복사할 때, 객체가 가지고 있는 필드의 값을 복사해준다. 이 때, 기본형 값들은 그대로 복사가 되며 참조형 변수의 경우 변수가 참조하는 객체에 대해서도 새롭게 복사해서 만들어준다.
깊은 복사와 얕은복사의 차이점을 알아보기위해 다음 클래스를 구현해보자.
public class CopiableObject {
private String name;
private int value;
private String[] array;
public CopiableObject(String name, int value, String[] array) {
this.name = name;
this.value = value;
this.array = array;
}
public CopiableObject shallowCopy() {
return new CopiableObject(this.name, this.value, this.array);
}
public CopiableObject deepCopy() {
return new CopiableObject(this.name, this.value, this.array.clone());
}
public String getName() {
return name;
}
public int getValue() {
return value;
}
public String[] getArray() {
return array;
}
}
이 클래스에는 깊은복사를 수행하는 deepCopy() 메소드와 얕은복사를 수행하는 shallowCopy() 메소드가 존재한다. 얼핏보면 비슷해보이지만 두 복사는 차이점이 있다. 이 클래스를 사용하는 예제를 통해 알아보겠다.
public class Example {
public static void main(String[] args) {
String[] array = {"element1", "element2", "element3"};
CopiableObject object = new CopiableObject("Dave", 27, array);
// 객체를 얕은 복사
CopiableObject copiedObject = object.shallowCopy();
// 원래 객체의 배열을 수정
object.getArray()[0] = "newElement";
// 복사된 객체의 배열에서 출력
System.out.println(copiedObject.getArray()[0]);
}
}
코드를 살펴보면 얕은복사를 수행할 객체 object를 만들고, 이 객체를 위에서 구현한 shallowCopy() 메소드를 이용해서 얕은 복사를 해준다.
이후 원래 객체의 배열에서 첫 번째 엘리먼트를 바꿔줬다. 그리고 복사된 copiedObject에서 배열을 꺼내와 첫 번째 엘리먼트를 출력했다. 결과는 원래 객체에서 수정한 수정사항이 복사된 객체에도 적용되었다.
이 코드에서 shallowCopy() 메소드를 deepCopy() 메소드로 바꿔보자.
public class Example {
public static void main(String[] args) {
String[] array = {"element1", "element2", "element3"};
CopiableObject object = new CopiableObject("Dave", 27, array);
// 객체를 얕은 복사
CopiableObject copiedObject = object.deepCopy();
// 원래 객체의 배열을 수정
object.getArray()[0] = "newElement";
// 복사된 객체의 배열에서 출력
System.out.println(copiedObject.getArray()[0]);
}
}
복사하는 방법을 깊은 복사로 바꿨을 뿐인데 결과가 달라졌다. 깊은복사에서는 원본에 대한 수정이 복사된 객체에 영향을 미치지 않았다. 결론부터 말하자면 깊은 복사에서는 배열이 통째로 복사되었기 때문에 영향을 미치지 않은 것이다.
그림으로 보면 정말 간단하다.
우선 얕은복사에서 객체가 복사될 때, 배열에 대한 참조값만 복사가 된다. 메모리 상에 배열은 하나만 존재하고 이 배열에 대한 주소값만 복사가 되기 때문에 원본 object 변수를 통해서 수정한 값이 copiedObject 에도 적용되는 것이다.
깊은복사에서는 객체가 참조하고 있는 또 다른 객체인 배열까지 복사한 다음 그 안쪽에 있는 내용을 모두 복사하기 때문에 원본 object 변수를 통해서 수정한 값이 copiedObject 변수에서 참조하는 값에 영향을 미치지 않는다.
부작용이 없는 코드를 작성하려면 이 두 가지 복사에 대해서 염두에 두어야 한다. 일반적으로 깊은복사(Deep Copy)는 데이터들을 안전하게 처리할 수 있다. 다른 곳에서의 참조가 객체를 수정할 염려가 없기 때문이다.
반대로 얕은복사(Shallow Copy)는 빠르다. 객체의 참조를 따라가면서 복사하지 않기 때문이다. 만약 참조하는 객체가 불변(Immutable)의 성질을 가지고 있는 객체라면 깊은복사를 할 이유는 없다. 즉, 한번 만들어지고 수정이 되지 않는 형태라면, 얕은복사를 토해 메모리 사용량도 줄일 수 있고, 빠른 성능도 얻을 수 있게 된다.
댓글