본문 바로가기
☕Java/Java 기본

객체를 불변으로 만들기 위한 방어적 복사

by 캔 2023. 12. 15.

참조형 변수를 멤버 변수로 사용하는 자바 객체의 경우 변수 취급에 주의해야 한다. 왜냐하면, 참조형 변수가 불변이 아닐 경우 외부에서 변경 가능해지는 등 부수효과가 크기 때문이다. 여기서는 가변 컬렉션의 예를 들어 이야기해보려고 한다.

 

컬렉션을 반환하는 getter가 단순히 멤버 필드를 그대로 반환할 경우 해당 필드가 참조형 변수이기 때문에 외부에서 요소를 추가, 수정, 삭제할 수 있게 된다. 이는 필드를 private으로 선언하여 객체를 은닉화한 이유를 없애버린다.

 

뿐만 아니라, 객체 생성 시 멤버 필드로 외부 컬렉션을 받아 올 경우, 해당 컬렉션을 외부에서 수정하면 같은 이유로 내부 멤버 컬렉션이 수정될 수 있다.

 

이러한 문제를 해결하기 위해 나온 개념이 방어적 복사이다. 외부에서 컬렉션이 수정될 것을 대비해서 컬렉션의 참조를 아예 끊어버리는 것이다. 위험 상황이 발생할 수 있다는 것을 가정하고 미리 대비하면서 운전하는 방어운전과 일맥상통한다. 방어적 복사를 하는 것은 결국 기존 컬렉션 요소로 구성된 새로운 객체를 만드는 것이다. 이렇게 하면 컬렉션 자체의 참조는 끊어지게 되어 컬렉션을 수정해도 본 객체의 상태에 영향을 끼치지 않는다.

 

구체적인 방법을 알아보자.

 

public class DefensiveCopy {
    private final List<String> elements;

    public DefensiveCopy(final List<String> elements) {
        this.elements = new ArrayList<>(elements);
    }
    
    //...
}

 

생성자에서는 new ArrayList(), new HashMap(), HashSet() 등 새로운 객체를 생성하여 복사를 한다. 여기서는 요소 추가, 수정, 삭제가 가능한 가변 컬렉션을 사용하여 객체 내부에서는 컬렉션을 조작할 수 있도록 한다. getter의 경우, Collection.copyOf() 등을 사용해 UnomodifiableCollection을 반환해도 되지만 생성자에서는 그렇지 않다.

 

public class DefensiveCopy {
    //...
    public List<String> getElements() {
        return List.copyOf(elements);
    }
}

 

getter에서는 생성자와 마찬가지로 new ArrayList(), new HashMap(), HashSet() 등 새로운 객체를 생성하여 사용할 수도 있지만, 그럴 경우 외부에서 사용할 때 요소 추가, 수정, 삭제가 가능하므로 다음 객체 사용 시에도 불변적으로 사용하도록 copyOf()나 각 컬렉션들의 of() 메서드를 사용하여 변경이 불가능한 UnomodifiableCollection을 반환하도록 하자.

'☕Java > Java 기본' 카테고리의 다른 글

자바 지네릭  (0) 2024.05.19
enum 어떻게 잘 활용할까?  (0) 2024.05.08
일급 컬렉션이라는 용어에 대해서  (0) 2023.12.14
날짜, 시간, 포맷팅  (0) 2023.07.11
자바 컬렉션 간 변환(Array, List, Map, Set) 정리  (0) 2022.10.01