LALA's blog
Collection 복사 방법 - 방어적 복사, unmodifiable, copyOf 본문
미션을 수행하면서 객체가 가지고 있는 Collection 을 get 해오는 경우가 종종 있었다.
그 경우 원본 Collection 의 참조값을 넘겨주면 어떻게 될까?
복사본의 Collection 을 add() 혹은 remove() 를 통해 컬렉션 요소를 변경시킬 경우 어떤 문제가 발생할까?
복사본은 원본 Collection 과 동일한 참조값을 가지고 있기 때문에 원본 Collection 의 요소들도 변경되게 된다.
이는 객체지향의 캡슐화를 완벽히 무너뜨리는 방법이므로 주의해야할 점이다. (getter 를 안 쓰도록 해야 하지만..)
1. 방어적 복사
생성자를 통해 초기화하거나 내부의 객체를 반환할 때, 새로운 객체로 감싸서 복사해주는 방법이다. 외부와 내부에서 주소 값을 공유하는 인스턴스의 관계를 끊어주기 위함이다.
public List<Car> getCars() {
return new ArrayList<>(cars);
}
이처럼 원본 cars 와 주소 공유를 끊어냈기 때문에, 외부에서 getCars() 로 얻은 List 의 요소를 추가, 삭제하더라도 원본 cars 는 변경되지 않는다.
그렇다면 깊은 복사일까?
아니다. Collection 의 주소만 끊어줬을 뿐, 가지고 있는 각각의 객체들의 주소는 공유되어 있다.
2. Unmodifiable Collection
Unmodifiable Collection 을 이용했을 경우 외부에서 변경 시 예외 처리되기 때문에 안전하게 보장할 수 있다.
unmodifiableList() 메서드를 통해 리턴되는 리스트는 읽기 용도로만 사용할 수 있으며, set(), add(), addAll() 등의 리스트에 변경을 가하는 메서드를 호출하면 UnsupportedOperationException 이 발생한다. (하지만 이는 컴파일 에러로 잡아낼 수 없기 때문에 외부에서 사용 시 혼란을 줄 수 있으니 유의해야 한다.)
하지만 unmodefiable 은 수정을 막는 것이 핵심 기능이다. 따라서 원본 객체와의 주소가 공유되어 있어 원본 객체가 변경되었을 경우 동일하게 영향을 받는다.
당연히 깊은 복사도 아니다.
3. copy.of
copyOf() 는 Immutable Collection 을 반환한다.
public List<Car> getCars() {
return List.copyOf(cars);
}
// List.java
static <E> List<E> copyOf(Collection<? extends E> coll) {
return ImmutableCollections.listCopy(coll);
}
// ImmutableCollections.java
static <E> List<E> listCopy(Collection<? extends E> coll) {
if (coll instanceof AbstractImmutableList && coll.getClass() != SubList.class) {
return (List<E>)coll;
} else {
return (List<E>)List.of(coll.toArray());
}
}
copyOf() 의 경우 원본 객체와의 주소 공유도 끊어주고, unmodifiable 로 만들어서 반환한다.
하지만 가지고 있는 객체들의 주소 공유는 copyOf() 또한 막을 수 없다. 얕은 복사이다.
외부에서 가져온 Collection 을 사용할 경우 (ex. 생성자의 파라미터) 방어적 복사만으로 해결할 수 있을 것 같다. 외부에서 넘겨준 후 내부 요소를 변경될 수 있기 때문에 방어적 복사를 통해 한 번 끊어주고 검증하도록 한 예시이다.
public Players(List<Player> players) {
players = new ArrayList<>(players);
validatePlayers(players);
this.players = players;
}
외부로 Collection 을 넘겨줄 경우에는 원본과의 주소 공유도 끊어주고, 수정을 막도록 copyOf() 로 넘겨줘야겠다.
public List<Player> getPlayers() {
return List.copyOf(players);
}
이렇게 살펴보니 자바 API 로는 깊은 복사를 수행할 수 없다는 것을 알게 됐다. 따라서 외부 혹은 내부로부터 변경에 취약하지 않도록 내부 요소들이 불변 객체여야 함을 다시 한번 느끼게 됐다.
우연히 알게 된 내용
List.of() 정적 팩토리 메서드는 Immutable Collection 을 반환한다 ! 😲
static <E> List<E> of() {
return ImmutableCollections.emptyList();
}
'개발 > 자바' 카테고리의 다른 글
Exception (0) | 2022.04.16 |
---|---|
HashMap (0) | 2022.04.16 |
equals() 와 hashCode() (0) | 2022.04.16 |