본문 바로가기
Java/객체 지향

방어적 복사

by 정구정구 2026. 6. 15.

방어적 복사가 필요한 이유

 객체 내에 List 같은 Collections 객체를 필드로 가지면 final로 선언해도 외부에서 .add로 조작이 가능하다. 이런 조작을 방어하기 위해 방어적 복사가 필요하다.

public class Store {
    private final String name;
    private final List<Item> items;

    public Store(String name, List<Item> items) {
        this.name = name;
        this.items = items;          // 외부 리스트를 그대로 받음
    }

    public List<Items> getItems() {
        return items;                 // 내부 리스트를 그대로 반환
    }
    
    public void addItems(Item item) {
    	if (checkSafe(item)) items.add(item); // 안전 체크
    }
}

 

 위 코드에서 새 item을 items에 추가하기 위해서 checkSafe()라는 함수를 거쳐야 하도록 의도되어 있다.

List<Item> items = new ArrayList<>();
Store drugStore = new Store("약국", items);

items.add(불법약품);                // 외부에서 내부 상태 조작 가능
drugStore.getItems().add(불법약품); // getter로 받은 리스트도 그대로 조작 가능

 

하지만 위와 같은 방법으로 addItems()를 거치지 않고 items에 item을 추가해버릴 수 있다.

 

 

입구를 막는 방어 - 생성자 복사

    public Store(String name, List<Item> items) {
        this.name = name;
        this.items = new ArrayList<>(items);
    }

 

위와 같이 생성자에서 새 리스트로 복사해서 가져오면 items.add(불법약품)를 해도 내부에 영향이 없다.

 

 

출구를 막는 방어 - getter에서 읽기 전용 뷰

public List<Item> getItems() {
    return Collections.unmodifiableList(items);
}

 

위와 같이 코드하면, 호출자가 drugStore.getItems().add(불법약품) 같이 호출하면 UnsupportedOperationException 에러가 발생한다.

 

 

방어적 복사를 위한 가장 깔끔한 방법 (Java 10+) - List.copyOf()

public Customer(String name, List<Item> items) {
    this.name = name;
    this.items = List.copyOf(items);   // 불변 복사본 (null도 거부)
}

public List<Item> getItems() {
    return items;       // 이미 불변이라 그대로 반환해도 안전
}