| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 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 |
- Stack
- 알기쉬운 알고리즘
- Graph
- 윤성우의 열혈 자료구조
- Selection Sorting
- 이것이 자바다
- R
- 윤성우 열혈자료구조
- Algorithm
- JSON
- insertion sort
- stream
- 혼자 공부하는 C언어
- 메모리구조
- 이스케이프 문자
- C 언어 코딩 도장
- coding test
- datastructure
- ㅅ
- s
- C programming
- Serialization
- list 컬렉션
- buffer
- Today
- Total
Engineering Note
[Java] 깊은복사와 얕은복사 그리고 방어적복사 본문
List 변수가 있을 때, 새로운 List 변수에 값을 할당하기만 하면, 두 변수는 같은 List를 가리키고, 하나의 List를 변경하면 다른 List도 변경되게 된다. 사실상 하나의 List기 때문에 두 List가 변하는건 아니고 사용하는 쪽은 변하는 것처럼 보일 수 있다.
이러한 복사를 얕은 복사라고 한다.
그렇다면 새로운 List를 만들어서,
import java.util.ArrayList;
import java.util.List;
class Item {
String name;
int price;
public Item(String name, int price) {
this.name = name;
this.price = price;
}
@Override
public String toString() {
return "[" + name + ": " + price + "원]";
}
}
public class CopyComparison {
public static void main(String[] args) {
// 1. 초기 원본 리스트 설정
List<Item> originalList = new ArrayList<>();
originalList.add(new Item("사과", 1000));
// ---------------------------------------------------------
// CASE 1: 얕은 복사 (Shallow Copy) - 주소값만 그대로 복사
// ---------------------------------------------------------
List<Item> shallowCopy = originalList;
// ---------------------------------------------------------
// CASE 2: 방어적 복사 (Defensive Copy) - 새로운 바구니만 생성
// ---------------------------------------------------------
List<Item> defensiveCopy = new ArrayList<>(originalList);
// ---------------------------------------------------------
// CASE 3: 깊은 복사 (Deep Copy) - 새로운 바구니 + 새로운 객체 생성
// ---------------------------------------------------------
List<Item> deepCopy = originalList.stream()
.map(item -> new Item(item.name, item.price))
.toList();
// ---------------------------------------------------------
// [테스트 1] 원본 리스트에 새로운 상품 '포도'를 추가해봅시다. (바구니 수정)
// ---------------------------------------------------------
originalList.add(new Item("포도", 3000));
System.out.println("=== 1. 바구니에 '포도' 추가 후 ===");
System.out.println("원본: " + originalList);
System.out.println("얕은 복사: " + shallowCopy); // 원본과 똑같이 포도가 생김 (위험)
System.out.println("방어적 복사: " + defensiveCopy); // 포도가 없음 (바구니 분리 성공!)
System.out.println("깊은 복사: " + deepCopy); // 포도가 없음 (바구니 분리 성공!)
// ---------------------------------------------------------
// [테스트 2] 원본 리스트의 0번 아이템 '사과'의 가격을 2000원으로 변경 (알맹이 수정)
// ---------------------------------------------------------
originalList.get(0).price = 2000;
System.out.println("\n=== 2. '사과' 가격을 2000원으로 수정 후 ===");
System.out.println("원본: " + originalList);
System.out.println("얕은 복사: " + shallowCopy); // 2000원으로 변함
System.out.println("방어적 복사: " + defensiveCopy); // 2000원으로 변함 (알맹이는 공유 중!)
System.out.println("깊은 복사: " + deepCopy); // 1000원 유지 (완벽한 독립!)
}
}
다른 예시
웹 어플리케이션을 개발할 때 자주 등정하는 패턴인 Entity를 DTO로 변환하는 코드입니다.
List<Order> orders = orderRepository.findOrdersByEmail(email, pageable);
for(Order order : orders){
OrderHistDto orderHistDto = new OrderHistDto(order);
List<OrderItem> orderItems = order.getOrderItems();
for(OrderItem orderItem : orderItems){
ItemImg itemImg = orderItem.getItem().getItemImgs().stream()
.filter(img -> "Y".equals(img.getRepImgYn()))
.findFirst()
.orElse(null);
OrderItemDto orderItemDto = new OrderItemDto(orderItem, itemImg.getImgUrl());
orderHistDto.addOrderItemDto(orderItemDto);
}
orderHistDtoList.add(orderHistDto);
}
Orders의 요소 order를 전혀 다른 인스턴스 OrderHistDto로 만들어주는 깊은 복사입니다.
OrderHistDto orderHistDto = new OrderHistDto(order);
order의 리스트 필드 'List<orderItem> orderItems'를 새로운 orderItems라는 리스트 참조변수에 할당하는 얕은 복사입니다. 새로 성언한 orderItems는 order내부의 'List<orderItem> orderItems'와 같은 List 인스턴스를 가리키고 있습니다.
List<OrderItem> orderItems = order.getOrderItems();
이렇게 얕은 복사를 통해 만들어진 orderItems의 각 요소를 새로운 인스턴스 OrderItemDto로 만들어주는 깊은 복사입니다.
하지만 여기서 전달해준 itemImg의 imgUrl String 객체는 얕은 복사 형태로 전달됩니다. 문자열은 참조변수만 전달해주었기 때문입니다. 그런데 String의 중요한 특징인 immutable(불변성) 특성 때문에 얕은 복사지만 깊은 복사 같은 효과가 있습니다. 우리가 배열을 복사할 때 깊은 복사를 하는 이유는 원본 데이터가 수정되었을 때 새로운 데이터가 영향을 받지 않게 하기 위합입니다.
다행히도 String은 immutable 객체이기 때문에 얕은 복사를 하더라도 원본 데이터가 변경될 때 새로운 문자열 객체가 만들어지고 원본 데이터를 참조하는 변수는 새로 만들어준 문자열 객체를 가리키기 때문에 처음에 원본데이터의 전달을 받은 새로운 참조변수였던 데이터는 여전히 original 문자열 값을 유지할 수 있습니다.
OrderItemDto orderItemDto = new OrderItemDto(orderItem, itemImg.getImgUrl());
그리고 orderItemDto는 orderHistDto에 얕은 복사를 합니다. 여기서 얕은 복사는 orderItemDtoList 관점에서 얕은 복사지만 orderItemList입장에서는 깊은 복사입니다. 결국 최종적으로 깊은 복사가 이루어졌습니다.
private List<OrderItemDto> orderItemDtoList = new ArrayList<>(); //주문 상품 리스트
public void addOrderItemDto(OrderItemDto orderItemDto){
this.orderItemDtoList.add(orderItemDto);
}
orderHistDto.addOrderItemDto(orderItemDto);
'Programming Language > Java' 카테고리의 다른 글
| [Java] 동시성 제어테스트를 위한 멀티쓰레드 코드 흐름 파악하기 (0) | 2026.01.24 |
|---|---|
| [Java] 문자열은 얕은 복사(참조 변수 할당)로 참조해도 괜찮은 이유 (0) | 2026.01.15 |
| [Java] Optional()에서 반환타입에서 .get()이 안좋은 이유와 예외처리를 하는 이유 (0) | 2026.01.09 |
| [Java] default 접근제어자와 테스트코드 패키지 (0) | 2026.01.08 |
| [Java] 동일성(Identity)과 동등성(Equality) (0) | 2025.12.23 |