Java Iterable과 Iterator 완전 가이드
Iterable과 Iterator는 Java 컬렉션의 핵심 인터페이스다. for-each 루프의 동작 원리와 커스텀 컬렉션 구현의 기초가 된다.
Iterable 인터페이스
정의
public interface Iterable<T> {
Iterator<T> iterator();
// Java 8+
default void forEach(Consumer<? super T> action) { ... }
default Spliterator<T> spliterator() { ... }
}
for-each 루프의 비밀
List<String> list = List.of("A", "B", "C");
// for-each 문법
for (String s : list) {
System.out.println(s);
}
// 컴파일러가 변환하는 실제 코드
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
System.out.println(s);
}
for-each 루프는 Iterable을 구현한 모든 객체에 사용할 수 있다.
Iterator 인터페이스
정의
public interface Iterator<E> {
boolean hasNext();
E next();
// Java 8+
default void remove() { throw new UnsupportedOperationException(); }
default void forEachRemaining(Consumer<? super E> action) { ... }
}
기본 사용법
List<String> list = new ArrayList<>(List.of("A", "B", "C"));
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String element = iterator.next();
System.out.println(element);
}
안전한 삭제
List<String> list = new ArrayList<>(List.of("A", "B", "C", "D"));
// ConcurrentModificationException 발생!
for (String s : list) {
if (s.equals("B")) {
list.remove(s); // 잘못된 방법
}
}
// Iterator.remove() 사용 - 올바른 방법
Iterator<String> iterator = list.iterator();
while (iterator.hasNext()) {
String s = iterator.next();
if (s.equals("B")) {
iterator.remove(); // 안전한 삭제
}
}
forEachRemaining
List<String> list = List.of("A", "B", "C", "D", "E");
Iterator<String> iterator = list.iterator();
// 처음 2개 수동 처리
iterator.next(); // A
iterator.next(); // B
// 나머지 일괄 처리
iterator.forEachRemaining(System.out::println); // C, D, E
Iterable vs Collection
계층 구조
Iterable<T>
│
└── Collection<T>
│
├── List<T>
├── Set<T>
└── Queue<T>
차이점
| 특징 | Iterable | Collection |
|---|---|---|
| 메서드 | iterator() | add(), remove(), size(), contains() 등 |
| 용도 | 순회만 가능 | 요소 조작 가능 |
| 크기 확인 | 불가 | size() 제공 |
| 스트림 변환 | StreamSupport 필요 | stream() 메서드 제공 |
Iterable만 받는 경우 처리
public void process(Iterable<String> iterable) {
// Iterable → List 변환
List<String> list = new ArrayList<>();
iterable.forEach(list::add);
// 또는 StreamSupport 사용
List<String> list2 = StreamSupport
.stream(iterable.spliterator(), false)
.collect(Collectors.toList());
}
Iterable을 다른 타입으로 변환
Iterable → List
Iterable<String> iterable = ...;
// 방법 1: forEach
List<String> list1 = new ArrayList<>();
iterable.forEach(list1::add);
// 방법 2: for-each
List<String> list2 = new ArrayList<>();
for (String s : iterable) {
list2.add(s);
}
// 방법 3: StreamSupport
List<String> list3 = StreamSupport
.stream(iterable.spliterator(), false)
.toList();
Iterable → Array
Iterable<String> iterable = ...;
// 방법 1: List 경유
List<String> list = new ArrayList<>();
iterable.forEach(list::add);
String[] array1 = list.toArray(new String[0]);
// 방법 2: StreamSupport
String[] array2 = StreamSupport
.stream(iterable.spliterator(), false)
.toArray(String[]::new);
Iterable → Stream
Iterable<String> iterable = ...;
// StreamSupport 사용
Stream<String> stream = StreamSupport.stream(iterable.spliterator(), false);
// 병렬 스트림
Stream<String> parallelStream = StreamSupport.stream(iterable.spliterator(), true);
실무 예제: ImportCandidates
// Spring Boot AutoConfiguration 예제
@Override
public String[] selectImports(AnnotationMetadata importingClassMetadata) {
Iterable<String> candidates = ImportCandidates.load(MyAutoConfiguration.class, classLoader);
// 방법 1: for-each
List<String> autoConfigs = new ArrayList<>();
for (String candidate : candidates) {
autoConfigs.add(candidate);
}
return autoConfigs.toArray(new String[0]);
// 방법 2: StreamSupport (더 간결)
return StreamSupport.stream(candidates.spliterator(), false)
.toArray(String[]::new);
}
커스텀 Iterable 구현
기본 구현
public class NumberRange implements Iterable<Integer> {
private final int start;
private final int end;
public NumberRange(int start, int end) {
this.start = start;
this.end = end;
}
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
private int current = start;
@Override
public boolean hasNext() {
return current <= end;
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return current++;
}
};
}
}
// 사용
NumberRange range = new NumberRange(1, 5);
for (int num : range) {
System.out.println(num); // 1, 2, 3, 4, 5
}
무한 Iterator
public class InfiniteCounter implements Iterable<Integer> {
@Override
public Iterator<Integer> iterator() {
return new Iterator<>() {
private int count = 0;
@Override
public boolean hasNext() {
return true; // 무한
}
@Override
public Integer next() {
return count++;
}
};
}
}
// 사용 시 주의 - limit 필수
InfiniteCounter counter = new InfiniteCounter();
StreamSupport.stream(counter.spliterator(), false)
.limit(10)
.forEach(System.out::println); // 0 ~ 9
파일 라인 Iterator
public class FileLineIterable implements Iterable<String>, AutoCloseable {
private final BufferedReader reader;
public FileLineIterable(Path path) throws IOException {
this.reader = Files.newBufferedReader(path);
}
@Override
public Iterator<String> iterator() {
return new Iterator<>() {
private String nextLine;
@Override
public boolean hasNext() {
if (nextLine != null) {
return true;
}
try {
nextLine = reader.readLine();
return nextLine != null;
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public String next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
String line = nextLine;
nextLine = null;
return line;
}
};
}
@Override
public void close() throws IOException {
reader.close();
}
}
// 사용
try (FileLineIterable lines = new FileLineIterable(Path.of("data.txt"))) {
for (String line : lines) {
System.out.println(line);
}
}
ListIterator
List 전용 양방향 Iterator.
특징
public interface ListIterator<E> extends Iterator<E> {
boolean hasNext();
E next();
boolean hasPrevious(); // 역방향 탐색
E previous(); // 역방향 이동
int nextIndex(); // 다음 인덱스
int previousIndex(); // 이전 인덱스
void remove();
void set(E e); // 현재 요소 교체
void add(E e); // 현재 위치에 추가
}
사용 예시
List<String> list = new ArrayList<>(List.of("A", "B", "C", "D"));
ListIterator<String> iterator = list.listIterator();
// 순방향 탐색
while (iterator.hasNext()) {
System.out.println(iterator.nextIndex() + ": " + iterator.next());
}
// 0: A, 1: B, 2: C, 3: D
// 역방향 탐색
while (iterator.hasPrevious()) {
System.out.println(iterator.previousIndex() + ": " + iterator.previous());
}
// 3: D, 2: C, 1: B, 0: A
요소 수정과 추가
List<String> list = new ArrayList<>(List.of("A", "B", "C"));
ListIterator<String> iterator = list.listIterator();
while (iterator.hasNext()) {
String s = iterator.next();
if (s.equals("B")) {
iterator.set("B-modified"); // 교체
iterator.add("B2"); // 추가
}
}
// list: [A, B-modified, B2, C]
특정 위치부터 시작
List<String> list = List.of("A", "B", "C", "D", "E");
// 인덱스 2부터 시작
ListIterator<String> iterator = list.listIterator(2);
while (iterator.hasNext()) {
System.out.println(iterator.next()); // C, D, E
}
Spliterator
Java 8에서 추가된 병렬 처리를 위한 Iterator.
특징
public interface Spliterator<T> {
boolean tryAdvance(Consumer<? super T> action); // 다음 요소 처리
Spliterator<T> trySplit(); // 분할
long estimateSize(); // 예상 크기
int characteristics(); // 특성 플래그
}
기본 사용
List<String> list = List.of("A", "B", "C", "D");
Spliterator<String> spliterator = list.spliterator();
// 크기 확인
System.out.println(spliterator.estimateSize()); // 4
// 순회
spliterator.forEachRemaining(System.out::println);
분할 (병렬 처리용)
List<Integer> numbers = IntStream.range(0, 100).boxed().toList();
Spliterator<Integer> spliterator = numbers.spliterator();
// 분할
Spliterator<Integer> split1 = spliterator.trySplit();
Spliterator<Integer> split2 = spliterator.trySplit();
System.out.println("원본 크기: " + spliterator.estimateSize()); // 25
System.out.println("분할1 크기: " + split1.estimateSize()); // 50
System.out.println("분할2 크기: " + split2.estimateSize()); // 25
커스텀 Spliterator
public class ArraySpliterator<T> implements Spliterator<T> {
private final T[] array;
private int current;
private final int end;
public ArraySpliterator(T[] array, int start, int end) {
this.array = array;
this.current = start;
this.end = end;
}
@Override
public boolean tryAdvance(Consumer<? super T> action) {
if (current < end) {
action.accept(array[current++]);
return true;
}
return false;
}
@Override
public Spliterator<T> trySplit() {
int remaining = end - current;
if (remaining < 2) {
return null;
}
int mid = current + remaining / 2;
Spliterator<T> split = new ArraySpliterator<>(array, current, mid);
current = mid;
return split;
}
@Override
public long estimateSize() {
return end - current;
}
@Override
public int characteristics() {
return ORDERED | SIZED | SUBSIZED | NONNULL;
}
}
forEach와 Iterable
Iterable.forEach
List<String> list = List.of("A", "B", "C");
// forEach 사용
list.forEach(System.out::println);
// 기본 구현
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
for-each vs forEach
List<String> list = List.of("A", "B", "C");
// for-each: break, continue 가능
for (String s : list) {
if (s.equals("B")) break;
System.out.println(s);
}
// forEach: break 불가, return은 continue 역할
list.forEach(s -> {
if (s.equals("B")) return; // continue와 같음
System.out.println(s);
});
forEach에서 예외 처리
List<String> paths = List.of("/file1.txt", "/file2.txt");
// 체크 예외 처리
paths.forEach(path -> {
try {
Files.readString(Path.of(path));
} catch (IOException e) {
throw new UncheckedIOException(e);
}
});
// 또는 헬퍼 메서드 사용
paths.forEach(unchecked(path -> Files.readString(Path.of(path))));
// 헬퍼 메서드
static <T> Consumer<T> unchecked(ThrowingConsumer<T> consumer) {
return t -> {
try {
consumer.accept(t);
} catch (Exception e) {
throw new RuntimeException(e);
}
};
}
@FunctionalInterface
interface ThrowingConsumer<T> {
void accept(T t) throws Exception;
}
유틸리티 메서드
Iterables (Guava)
// Guava 라이브러리
import com.google.common.collect.Iterables;
Iterable<String> iterable = ...;
// 크기 확인
int size = Iterables.size(iterable);
// 첫 번째/마지막 요소
String first = Iterables.getFirst(iterable, "default");
String last = Iterables.getLast(iterable, "default");
// 필터링
Iterable<String> filtered = Iterables.filter(iterable, s -> s.startsWith("A"));
// 변환
Iterable<Integer> lengths = Iterables.transform(iterable, String::length);
// 합치기
Iterable<String> combined = Iterables.concat(iterable1, iterable2);
직접 구현하는 유틸리티
public class IterableUtils {
public static <T> int size(Iterable<T> iterable) {
if (iterable instanceof Collection) {
return ((Collection<T>) iterable).size();
}
int count = 0;
for (T ignored : iterable) {
count++;
}
return count;
}
public static <T> List<T> toList(Iterable<T> iterable) {
if (iterable instanceof List) {
return (List<T>) iterable;
}
List<T> list = new ArrayList<>();
iterable.forEach(list::add);
return list;
}
public static <T> Stream<T> stream(Iterable<T> iterable) {
return StreamSupport.stream(iterable.spliterator(), false);
}
public static <T> Optional<T> first(Iterable<T> iterable) {
Iterator<T> iterator = iterable.iterator();
return iterator.hasNext() ? Optional.of(iterator.next()) : Optional.empty();
}
}
정리
| 인터페이스 | 용도 | 주요 메서드 |
|---|---|---|
| Iterable | 순회 가능 객체 정의 | iterator(), forEach() |
| Iterator | 순차 탐색 | hasNext(), next(), remove() |
| ListIterator | 양방향 탐색 (List 전용) | previous(), set(), add() |
| Spliterator | 병렬 처리용 분할 | trySplit(), tryAdvance() |
변환 패턴
Iterable<T> iterable = ...;
// → List
List<T> list = StreamSupport.stream(iterable.spliterator(), false).toList();
// → Array
T[] array = StreamSupport.stream(iterable.spliterator(), false).toArray(T[]::new);
// → Stream
Stream<T> stream = StreamSupport.stream(iterable.spliterator(), false);
for-each 사용 조건
1. Iterable<T>를 구현한 객체
2. 배열 (int[], String[] 등)
Iterable을 이해하면 Java 컬렉션의 동작 원리를 깊이 이해할 수 있다. 커스텀 컬렉션 구현 시 Iterable과 Iterator를 올바르게 구현하는 것이 핵심이다.
댓글