이펙티브 자바 Item 58에서는 for-each를 사용을 추천한다. 하지만 for-each를 사용할 수 없는 부분을 명시하고 있었다.
for-each에서는 컬렉션의 원소를 수정할 수 없다고 한다. 이 또한 코딩테스트에서 나도 경험했던 부분이다.
그런데 스터디원이 자세한 예시를 요구하여, 코드를 작성했는데.. 어머나..?? 갑자기 삭제가 되는 것이다.
코드는 아래와 같다.
그런데 놀랍게도 예상하는 결과가 나왔다.
이펙티브 자바의 말에 의하면, 그리고 여러 사람들의 글에 의하면 이것은 불가능했어야했다.
처음에는 래퍼 클래스와 기본타입은 가능한가..?? 라는 생각도 했었지만, 너무 자기합리화로 끝내는 것 같아서 조금 더 정확하게 파고들고 싶었다.(개발자의 본성이랄까 ㅋㅅㅋ)
우선은 remove의 원리를 알고싶었다. 어째서, 저럴 때는 되고 이럴 때는 안되고..
ArrayList의 remove는 파고파고 들어가 fastRemove()를 호출한다.
이 fastRemove에서 modCount를 조작한다. 이것을 조금 더 살펴보니 이 modCount가 이 컬렉션의 변경을 확인하는 용도로 쓰이는 것이었다.
실제로 ArrayList의 Iterator를 구현한 내부 클래스인 Itr을 살펴보았다.
Itr에서는 expectedmodCount를 사용하는데 modCount의 값을 가져온다. modCount 값은 List의 add나 remove 시 변경되는 '원소의 개수'를 의미하는 듯 하다.
그렇게 expectedModCount는 이제부터 변하지 않는 수이다. Iterator를 사용해 반복할 때 해당 컬렉션은 수정이 불가능하므로, expectedModCount와 modCount를 비교하여 반복시 변경하였다면 오류를 호출하는 방식이다.
그렇다면 expectedModCount와 modCount는 어느 시점에서 변경될까? 아래의 메서드를 통해서 확인함을 알 수 있었다.
그러니까 우리가 반복문 내에서 컬렉션을 수정하였을 때, 그 즉시 오류가 발생하는 것이 아닌, checkForComodification()을 호출하는 시점에서 오류가 발생하는 것이다.
checkForComodification()은 많은 곳에서 호출하고 있지만, 대표적으로 Iterator를 구현한 내부 클래스에서는 next()와 remove(), forEachRemaining()이었다. (사실 내부 클래스의 모든 메서드..ㅎ)
자, 그렇다면 다시 문제제기로 돌아와보자. 왜 저 상단의 코드는 오류 없이 끝내었을까?? 분명 3을 삭제한 이후에 5를 만나는 과정에서 오류를 뱉어야할텐데..
그래서 반복문이 순회하는 과정을 출력해보았다.
단순히 방문하는 integer를 출력하였다. 그랬더니..
1, 3까지만 방문하는 것이었다..!!
왜 5를 방문하지 않는지는 for-each문에 대해서 더욱 깊게 파헤쳐봐야겠지만 이걸로 어느정도 궁금증이 해결됐다.
결국 for-each문에서 5를 방문하지 않았기에 오류를 뿜어낸 것이다. 예측컨데, 원소 3을 삭제한 이후 리스트의 사이즈가 2로 변경되어, iter 횟수가 모두 소진됨을 인지하고 종료한 것이 아닐까 싶다..!!
오랜만에 고찰을 해서 뿌듯하다..
알게된 것
- ConcurrentModificationException의 발생 시점
- Iterator를 구현한 클래스에서의 Collection 수정 확인 방법
'Error Record' 카테고리의 다른 글
The server selected protocol version TLS10 is not accepted by client preferences (0) | 2024.11.20 |
---|---|
[Makefile] 오류, *** 분리 기호가 빠졌음. 멈춤 해결 방법 (1) | 2023.12.05 |
The designated data directory /var/lib/mysql/ is unusable. (0) | 2023.11.16 |
AttributeError: Can't get attribute (0) | 2023.09.08 |
MySQL Workbench 튕김 현상 해결 (0) | 2023.08.30 |