Java에서 복사 가 어떻게 작동하는지 이해하려면,
먼저 변수와 객체가 메모리 상에 어떻게 저장 되는지부터 알아야 한다.
여기서 복사와 가장 밀접한 부분은 스택(Stack)과 힙(Heap)이다.
스택에는 객체의 참조값(주소)이 저장되고,
힙에는 new로 생성한 실제 객체 인스턴스가 저장된다.
즉, JVM 메모리 공간은
참조값(주소)→스택,실제 데이터→힙에 있는 구조를 가지며
이 구조가 바로 복사 시 어떤 값이 복사되는지를 결정하는 핵심이다.
얕은 복사(Shallow Copy) 는 스택에 있는 참조값(주소값)만 복사 하여, 두 변수가 같은 힙 객체를 공유 하는 방식이고,
깊은 복사(Deep Copy) 는 힙에 있는 객체를 새롭게 생성해서 스택에 새로운 참조값을 저장 하는 방식이다.
아래 String 형태의 name과 address를 필드로 가지는 Person 클래스가 있다.
private static class Person {
private String name;
private String address;
public Person(final String name, final String address) {
this.name = name;
this.address = address;
}
public String getName() {
return name;
}
public void setName(final String name) {
this.name = name;
}
public String getAddress() {
return address;
}
public void setAddress(final String address) {
this.address = address;
}
}
Person person1 = new Person("길동", "서울");
Person person2 = person1;
person2.setAddress("부산");
System.out.println(person1.address);
System.out.println(person2.address);
이름이 '길동', 주소가 '서울'인 person1을 생성한 후, person1을 person2에 복사했다.
그 후 person2의 주소를 '부산'으로 변경했다.
System.out.println(person1.address); // 부산
System.out.println(person2.address); // 부산
System.out.println(person1); // example$Person@2f7a2457
System.out.println(person2); // example$Person@2f7a2457
person2의 주소만 변경했음에도 person1의 주소도 함께 '부산'으로 변경됐다. 또한, 두 객체 모두 Person@2f7a2457이라는 동일한 주소 를 가지고 있다는 것을 확인할 수 있다.

이렇게 얕은 복사는 주소값만 복사하여 같은 객체를 공유하는 방식으로
person2는 새로 생성된 것이 아니라, 기존 person1이 가리키는 객체의 주소를 그대로 참조한다. 따라서 person2의 값을 변경하면, 같은 객체를 공유하는 person1의 값도 함께 변경된다.
얕은 복사는 하나의 물체에 이름표만 하나 더 붙인 것이라고 생각하면 이해가 쉽다. 이름표가 늘어나도 물체는 하나뿐이기 때문에, 어떤 이름으로 접근하든 동일한 물체에 영향을 주게 되는 것이다.
Person person1 = new Person("길동", "서울");
Person person2 = new Person(person1.getName(), person1.getAddress());
person2.setAddress("부산");
이번에는 person1을 복사할 때 새로운 Person 객체를 생성하여 person2에 담았다.
person1의 이름과 주소 값을 생성자에 전달해 새로운 Person 객체 person2를 만들었다.
그 후 이전과 동일하게 person2의 주소를 '부산'으로 변경했다.
System.out.println(person1.address); // 서울
System.out.println(person2.address); // 부산
System.out.println(person1); // example$Person@2f7a2457
System.out.println(person2); // example$Person@6d06d69c
이전과 달리 person1의 주소는 여전히 '서울' 그대로이고 person2의 주소만 '부산'으로 변경됐다. 또한 두 객체가 서로 다른 주소값을 가지고 있는 것을 확인할 수 있다.

이렇게 깊은 복사(Deep Copy) 는 기존 객체의 데이터를 바탕으로 새로운 객체를 완전히 복제하여 생성하는 방식이다.
힙 메모리 상에 새로운 객체 인스턴스를 생성하고, 스택에는 해당 객체의 새로운 참조값이 저장된다. 따라서 하나의 객체를 수정해도, 다른 객체에는 전혀 영향을 주지 않는다.
깊은 복사는 같은 설계도를 가지고 새로 물체를 하나 더 만든 것이라고 생각하면 이해가 쉽다.
외형은 같지만, 물리적으로 완전히 다른 물체이기 때문에 하나를 바꾸더라도 다른 쪽에는 영향이 전혀 없다.
얕은 복사는 참조값만 복사해서 같은 객체를 공유하는 방식깊은 복사는 새로운 객체를 생성해서 각자 독립된 객체로 관리하는 방식차이를 이해하지 못하면 의도치 않게 객체가 공유되어 값이 바뀌는 문제가 발생할 수 있다.
결국 복사의 본질의, 객체 간의 독립성 에 대한 문제다.
참조값만 복사되면 하나를 바꾸면 둘 다 바뀌고, 객체 자체를 복사하면 각각 독립적으로 유지된다.