Notice
Recent Posts
Recent Comments
Link
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
Tags
- 2024 정보처리기사 수제비 실기
- 자바 중급1편 - 날짜와 시간
- 스프링 mvc2 - 타임리프
- 스프링 mvc2 - 로그인 처리
- 스프링 mvc1 - 스프링 mvc
- 자바의 정석 기초편 ch5
- 자바 고급2편 - io
- 자바의 정석 기초편 ch4
- 스프링 mvc2 - 검증
- 자바로 계산기 만들기
- 2024 정보처리기사 시나공 필기
- 데이터 접근 기술
- 스프링 입문(무료)
- 자바의 정석 기초편 ch7
- 자바의 정석 기초편 ch9
- 자바 기초
- 자바 중급2편 - 컬렉션 프레임워크
- 스프링 트랜잭션
- 자바의 정석 기초편 ch6
- 자바의 정석 기초편 ch1
- 람다
- 자바의 정석 기초편 ch12
- 자바의 정석 기초편 ch14
- 자바의 정석 기초편 ch13
- 자바의 정석 기초편 ch2
- 자바로 키오스크 만들기
- 자바의 정석 기초편 ch11
- @Aspect
- 스프링 고급 - 스프링 aop
- 자바 고급2편 - 네트워크 프로그램
Archives
- Today
- Total
개발공부기록
Shallow Copy와 Deep Copy의 차이?, 자바에서 Deep Copy를 하기 위해서는 무엇을 사용해야 할까? 본문
이론 직접 정리/자바
Shallow Copy와 Deep Copy의 차이?, 자바에서 Deep Copy를 하기 위해서는 무엇을 사용해야 할까?
소소한나구리 2025. 3. 13. 21:01728x90
Shallow Copy
Shallow Copy(얕은 복사)
- 객체를 복사할 때 객체가 가진 필드의 값을 그대로 복사하는 방법을 뜻한다
- 기본 자료형의 경우 값 자체가 복사되며, 객체(참조형)의 경우 참조 주소(메모리 주소값)만 복사된다는 뜻이다
특징
- 원본 객체와 복사본 객체는 서로 다른 객체지만 객체 내부에서 참조하고 객체는 동일한 객체를 가리키게 된다.
- 실제 데이터가 아닌 참조값만 복사하기 때문에 복사 과정이 매우 빠르다
- 기존 객체의 데이터와 공유되기 때문에 추가적인 메모리 사용량이 매우 적어 메모리 사용이 효율적이다
단점
- 복사된 객체와 원본 객체가 내부 데이터를 공유한다는 특성을 모르고 접근할 경우 원본 데이터를 변경하면 복사된 객체도 영향을 받으므로 사이드 이펙트(side-effect) 문제가 발생할 수 있다(Mutable 객체의 경우)
Deep Copy
Deep Copy(깊은 복사)
- 객체를 복사할 때 객체 내부에 존재하는 모든 참조형 필드들까지 독깁적인 새로운 객체로 생성하여 완전히 별개의 객체를 만드는 방법이다
특징
- 복사된 객체와 원본 객체가 완전히 독립적이기 때문에 한쪽을 수정해도 다른 쪽에 영향을 주지 않는다
- 원본과 복사된 객체간의 모든 데이터가 완전히 독립적이기 때문에 사이드 이펙트를 걱정할 필요가 없어 안전하다
- 객체 내부 데이터가 외부에 노출되지 않아 데이터 캡슐화가 자연스럽게 유지된다
단점
- 새로운 메모리를 할당하고 데이터를 복사해야 하기 때문에 데이터가 크고 복잡한 객체에 대해 복사하는 시간이 많이 걸리게 되어 빈번한 Deep Copy는 애플리케이션의 성능을 저하시킬 수 있다
- 모든 데이터를 복제하기 때문에 메모리 소비량이 증가하게 된다
- Deep Copy를 사용하려면 직접 모든 참조 필드를 일일히 복사하거나 직렬화(Seriallization) 같은 메커니즘을 사용해야 하며 객체 구조가 변경되면 복사 로직도 함께 수정해야 하기 때문에 유지보수 비용이 증가할 수 있다.
자바에서 Deep Copy를 하기 위한 방법
1. 복사 생성자(Copy Constructor) - 순수 자바일 때 간편하므로 권장

- 객체를 복사할 때 같은 타입의 객체를 매개변수로 받아서 새 객체를 생성하는 생성자를 만들어서 Deep Copy를 한다.
- CopyConstructor 클래스에는 String name과 int[] score가 필드로 존재할 때 일반적으로 값을 초기화하는 생성자는 매개변수의 값을 각 필드에 대입하면서 초기화한다.
- 복사 생성자는 생성자의 매개변수로 동일 클래스 타입의 객체를 받아서 필드를 꺼내 복사한다.
- 이때 name 필드는 String 타입이므로 이미 불변객체라 그대로 값이 복사된다.
- 문제는 int[]인 socre인데 socre는 int[]의 데이터가 저장된 메모리 주소값을 가지고 있어 그냥 대입하면 Shallow Copy가 되기 때문에 clone()메서드를 사용하여 Deep Copy를 구현할 수 있다.


- 실행 테스트를 위해 CopyConstructor student = new CopyConstructor(값 초기화, 배열 초기화)로 객체를 생성하고, new CopyConstructor(student)를 통해 생성자를 통해서 객체를 복사해서 출력해보면 서로 다른 주소값을 가지고 있는 것을 확인할 수 있다.
- 복사된 studentCopy의 score[0]의 값을 변경해보면 원본 객체인 student의 score 값은 그대로이지만 studentCopy의 score값은 변경되어 서로 독립적으로 객체가 동작하는 것을 확인할 수 있다
2. Cloneable 인터페이스의 clone() 메서드 오버라이딩
- 위에서 int[]인 score 변수를 복사할 때 사용했던 clone() 메서드를 직접 오버라이딩해서 구현할 수 있다.
- 직접 구현하고자 하는 객체에 Coloneable 인터페이스를 구현하고 clone()메서드의 로직을 직접 작성하면 되는데 모든 필드별로 개발자가 직접 복제 로직을 작성하는 것이 번거롭기 때문에 불편하다.
- 단순히 인터페이스의 추상 메서드를 구현하는 방식이기 때문에 예시는 생략한다.
3. 직렬화(Serialization)을 통한 복사

- Java 내장 직렬화를 사용하여 DeepCopy를 구현할 수도 있다.
- DeepCopy가 적용되어야 할 객체에 Serializable 인터페이스를 구현해둔다.
- 해당 인터페이스는 마커 인터페이스로 특별한 기능은 없지만 이 인터페이스를 구현한 객체는 직렬화가 가능한 객체로 인식하게 되며, Serializable 인터페이스가 없는 객체를 직렬화할 경우 오류가 발생한다.(NotSerializableException)

- Serializable를 상속받은 모든 객체를 직렬화를 이용한 Deep Copy를 할 수 있는 deepCopy() 메서드 이다.
- 직렬화를 하기 위해 자바 객체를 바이트(byte)로 변환해야 하는데 메모리상에서 즉시 직렬화와 역직렬화를 처리하기 위해 ByteArrayOutputStream과 ByteArrayInputStream()을 사용하고 이를 통해서 ObjectOutputStream, ObjectInputStream을 생성한다.
- oos.writeObejct(객체)로 객체를 직렬화후 바로 역직렬화를 하여 Deep Copy를할 수 있다


- new SerializationCopy()로 Serializable을 구현한 객체를 생성하고, 해당 객체를 만들어 둔 deepCopy()메서드를 통해 복사를해서 실행해보면 Deep Copy가 성공적으로 된 것을 확인할 수 있다.
- 복사된 studentCopy의 score[0]의 값을 변경해보면 원본에 영향이 없이 독립적으로 studentCopy의 score[0]의 값만 변경되었다.
4. JSON 직렬화 방식(Jackson)을 이용한 Deep Copy - 권장
- 외부 라이브러리이기 때문에 직접 다운받아서 라이브러리를 IDE에 등록해주거나 만약 Maven이나 Gradle의 빌드를 통해서 프로젝트를 생성했다면 의존관계를 추가해야 한다.
- 순수 자바 프로젝트인 경우 외부라이브러리이기 때문에 의존성을 추가할 때 버전 정보를 추가해야 한다.(최신 버전 권장)
- 만약 스프링부트 프로젝트로 프로젝트를 생성했다면 Jackson 라이브러리를 내장하고 있어서 버전 정보 없이 의존성을 추가하면 된다
- 스프링 부트를 사용한다면 보통 spring-web을 의존관계를 등록해서 생성할텐데 그 내부에 Jackson 라이브러리가 포함되어있다.
Maven의 경우
<!-- 순수 자바 프로젝트 일 경우 명시 필수-->
<dependencies>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.17.0</version>
</dependency>
</dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
Gradle의 경우
imimplementation 'org.springframework.boot:spring-boot-starter-web' // 스프링 부트 프로젝트
implementation 'com.fasterxml.jackson.core:jackson-databind:2.17.0' // 순수 자바 프로젝트
보통 Jackson라이브러리를 사용하는 경우는 스프링 부트 환경일 것이기 때문에 스프링 부트로 프로젝트를 생성한 예제를 구현해보았다.

- Jackson 라이브러리를 사용하려면 기본 생성자가 필요하다 - 이 방법을 권장한다.
- 만약 기본 생성자를 사용하지 않을 것이라면 각 생성자의 필드에 @JsonProperty()를 통해 필드 매핑을 명확히 해줘야 Jackson이 역직렬화 할 때 오류가 발생하지 않는다.

- 보통 실무에서는 Util클래스를 별도의 패키지로 Util 클래스들을 모아서 관리하지만 지금은 같은 패키지에 만들었다.
- 스프링 부트 프로젝트에서는 Jackson 라이브러리를 사용하기 위한 ObjectMapper를 기본적으로 스프링 빈으로 등록되어있기 때문에 생성하여 스프링 빈으로 등록할 필요 없이 의존관계 주입을 통해 사용하면 된다
- 주입 받은 mapper를 writeValueAsString(object)로 JSON 문자열로 직렬화 한 다음, mapper.readValue(직렬화, 클래스)를 통해서 역직렬화 하여 새로운 객체로 반환된다



- 애플리케이션을 실행하는 main()메서드가 있는 Application클래스에서 DeepCopyUtil를 주입하면 준비가 모두 끝난다
- 스프링 부트 프로젝트에서 콘솔 출력을 하기 위해 CommandLineRunner 인터페이스를 구현하여 run()메서드를 오버라이딩하고 객체를 deepCopy해보면 원본 Student 객체와 복제된 Student 객체가 독립적으로 별도의 주소값을 가지고 있는 것을 확인할 수 있다.
728x90
'이론 직접 정리 > 자바' 카테고리의 다른 글
자바의 예외, RuntimeException, CheckedException, UncheckedException의 차이점, RuntimeException이 필수 처리 예외가 아닌 이유, 예외 처리 방안 (0) | 2025.03.24 |
---|---|
Java는 Call By Value? Call By Reference? (0) | 2025.03.13 |
Managed - Unmanaged 언어의 차이는 무엇이고 어떤 장, 단점이 있는가 (0) | 2025.03.13 |
불변 객체는 무엇이며 Java에서 어떻게 구현하는가, String 클래스가 불변인 이유 (0) | 2025.03.05 |
Java Final 키워드에 대해서 설명, 각각의 쓰임에 따라 어떻게 동작하는가?(Class, Method, Variable) (0) | 2025.03.04 |