바로 전에 작성했던 깊은 복사와 방어적 복사에 대한 포스팅에 이어서...
제가 복사에 대한 내용을 다루는 이유는 사실 제가 딱 하루 전까지만 해도 Collections.unmodifiableXX()이 방어적 복사라고 잘못 알고 있었기 때문이죠...
깊은 복사와 방어적 복사라는 개념을 잘 모르시겠다면 아래 포스팅을 보고 오시면 됩니다!
2022.03.05 - [개발 이야기/Java] - Collection의 깊은 복사와 방어적 복사
Collections.unmodifiableXX()
자 이번 포스팅의 주제 Collections.unmodifiableXX() 이라는 Collections클래스의 팩토리 메서드인데요, 각 컬렉션 종류마다 있어요.
ex) Collections.unmodifiableList(), Collections.unmodifiableMap(), Collections.unmodifiableSet() ...
이 친구들은 컬렉션에 요소를 삽입하거나 삭제하거나 바꿔끼울 수 없는 컬렉션을 반환하는데요
주로 getter로 내부의 리스트를 반환할 때 그 리스트를 변경하지 못하게 할 목적으로 저 메소드로 한번 감싸서 리턴하는 용도로 사용합니다.
'메서드로 감싸서 새로운 변화가 불가능한 새 컬렉션 객체를 리턴한다.'라고 생각해서 저는 이게 방어적 복사를 같이 해준다고 생각하고 사용했었죠.
그런데, 우아한테크코스 미션을 진행하다 리뷰어분께서 방어적 복사에 대해 공부해보라고 그러시더라구요.
그걸 보고 저는 '잉..? 나 방어적 복사 하고 있는데..?' 하고 생각했는데... 하면서 혹시나 내가 잘못 알고 있는게 있나..? 있다면 가장 먼저 의심되는 부분이 저 Collections.unmodifiableXX()을 사용하는 곳이었죠.
코드를 까보면서 저 친구의 동작을 살펴볼까요?
Collections.unmodifiableList()를 들어가봤더니 'UnmodifiableList'라는 클래스를 반환하는군요.
바로 아래에 있는 클래스네요. 생성자를 보면 매개변수로 받은 리스트를 얕은 복사해서 보관하는걸 볼 수 있어요.
즉 원본 컬렉션을 완전히 참조하고 있기 때문에 요소를 수정하는 메소드 호출만 막았을 뿐 원본과의 관계가 있다는거죠...
그럼 이렇게 생각할 수 있어요. '아 원본 참조하는게 뭐..? 어차피 변경이 막혔다며?'
변경이 막힌건 반환받은 UnmodifiableList이고 원본은 리스트는 변경이 가능합니다.
코드로 살펴볼까요?
@Test
@DisplayName("unmodifiableList 테스트")
void collectionUnmodifiableList() {
List<String> strs = new ArrayList<>();
strs.add("a");
strs.add("b");
strs.add("c");
List<String> copy = Collections.unmodifiableList(strs);
strs.add("d");
assertThat(strs).isEqualTo(copy);
}
학습 테스트를 작성해봤습니다. Collections.unmodifiableList()로 UnmodifiableList를 받은 후에 원본에 요소를 추가했어요.
만약 방어적 복사를 했다면 원본과 copy가 동등성을 만족할 수 없었을 텐데, 저 테스트는 isEqualTo를 통과합니다.
바로 원본에 요소가 추가되면서 UnmodifiableList은 그 원본을 참조하니 같이 요소가 추가되기 때문이죠.
즉 UnmodifiableList는
아... 그럼 나는 방어적 복사도 하면서 수정을 불가능하게 하려면 방어적 복사 한 후에 Collections.unmodifiableXX()까지 써줘야하는거야?
하실 수 있는데, 이 기능을 하는 정적 팩토리 메서드가 있습니다.
XX.copyOf()
위에서 말한 '방어적 복사 + unmodifable'을 동시에 해주는 메서드들이에요 마찬가지로 컬렉션별로 있어요
unmodifable이라면 요소를 추가하거나 삭제하거나 변경하는 메서드를 호출하면 'UnsupportedOperationException'을 던져야겠죠?
코드로 알아봅시다.
@Test
@DisplayName("copyOf 테스트")
void collectionCopyOf() {
List<String> strs = new ArrayList<>();
strs.add("a");
strs.add("b");
strs.add("c");
List<String> copy = List.copyOf(strs);
strs.add("d");
assertThat(strs).isNotEqualTo(copy);
assertThatThrownBy(() -> copy.add("e"))
.isInstanceOf(UnsupportedOperationException.class);
}
자 위에 Collections.unmodifiableList()을 쓸때와 똑같이 원본에 사용해서 새 List를 얻은 후에 원본을 수정했습니다.
이번엔 원본과 동등하지 않음을 검사하는 isNotEqualTo를 통과합니다. 바로 방어적 복사가 이루어졌기 때문에 원본을 수정해도 새 List에 요소 추가가 없던 것이죠.
그리고 바로 다음 부분은 복사된 List에 요소를 추가하려 하면..?
UnsupportedOperationException이 발생하네요.🤓
결론.
Collections.unmodifiableXX() 메서드는 방어적 복사를 하지 않는다.
방어적 복사를 원하면서 동시에 변경을 막고싶다면 XX.copyOf()를 사용하라!
'Java' 카테고리의 다른 글
Stream.forEach() vs for-each (0) | 2022.03.14 |
---|---|
표준 함수형 인터페이스 (0) | 2022.03.08 |
Collection의 깊은 복사와 방어적 복사 (2) | 2022.03.05 |
추상 클래스와 인터페이스의 용도 차이 (0) | 2022.03.03 |
생성자에 매개변수가 많아도 안무섭게 해주는 Builder! (0) | 2022.02.28 |