
자바 프로그래밍에서 객체를 복사한다는 것은 단순히 변수를 대입하는 것 이상의 의미를 갖습니다. 특히 객체 내부에 또 다른 객체(참조 타입)를 포함하고 있을 때, "어디까지 복사할 것인가"에 대한 정의가 명확하지 않으면 예기치 못한 사이드 이펙트(Side Effect)로 인해 데이터 무결성이 깨질 수 있습니다. 오늘 포스팅에서는 자바 메모리 구조의 관점에서 얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)의 메커니즘을 심도 있게 분석하고, 실무에서 어떤 방식을 선택해야 하는지 가이드를 제시하겠습니다.
1. 얕은 복사(Shallow Copy)의 이해
얕은 복사는 객체의 필드 값만을 복사합니다. 필드가 기본 타입(Primitive Type)인 경우 실제 값이 복사되지만, 참조 타입(Reference Type)인 경우에는 메모리 주소값만 복사됩니다. 결과적으로 원본 객체와 복사본 객체가 동일한 내부 객체를 공유하게 되는 구조입니다.
2. 깊은 복사(Deep Copy)의 이해
깊은 복사는 객체가 참조하고 있는 내부 객체들까지 모두 새로운 인스턴스로 생성하여 복사하는 방식입니다. 원본과 복사본은 논리적으로 동일한 데이터를 가지지만, 물리적으로는 메모리 상에서 완전히 독립된 존재가 됩니다. 따라서 어느 한쪽을 수정해도 다른 쪽에 영향을 주지 않습니다.
3. 얕은 복사 vs 깊은 복사 상세 비교
두 복사 방식의 기술적 차이점과 특징을 표로 정리하였습니다.
| 구분 | 얕은 복사 (Shallow Copy) | 깊은 복사 (Deep Copy) |
|---|---|---|
| 복사 대상 | 주소값 (Reference) | 실제 값 (Value) 및 내부 객체 전체 |
| 독립성 | 불완전 (내부 객체 공유) | 완전 독립 |
| 성능 및 자원 | 빠름, 메모리 소모 적음 | 느림, 메모리 소모 많음 (재귀적 생성) |
| 구현 난이도 | 매우 낮음 (Object.clone() 등) | 상대적으로 높음 (복사 생성자, 직렬화 등) |
| 위험 요소 | 원본 수정 시 복사본 데이터 오염 가능 | 순환 참조 발생 시 무한 루프 위험 |
4. Sample Example: 구현 방식의 차이
얕은 복사 예시 (Object.clone 활용)
class Address { String city; }
class User implements Cloneable {
String name;
Address address;
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone(); // 얕은 복사 수행
}
}
깊은 복사 예시 (복사 생성자 활용 - 추천 방식)
class Address {
String city;
Address(Address other) { this.city = other.city; } // 복사 생성자
}
class User {
String name;
Address address;
// 깊은 복사를 수행하는 생성자
User(User other) {
this.name = other.name;
this.address = new Address(other.address); // 내부 객체도 새로 생성
}
}
5. 독창적인 인사이트: 왜 clone()보다 복사 생성자인가?
자바의 창시자 중 한 명인 조슈아 블로크(Joshua Bloch)는 Object.clone()의 사용을 지양하라고 권고합니다. Cloneable 인터페이스는 생성자 없이 객체를 생성하는 비정상적인 메커니즘을 가지고 있으며, 체크드 예외 처리가 번거롭기 때문입니다.
따라서 실무에서는 복사 생성자(Copy Constructor)나 복사 팩토리(Copy Factory)를 사용하는 것이 코드의 가독성을 높이고, final 필드와 충돌하지 않는 가장 안전한 깊은 복사 방법입니다.
6. 결론: 어떤 복사를 선택해야 하는가?
단순히 데이터를 읽기만 하는 용도라면 성능이 우수한 얕은 복사가 유리합니다. 하지만 복사된 데이터를 수정해야 하거나, 원본 데이터의 불변성(Immutability)을 보장해야 하는 비즈니스 로직이라면 반드시 깊은 복사를 수행해야 합니다. 특히 멀티스레드 환경의 스프링 빈(Spring Bean) 처리나 복잡한 엔티티(Entity) 조작 시 이 차이를 간과하면 치명적인 버그로 이어질 수 있음을 명심하십시오.
내용 출처:
- Effective Java 3rd Edition (Joshua Bloch) - Item 13: Override clone judiciously
- Java Language Specification (JLS) SE 17: Chapter 12. Execution
- Baeldung: Java Deep Copy vs Shallow Copy Guide
'Language > Java' 카테고리의 다른 글
| [JAVA] 파일 경로 지정 시 절대 경로와 상대 경로의 차이는? 유연한 설계를 위한 가이드 (0) | 2026.01.21 |
|---|---|
| [JAVA] Cloneable 인터페이스와 clone() 메서드 사용법 : 얕은 복사의 함정과 해결책 (0) | 2026.01.20 |
| [JAVA] Path와 Paths 클래스(NIO.2)의 특징 : 현대적 파일 시스템 처리 기법 (0) | 2026.01.20 |
| [JAVA] System.out.println을 실제 서비스에서 지양하는 이유는? 성능과 운영의 관점 (0) | 2026.01.20 |
| [JAVA] 로그(Logging) 라이브러리(SLF4J, Logback)를 사용하는 이유와 실무적 가치 (0) | 2026.01.20 |