| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | ||||
| 4 | 5 | 6 | 7 | 8 | 9 | 10 |
| 11 | 12 | 13 | 14 | 15 | 16 | 17 |
| 18 | 19 | 20 | 21 | 22 | 23 | 24 |
| 25 | 26 | 27 | 28 | 29 | 30 | 31 |
- 예외처리
- 자료구조
- 가비지컬렉션
- 자바기초
- 코딩테스트
- 개발자취업
- 코딩테스트팁
- 프로그래밍기초
- HashMap
- 파이썬
- JVM
- 알고리즘공부
- 알고리즘
- 프로그래머스
- 자바
- 정렬
- 코딩공부
- 개발자팁
- 멀티스레드
- 자바프로그래밍
- 객체지향
- 자바개발
- 코딩테스트준비
- 개발공부
- 백준
- 자바공부
- 메모리관리
- Java
- 코딩인터뷰
- 클린코드
- Today
- Total
코드 한 줄의 기록
Java Iterator와 Fail-Fast, 컬렉션 수정 시 주의할 점들 본문
자바로 개발하다 보면 리스트나 셋 같은 컬렉션을 순회하면서 요소를 추가하거나 삭제해야 할 때가 있다. 그런데 이 과정에서 ConcurrentModificationException이라는 예외를 만나본 적 있지 않은가? 처음엔 당황스럽지만, 이 예외는 자바 컬렉션의 안전장치로서 중요한 역할을 한다. 오늘은 Iterator의 개념부터 fail-fast 메커니즘, 그리고 컬렉션을 안전하게 수정하는 방법까지 함께 살펴보려 한다.
Iterator란 무엇인가?
Iterator는 자바 컬렉션 프레임워크에서 컬렉션의 요소들을 순차적으로 읽어오기 위한 표준 인터페이스다. 쉽게 말해, 리스트나 셋처럼 여러 데이터를 담고 있는 자료구조를 하나씩 탐색할 수 있게 해주는 도구라고 생각하면 된다.
Iterator는 다음 세 가지 핵심 메서드를 제공한다.
- hasNext(): 다음에 읽어올 요소가 있는지 확인한다.
- next(): 다음 요소를 반환하고 커서를 한 칸 이동시킨다.
- remove(): next()로 가져온 마지막 요소를 컬렉션에서 삭제한다.
List<String> list = new ArrayList<>();
list.add("사과");
list.add("바나나");
list.add("포도");
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
}
Iterator의 진짜 장점은 모든 컬렉션 타입에 공통으로 사용할 수 있다는 점과, 순회 중 안전하게 요소를 삭제할 수 있다는 점이다.
Fail-Fast 메커니즘의 이해
자바 컬렉션의 대부분(ArrayList, HashMap 등)은 fail-fast 방식으로 동작한다. 문제 발생 시 즉시 예외를 던져 이상한 상태로 진행되지 않게 한다.
modCount와 expectedModCount
Fail-fast의 핵심은 modCount와 expectedModCount이다.
final void checkForComodification() {
if (modCount != expectedModCount)
throw new ConcurrentModificationException();
}
Iterator 생성 후 컬렉션 구조가 변경되면 위 메서드에서 예외를 던진다.
왜 이렇게 엄격할까?
순회 중 구조가 변경되면 인덱스가 밀려서 엉뚱한 데이터를 읽을 수 있다. Fail-fast는 “더 이상 안전하지 않으니 중단하라”는 경고다.
ConcurrentModificationException 발생 상황
1. 단일 스레드 환경
for (String item : list) {
if (item.equals("B")) {
list.remove(item); // 예외 발생
}
}
향상된 for문은 내부적으로 Iterator를 사용하므로, 직접 컬렉션을 수정하면 예외가 발생한다.
2. 멀티스레드 환경
List<String> list = new ArrayList<>();
new Thread(() -> {
for (String s : list)
System.out.println(s);
}).start();
new Thread(() -> list.add("D")).start();
다른 스레드가 동시에 수정 시 예외 발생.
안전하게 컬렉션 수정하는 방법
1. Iterator.remove() 사용
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
if (iterator.next().equals("B")) {
iterator.remove();
}
}
2. 역순 for문 사용
for (int i = list.size() - 1; i >= 0; i--) {
if (list.get(i).equals("B")) list.remove(i);
}
3. removeIf()
list.removeIf(item -> item.equals("B"));
4. 복사본 후 변경
List<String> copy = new ArrayList<>(list);
for (String s : copy) {
if (s.equals("B")) list.remove(s);
}
5. CopyOnWriteArrayList 사용
CopyOnWriteArrayList<String> list = new CopyOnWriteArrayList<>();
for (String s : list) {
if (s.equals("B")) list.remove(s); // 안전
}
fail-safe 방식으로 수정 시에도 예외가 발생하지 않는다.
향상된 for문의 함정
for (String s : list) { // 내부적으로 iterator 사용
list.remove(s); // 예외 발생
}
Stream 주의
list.stream()
.forEach(s -> list.remove(s)); // 위험
Stream의 forEach()는 내부적으로 Iterator를 사용한다. 대신 filter()를 이용하자.
ListIterator로 양방향 순회
ListIterator<String> it = list.listIterator();
while (it.hasNext()) {
if (it.next().equals("B")) {
it.set("BB");
it.add("B2");
}
}
컬렉션 순회 중에는 Iterator.remove() 또는 removeIf() 사용
향상된 for문은 내부적으로 Iterator 사용
멀티스레드 환경에서는 CopyOnWriteArrayList 고려
자료구조 특성에 맞게 순회 방식을 선택
이 개념들을 이해하면 컬렉션을 다룰 때 훨씬 안전하고 예측 가능한 코드를 작성할 수 있다.
Java Queue, Deque, PriorityQueue 완벽 가이드
코딩 테스트를 준비하다 보면 Queue 관련 자료구조를 정말 많이 사용하게 됩니다. 특히 BFS 알고리즘이나 우선순위 처리 같은 문제에서 필수적이죠. 저도 처음엔 이 세 가지가 뭐가 다른지 헷갈렸
byteandbit.tistory.com
'JAVA' 카테고리의 다른 글
| Java 함수형 인터페이스와 메서드 레퍼런스 완벽 가이드 (0) | 2025.10.29 |
|---|---|
| Java 제네릭 완벽 가이드: 타입 파라미터부터 와일드카드까지 실전 총정리 (0) | 2025.10.28 |
| Java Comparable과 Comparator 완벽 가이드 - 정렬 전략 완전 정복 (0) | 2025.10.27 |
| Java Queue, Deque, PriorityQueue 완벽 가이드 (0) | 2025.10.27 |
| Java Map 완전 정복: HashMap, LinkedHashMap, TreeMap 비교와 키 설계 핵심 가이드 (0) | 2025.10.26 |