반복자(Iterators)는 컬렉션의 요소를 하나씩 순회하면서 접근할 수 있도록 하는 객체를 의미한다. 여기에는 Iterable,Enumeration, Iterator, ListIterator, Spliterator 등의 객체가 포함된다.

Iterable 인터페이스는 반복 가능한 객체를 나타내며, 이 인터페이스를 구현한 클래스는 객체의 집합을 순회(iterate)할 수 있는 기능을 제공하는 Iterator 객체를 반환하는iterator() 메서드를 구현해야 한다. Iterable 인터페이스는 자바의 for-each 루프에서 사용할 수 있도록 설계되었다.
컬렉션들이 Iterator 객체들을 반환하기 위해서는 Iterable 인터페이스를 반드시 구현해야한다.
| 메서드 | 기능 |
|---|---|
| Iterator iterator() | 컬렉션의 요소들을 반복하는 Iterator를 반환 |
| void forEach(Consumer action) | 모든 요소에 작업 action을 수행 default 메서드 |
Enumeration은 자바의 컬렉션 프레임워크에서 사용되는 인터페이스로, 요소들을 순회(iterate)할 수 있는 방법을 제공한다. 자바 2부터 Iterator 인터페이스가 도입되면서 Enumeration은 구식으로 간주된다.
| 메서드 | 기능 |
|---|---|
| boolean hasMoreElements() | 읽어 올 요소가 남아있는지 확인 있으면 true, 없으면 false를 반환 |
| Object nextElement() | 다음 요소를 읽음 호출전 hasMoreElements()를 호출해서 읽어올 요소가 남아있는지 확인하는 것이 안전함 |
Iterator은 자바의 컬렉션 프레임워크에서 사용되는 인터페이스로, 요소들을 순회(iterate)할 수 있는 방법을 제공한다. 자바 2부터 도입되었으며 Enumeration를 대체하였다..
| 메서드 | 기능 |
|---|---|
| boolean hasNext() | 읽어 올 요소가 남아있는지 확인. 있으면 true, 없으면 false를 반환 |
| Object next() | 다음 요소를 읽어옴. next()를 호출하기 전에 hasNext()를 호출해서 읽어 올 요소가 있는지 확인하는 것이 안전 |
| void remove() | next()로 읽어온 요소를 삭제. next()를 호출한 다음에 remove()를 호출해야함. (선택적 기능) |
Iterable인터페이스는 위 다이어그램 처럼 각 컬렉션들이 직접 구현을 하지만 Iterator는 그렇지 않다. 그렇다면 Iterator인터페이스는 어떻게 구현되어 있을까? 바로 자바 컬렉션 프레임워크에서 사용하는 내부 클래스들이 주로 Iterator 인터페이스를 구현한다. 자바의 주요 컬렉션 클래스들이 Iterator 인터페이스를 구현한 내부 클래스를 사용해서 iterator() 메서드를 통해 Iterator 객체를 반환한다.
이해하기 쉽게 ArrayList 클래스를 예시로 들어 설명하면 다음과 같다. 해당 코드는 ArrayList의 내부 구현 코드중 일부분을 발췌한 것이다.
✍️ 작성
public class ArrayList<E> extends AbstractList<E> implements List<E>, RandomAccess, Cloneable, java.io.Serializable {
// 중략: 다른 메서드 및 필드들
private class Itr implements Iterator<E> {
int cursor; // 다음 요소를 가리키는 인덱스
int lastRet = -1; // 마지막으로 반환된 요소의 인덱스
@Override
public boolean hasNext() {
return cursor != size();
}
@Override
public E next() {
int i = cursor;
if (i >= size()) {
throw new NoSuchElementException();
}
Object[] elementData = ArrayList.this.elementData;
cursor = i + 1;
return (E) elementData[lastRet = i];
}
@Override
public void remove() {
if (lastRet < 0) {
throw new IllegalStateException();
}
ArrayList.this.remove(lastRet);
cursor = lastRet;
lastRet = -1;
}
// 기타 메서드 생략
}
@Override
public Iterator<E> iterator() {
return new Itr();
}
// 중략: 다른 메서드 및 필드들
}
본 코드에서 보듯 ArrayList의 경우 Iterator인터페이스를 구현하는 Itr이라는 내부 클래스가 존재하며 Iterator 인스턴스를 반환하는 iterator() 메서드는 내부클래스 Itr의 인스턴스를 반환하도록 작성되었다.
이렇게 구현된 ArrayList의 Iterator 활용 예시는 다음과 같다.
✍️ 작성
import java.util.*;
public class ArrayListIteratorExample {
public static void main(String[] args) {
// ArrayList 생성 및 초기화
ArrayList<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
// Iterator 생성
Iterator<String> iterator = list.iterator();
// Iterator를 사용하여 ArrayList의 요소를 순회
while (iterator.hasNext()) {
String fruit = iterator.next();
System.out.println(fruit);
}
}
}
ListIterator는 Iterator를 상속받아서 기능을 추가한 것으로, 컬렉션의 요소에 접근할 때 Iterator는 단방향으로만 이동할 수 있는데 반해 ListIterator는 양방향으로의 이동이 가능하다.
| 메서드 | 기능 |
|---|---|
| boolean hasNext() | 읽어 올 다음 요소가 남아있는지 확인. 있으면 true, 없으면 false를 반환 |
| boolean hasPrevious() | 읽어 올 이전 요소가 남아있는지 확인. 있으면 true, 없으면 false를 반환 |
| Object next() | 다음 요소를 읽어옴. next()를 호출하기 전에 hasNext()를 호출해서 읽어 올 요소가 있는지 확인하는 것이 안전 |
| Object previous() | 이전 요소를 읽어옴. previous()를 호출하기 전에 hasPrevious()를 호출해서 읽어 올 요소가 있는지 확인하는 것이 안전 |
| int nextIndex() | 다음 요소의 index를 반환 |
| int previousIndex() | 이전 요소의 index를 반환 |
| 메서드 | 기능 |
|---|---|
| void add(Object o) | 컬렉션에 새로운 객체(o)를 추가한다.(선택적 기능) |
| void remove() | next()로 읽어온 요소를 삭제. next()를 호출한 다음에 remove()를 호출해야함. (선택적 기능) |
| void set(Object o) | next() 또는 previous()로 읽어 온 요소를 지정된 객체(o)로 변경 반드시 next()나 previous()를 먼저 호출한 다음에 이 메서드를 호출해야 함. (선택적 기능) |
Spliterator는 자바 8에서 도입된 인터페이스로, 컬렉션의 요소를 순회하고 병렬 처리를 지원하는 기능을 제공한다. Spliterator는 "Splitable Iterator"의 약자이며, 이는 컬렉션을 분할하여 병렬 처리를 최적화하는 데 사용될 수 있다. Spliterator 인터페이스는 자바의 스트림 API와 함께 사용되는 경우가 많다.
| 메서드 | 기능 |
|---|---|
| boolean tryAdvance(Consumer action) | 컬렉션의 다음 요소를 처리하고, 더 이상 요소가 없으면 false를 반환. 요소가 존재하면 주어진 Consumer에 해당 요소를 제공하고, true를 반환. |
| void forEachRemaining(Consumer action) | 남아있는 모든 요소를 지정된 작업(Consumer)에 대해 수행 |
| Spliterator trySplit() | Spliterator를 약 절반으로 분할하여 병렬 처리를 가능하게 함. 분할된 Spliterator를 반환하고, 더 이상 분할할 수 없으면 null을 반환. |
| long estimateSize() | 남아있는 요소의 수를 추정. 정확한 크기를 알 수 없는 경우, 상한을 반환 |
| int characteristics() | Spliterator의 특성을 나타내는 비트 집합을 반환. 특성들은 Spliterator가 제공하는 보장과 성능 최적화를 가능하게 함. 특성들의 예시로는 다음이 있음. - ORDERED : 요소들이 정의된 순서로 처리됨을 나타냄. - DISTINCT : 요소들이 고유함을 나타냄. - SORTED : 요소들이 정렬되어 있음을 나타냄. - SIZED : 요소의 개수를 알 수 있음을 나타냄. - CONCURRENT : 여러 스레드가 동시에 Spliterator를 사용해도 안전함을 나타냄. - IMMUTABLE : 요소가 변경되지 않음을 나타냄. - SUBSIZED : 분할된 Spliterator들도 SIZED 특성을 가짐을 나타냄. |
다음은 Spliterator를 사용하는 예제이다. 익숙치 않은 기능이기에 이해를 돕기 위해 추가했다.
✍️ 작성
import java.util.ArrayList;
import java.util.Spliterator;
import java.util.List;
import java.util.function.Consumer;
public class SpliteratorExample {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("Apple");
list.add("Banana");
list.add("Cherry");
list.add("Date");
list.add("Elderberry");
Spliterator<String> spliterator1 = list.spliterator();
// TrySplit example
Spliterator<String> spliterator2 = spliterator1.trySplit();
System.out.println("Spliterator 1 elements:");
spliterator1.forEachRemaining(System.out::println);
System.out.println("\nSpliterator 2 elements:");
spliterator2.forEachRemaining(System.out::println);
}
}
🖥️ 결과
Spliterator 1 elements:
Cherry
Date
Elderberry
Spliterator 2 elements:
Apple
Banana
해당 코드에서는 Spliterator를 사용하여 리스트를 분할하고 각 Spliterator에서 요소를 순회한다.
자바의 정석 3판 (저자 : 남궁성)
오라클 Java 문서 : Iterable
오라클 Java 문서 : Enumeration
오라클 Java 문서 : Iterator
오라클 Java 문서 : ListIterator
오라클 Java 문서 : Spliterator