List를 immutable 하게 변경시켜야 할 일이 있어서, UnmodifiableCollection을 쓰게 되었는데요. 이 친구들에 대해서 간단하게 알아보도록 하겠습니다.
아래와 같은 listBuilder 클래스가 있습니다.
list에 무언가를 넣고, 8번째 줄에서 Collections.unmodifiableList를 호출합니다. 이 때 어떤 일이 일어날까요?
Collection class의 1289번째 줄에 접근합니다. 저는 ArrayList를 넣었으니, RandomAccessList 생성자를 호출할 겁니다.
1397번째 줄에서 부모 생성자를 호출합니다.
1304번째 줄에서 또 부모 생성자를 호출합니다. 여기서 주목할 만한 것은 1301번째 줄의 list는 제가 넣었던 ArrayList의 참조 값이라는 점입니다. 여기까지 그림으로 도식화 시켜 보면 아래와 같습니다.
Collections의 unmodifiableList(li)를 호출하면, 결국 하는 일은 li를 wrapping한 객체를 리턴합니다.
결과값을 받은 immutable을 보면, "chogahui"와 "05"가 들어갔음을 단번에 알 수 있습니다. 일반적인 Collection하고 차이가 있는 것은, sort를 한다던지, 자료구조의 변형이 일어나는 경우에는 여지없이 Exception을 날린다는 것입니다. 예를 들어서, immutable에 "3"이라는 것을 추가해 보도록 하겠습니다.
9번째 줄에 immutable에 "03"이라는 것을 추가하였습니다.
그랬더니, UnSupportedOperationException이 뜹니다. 이는 지원되지 않는 연산이기 때문입니다. 표준 예외를 사용하라는 아이템에 언급되는 예외이니 알아두시면 좋을 듯 싶습니다. Collections의 1057번째 줄에서 예외가 발생하였는데요. 이게 왜 엉뚱한 것 같은 클래스에서 발생했을까요? 먼저, UnmodifiableRandomAccessList 클래스는 UnmodifiableList을 extends를 합니다.
그리고, UnmodifiableList는 UnmodifiableCollection를 extend 합니다. 이 관계를 그림으로 나타내면 아래와 같습니다.
여기서, 노란색으로 표시한 클래스 안에서는 어떤 Exception도 throw 하는 게 보이지 않습니다. 위에서부터 올라가면서 봐야 겠습니다. 문제의 add(E). 이 부분이 UnmodifiableList에 있나요?
없네요. add(E)는 어디에 있을까요? UnmodifiableList는 UnmodifiableCollection이니, 변경할 수 없는 collection 클래스 안에 있을 겁니다.
1057번째 줄에 있는 메서드를 수행하면 UnsupportedOperationException을 던질 겁니다. 그러니 예외가 발생할 겁니다. 즉, 자료구조를 변형하려고 시도하거나, 다른 값으로 바꾸려고 시도하는 경우에는 예외가 던져진다는 점이 다릅니다. 일종의 방화벽인 셈입니다. 그런데, 이것만 믿으면 안되는 이유는..
이런 경우는 어떨까요?
li에 "chogahui"하고, "05"를 넣었습니다. 그리고, immutable에 li를 넣은 다음에 다시, li에 03을 넣었습니다. 이러면 어떨까요?
이 상황을 그림으로 그려보면 위와 같습니다. 변경할 수 없는 객체에서 "chogahui", "05"가 저장되어 있는 List에 접근하는 방법 말고도, direct에서 접근하는 방법이 있습니다. 그러면, 우리는 편하게 10번째 줄처럼 바꿔버리면 됩니다. 03을 추가해 볼까요?
그러면, 위와 같이 변형이 되어 버립니다. 이는, UnmodifiableRandomAccessList를 생성할 때, 필드 li를 얕은 복사 했기 때문입니다. 쉽게 비유하면, 방화벽으로 접근하는 방법 말고, 우회 루트로 접근해서 값을 변경했다는 겁니다. 이 상황을 막으려면, Wrapping 된 필드 중에, 중요한 필드인 Collection 객체를 가리키는 필드를 함부로 노출하면 안 됩니다.
요런 식으로 li는 생성할 때만 잠깐 쓰고, 외부에서 안에 있는 객체에 접근할 방법이 없게 만들어야 합니다. 외부에서는 이렇게 쓰면 되겠네요.
그렇게 크게 어려운 것은 없어 보이네요. 그런데, 이것만 했다고 또 끝이 아닙니다. 여전히 변할 수 있는 가능성이 있습니다. List는 참조값을 저장합니다. set 연산을 했을 때, exception이 떨어지더라. 라는 말은 이 말과 동일합니다.
List 내에 있는 원소가 참조하고 있는 객체가 바뀔 때 떨어지더라. 이 말인 즉슨, 참조하고 있는 객체의 내용이 바뀌는 것은 별로 상관이 없다는 의미입니다. 가리키고 있는 것이 바뀌면 모를까.
예를 들어서, immutable List가 StringBuilder 객체들을 담고 있다고 해 보겠습니다.
사실, wrapped이 된 list에 직접 접근하는 건 별 문제가 안 될 수도 있습니다. 중요한 것은, 그 안에 있는 내용에 접근해서, 그것을 직접 바꿔버릴 수 있다는 것입니다. StringBuilder는 불변 객체가 아닙니다.
그렇기 때문에, "chogahui"와 "05"를 담은 list가 변경되면 안 된다는 의도가 실패하게 됩니다.
'레퍼런스 > 분석' 카테고리의 다른 글
java countrunandmakeascending 메소드 : 키가 거꾸로 들어온 듯한 이유 (0) | 2021.02.04 |
---|---|
java 두 배열 비교할 때 어떻게 하면 좋을까요? (4) | 2021.01.19 |
Java의 String.valueOf 메서드의 동작과 lombok의 ToString 어노테이션의 스택 오버플로우 (0) | 2020.11.05 |
잘못 알고 있었던 java for each 구문과 modcount 필드 (2) | 2020.10.17 |
왜 mutable한 객체를 java map의 키 값으로 삼으면 조심해야 할까요? (0) | 2020.10.09 |
최근댓글