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를 올바르게 구현하는 것이 핵심이다.