ConcurrentModificationException 예외가 발생하는 여러 상황이 있지만, 그 중에서 Collection의 remove 메소드를 사용해서 예외가 발생하는 경우와, 예외를 방지하는 방법에 대해 설명드리겠습니다.
remove
List<Integer> list = new ArrayList<>();
for (int i = 0; i < 100; i++) {
list.add(i);
}
for (Integer n : list) {
list.remove(n);
}
List의 remove 메소드를 사용할 때 ConcurrentModifiedException이 발생할 수 있습니다.
foreach(향상된 for문)를 통해 순회하는 경우 내부에서 Iterator를 사용하는데, Iterator의 next() 메소드를 호출하면 동시 수정 여부를 체크하게 됩니다.
동시 수정 여부는 ArrayList 내부에 있는 Iterator의 카운트(expectedModCount)와 List에 있는 카운트(modCount)와 일치하는지 여부에 따라 판별합니다.
만약에 List의 remove 메소드를 사용하면 List의 카운트가 변경되지만, Iterator의 카운트는 변경되지 않습니다.
그래서 두 카운트 값이 달라져서 동시 수정 여부 체크 메소드를 통과하지 못하고 ConcurrentModifiedException이 발생합니다.
다음과 같은 방법을 사용하면 ConcurrentModifiedException 예외 없이 컬렉션에서 객체를 제거할 수 있습니다.
ConcurrentModifiedException 예외 방지
for 문 사용
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(String.valueOf(i));
}
for (int i = 0; i < 10; i += 2) {
list.remove(String.valueOf(i));
}
System.out.println(list);
foreach를 사용하여 컬렉션 객체를 제거하면 Iterator에 의해 예외가 발생했기 때문에 foreach를 사용하지 않으면 됩니다.
일반 for문은 Iterator를 사용하지 않으므로 ConcurrentModifiedException 예외가 생기지 않습니다.
다만, List의 경우 remove 메소드가 2개인데 하나는 객체를 받고, 하나는 인덱스를 받습니다.
인덱스를 받아서 제거하는 경우, list의 길이가 점점 줄어들게되어 IndexOutOfBoundsException이 발생할 수 있습니다.
예를 들어, 10개짜리 리스트에서 8, 9번째 요소를 제거한다고 가정해보면,
8번째 요소를 제거하면 길이가 9가 되어 9번째 요소를 찾을 수 없게 됩니다.
그래서 remove(9)를 하게 되면 IndexOutOfBoundsException이 발생합니다.
위의 예제에서 remove 메소드에서 String.valueOf를 제거하고 동작시키면 예외를 확인할 수 있습니다.
Iterator 사용
List<String> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(String.valueOf(i));
}
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
if (Integer.parseInt(iterator.next()) % 2 == 0) {
iterator.remove();
}
}
System.out.println(list);
이렇게 써도 됩니다.
list.removeIf(s -> Integer.parseInt(s) % 2 == 0);
왜 Iterator에서 제거하는데 List의 요소가 제거되는가?
리스트에 있는 Iterator의 remove를 사용하면 List의 remove를 호출하기 때문입니다.
읽으면 좋은 글
[Java] Collection - ConcurrentModificationException: toString