개발을 하다 보면 리스트, 배열, 트리 등 다양한 자료구조에 들어있는 데이터를 순회(Loop)해야 할 일이 수도 없이 생깁니다.
이때 **"데이터가 어떻게 저장되어 있는지(내부 구조)는 몰라도, 순서대로 꺼낼 수 있게 해주는 것"**이 이터레이터 패턴의 핵심입니다. 하지만 이 순회를 "내가 직접 할지" 아니면 **"맡길지"**에 따라 코드는 완전히 달라집니다.

1. 방식 1: 외부 반복자 (External Iterator) - "능동적"
가장 고전적인 방식(C++, Java의 초기 스타일)입니다. 클라이언트가 **"다음 거 내놔(next)"**라고 직접 명령하며 순서를 제어합니다. 우리가 아는 Iterator<E> 인터페이스가 바로 이것입니다.
특징
- 제어권: 클라이언트(사용자)에게 있습니다.
- "언제 멈출지", "건너띌지"를 클라이언트가 결정합니다.
코드 예시
// 자료구조 (Collection)
List<String> names = Arrays.asList("James", "Emily", "John");
// 1. 반복자(Iterator) 획득
Iterator<String> iterator = names.iterator();
// 2. 클라이언트가 직접 반복을 제어 (Explicit Control)
while (iterator.hasNext()) {
String name = iterator.next();
if (name.equals("Emily")) {
System.out.println("찾았다 에밀리!");
break; // 클라이언트 맘대로 멈출 수 있음
}
}
장단점
- 장점: 세밀한 제어가 가능합니다. (중간에 멈추기, 두 개의 리스트를 동시에 비교하며 돌리기 등)
- 단점: 코드가 길어지고(Boilerplate), 클라이언트가 반복 상태를 관리해야 합니다.
2. 방식 2: 내부 반복자 (Internal Iterator) - "수동적"
Java 8(Lambda), JavaScript 등 현대적인 언어에서 주류가 된 방식입니다. 클라이언트는 "할 일(로직)"만 던져주고, 반복 자체는 컬렉션 내부에서 알아서 처리합니다.
특징
- 제어권: 컬렉션(Iterator 구현체)에게 있습니다.
- 클라이언트는 "어떻게(How)" 반복할지 신경 쓰지 않고, "무엇을(What)" 할지만 정의합니다.
코드 예시
List<String> names = Arrays.asList("James", "Emily", "John");
// 클라이언트는 '로직(Lambda)'만 전달함. 반복은 알아서 돌아감.
names.forEach(name -> {
System.out.println("이름: " + name);
});
// Stream API도 내부 반복자의 일종
names.stream()
.filter(name -> name.startsWith("J"))
.forEach(System.out::println);
장단점
- 장점:
- 가독성: 코드가 매우 간결하고 선언적(Declarative)입니다.
- 병렬 처리: 반복 제어권이 라이브러리에 있으므로, parallelStream()만 붙이면 알아서 멀티스레드로 반복을 쪼개서 수행할 수 있습니다.
- 단점: break나 return으로 중간에 반복을 멈추기가 까다롭거나 불가능합니다.
3. 실무 예시: 언제 무엇을 쓰는가?
자바의 Iterator 인터페이스(External)와 Stream API(Internal)가 완벽한 비교 대상입니다.
상황 A: 복잡한 비교가 필요할 때 (External)
두 개의 리스트를 동시에 순회하면서 비교해야 한다면 내부 반복자(forEach)로는 구현이 매우 어렵습니다.
// 외부 반복자가 필요한 순간
while (iter1.hasNext() && iter2.hasNext()) {
if (iter1.next().equals(iter2.next())) { ... }
}
상황 B: 대용량 데이터 처리 및 가독성 (Internal)
데이터를 필터링하고 변환하는 파이프라인 작업에는 내부 반복자가 압도적입니다.
// 내부 반복자의 승리
list.stream()
.filter(User::isActive)
.map(User::getEmail)
.forEach(this::sendEmail);
요약: External vs Internal
| 구분 | External Iterator (for, while) | Internal Iterator (forEach, stream) |
| 제어 주체 | 클라이언트 (개발자) | 라이브러리 (컬렉션) |
| 스타일 | 명령형 (Imperative) | 선언형 (Functional) |
| 제어 유연성 | 높음 (break, continue 자유로움) | 낮음 (중단하기 어려움) |
| 병렬 처리 | 개발자가 직접 구현해야 함 (어려움) | 라이브러리가 지원 (쉬움) |
결론
단순히 코드를 짧게 줄이고 싶거나 병렬 처리가 필요하다면 내부 반복자(Internal, Stream) 가 정답입니다. 이것이 현대적인 코딩 스타일입니다.
하지만 반복 중간에 복잡한 제어(조건부 중단, 다중 컬렉션 제어)가 필요하다면, 고전적인 외부 반복자(External, Iterator) 를 사용하는 것을 두려워하지 마세요. 구관이 명관일 때가 있습니다.
'프로그래밍 > 디자인패턴' 카테고리의 다른 글
| 커맨드 패턴(Command Pattern) 총정리 (0) | 2025.12.07 |
|---|---|
| 책임 연쇄 패턴(Chain of Responsibility) 총정리 (0) | 2025.12.07 |
| 상태 패턴(State Pattern) 완벽 정리 (0) | 2025.12.07 |
| 템플릿 메서드 패턴(Template Method Pattern) 총정리 (0) | 2025.12.07 |
| 빌더 패턴(Builder Pattern) 총정리 (0) | 2025.12.07 |