58. 전통적인 for 문보다는 for-each 문을 사용하라

✅ 핵심 요약

for-each 문(향상된 for 문)을 사용하면 코드가 간결해지고, 오류 발생 가능성을 줄일 수 있으며, 가독성이 향상됩니다. 전통적인 for문 (인덱스 사용)이나 Iterator를 직접 사용하는 방식보다 훨씬 안전하고 명확하므로, 가능한 모든 경우에 for-each 문을 사용하는 것이 좋습니다.


📚 필수 개념 및 배경 지식

이 아이템을 제대로 이해하려면 다음 개념들을 알아두는 것이 좋습니다.

개념
설명

전통적인 for 문

for (int i = 0; i < list.size(); i++) { list.get(i) } • 인덱스로 접근 가능한 자료구조에만 사용 가능 • 인덱스 조작이 필요할 때 유용하지만 실수 위험 높음

Iterator 방식

Iterator<T> it = list.iterator();while (it.hasNext()) { T item = it.next(); } • 모든 컬렉션에 사용 가능하고 요소 삭제(remove())도 가능 • 코드가 길고 복잡함

for-each 문

for (String item : items) { ... } • Java 5부터 도입된 간결한 문법 • 가독성 높고 실수 가능성 낮음 • 인덱스 접근이나 수정은 불가능

Iterable

for-each를 사용하기 위한 필수 인터페이스 • iterator() 메서드 하나만 구현하면 됨 • 모든 Java 컬렉션은 기본적으로 구현되어 있음

✅ 올바른 예제 (for-each 사용)

import java.util.*;

// 카드 무늬 (Suit): 총 4가지
enum Suit {
    CLUB, DIAMOND, HEART, SPADE
}

// 카드 숫자 (Rank): 총 13가지
enum Rank {
    ACE, TWO, THREE, FOUR, FIVE, SIX, SEVEN,
    EIGHT, NINE, TEN, JACK, QUEEN, KING
}

// 카드 클래스
class Card {
    private final Suit suit;
    private final Rank rank;

    public Card(Suit suit, Rank rank) {
        this.suit = suit;
        this.rank = rank;
    }

    @Override
    public String toString() {
        return rank + " of " + suit;
    }
}

public class DeckForEachExample {
    public static void main(String[] args) {
        // 무늬와 숫자 리스트 생성
        Collection<Suit> suits = Arrays.asList(Suit.values());  // 4개
        Collection<Rank> ranks = Arrays.asList(Rank.values());  // 13개

        List<Card> deck = new ArrayList<>();

        // ✅ for-each 문으로 4 × 13 = 52장 카드를 안전하게 생성
        for (Suit suit : suits) {
            for (Rank rank : ranks) {
                deck.add(new Card(suit, rank));
            }
        }

        System.out.println("총 카드 수: " + deck.size());  // ✅ 출력: 52
        System.out.println("생성된 카드 예시:");
        for (int i = 0; i < 5; i++) {
            System.out.println(deck.get(i));
        }
    }
}

❌ 잘못된 예제 (버그 발생)

import java.util.*;

public class DeckBuggyIteratorExample {
    public static void main(String[] args) {
        Collection<Suit> suits = Arrays.asList(Suit.values());  // 4개
        Collection<Rank> ranks = Arrays.asList(Rank.values());  // 13개

        List<Card> deck = new ArrayList<>();

        // ❌ 반복자 사용 시 실수 예제: i.next()가 너무 자주 호출됨!
        for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ) {
            for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); ) {
                // ⚠️ 문제 발생: i.next()가 안쪽 반복문에서도 계속 호출됨
                // 결과: suit 1개당 rank 13번 쓰이면 suit는 4개밖에 없기 때문에
                // 5번째 rank부터는 i.next()가 NoSuchElementException을 던짐
                deck.add(new Card(i.next(), j.next()));  // ❗예외 발생 위험
            }
        }
        // 이 코드는 정상 종료되지 않거나, 예외를 발생시킬 수 있음
        System.out.println("카드 생성 완료: " + deck.size() + "장"); // ❌ 실행 중 예외 발생
    }
}

🔎 문제: 바깥 반복자인 i.next()안쪽 반복문에서 계속 실행되면서, 바깥 컬렉션이 빨리 끝나버림 → 예외 발생 또는 누락


🛠 특별한 상황에서는 전통적인 for 문이 더 적절

List<String> list = new ArrayList<>(List.of("a", "b", "c"));
for (Iterator<String> it = list.iterator(); it.hasNext(); ) {
    if (it.next().equals("b")) it.remove(); // ❗ 삭제하려면 전통적 반복자 사용
}

📦 자바 버전에 따른 변화

☕ Java 8 이후

  • Collection.removeIf() 메서드 추가: 컬렉션에서 필터링하며 원소를 제거할 때 for 문 대신 사용 가능

  • 스트림 API: stream().filter().collect() 같은 방식으로 필터링 및 변환 가능

// Java 8 이전
List<String> filtered = new ArrayList<>();
for (String s : original) {
    if (s.length() > 3) {
        filtered.add(s);
    }
}
// Java 8 이후
List<String> filtered = original.stream()
    .filter(s -> s.length() > 3)
    .collect(Collectors.toList());

💡 핵심 포인트

for-each를 써야 하는 이유

  • 가독성 및 간결성: for-each는 반복자나 인덱스 변수 선언 및 관리 코드가 없어 훨씬 깔끔합니다.

  • for-each 문은 배열과 컬렉션 모두에 사용할 수 있다

  • 성능은 전통적인 반복문과 차이가 없다

🚫 for-each를 쓸 수 없는 경우

  • 예외 상황: 다음의 경우에는 for-each를 사용할 수 없음

    1. 파괴적인 필터링 : 원소를 삭제하면서 순회할 때 (→ Iterator.remove() 필요)

    2. 변형 : 원소를 수정할 때 (인덱스 필요)

    3. 병렬 반복 : 두 개 이상의 리스트를 동시에 순회할 때


✅ 결론

단순 순회에는 무조건 for-each를 사용하세요. 코드가 더 안전하고 읽기 쉬워집니다. for-each를 사용할 수 없는 명확한 예외 상황(제거, 수정, 병렬 반복)에서만 다른 방법을 고려하고, 이때는 관련 버그에 주의해야 합니다.


🎯 느낀점

  • 처음엔 for-each 문이 단순히 짧은 문법이라 생각했는데, 이 아이템을 공부하면서 실수를 줄이고 코드 품질을 높이는 핵심 도구라는 걸 느꼈어요.

  • 특히 중첩 반복문에서 next()를 잘못 쓰면 큰 버그가 생긴다는 걸 보면서, for-each는 단순히 "간편함"을 넘어서 안정성과 신뢰성을 주는 문법이라고 이해하게 되었습니다.

  • 이제 반복문을 작성할 땐, "이 작업이 for-each로 가능한가?" 먼저 떠올리게 되었습니다.

Last updated