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
를 사용할 수 없음파괴적인 필터링 : 원소를 삭제하면서 순회할 때 (→
Iterator.remove()
필요)변형 : 원소를 수정할 때 (인덱스 필요)
병렬 반복 : 두 개 이상의 리스트를 동시에 순회할 때
✅ 결론
단순 순회에는 무조건
for-each
를 사용하세요. 코드가 더 안전하고 읽기 쉬워집니다.for-each
를 사용할 수 없는 명확한 예외 상황(제거, 수정, 병렬 반복)에서만 다른 방법을 고려하고, 이때는 관련 버그에 주의해야 합니다.
🎯 느낀점
처음엔
for-each
문이 단순히 짧은 문법이라 생각했는데, 이 아이템을 공부하면서 실수를 줄이고 코드 품질을 높이는 핵심 도구라는 걸 느꼈어요.특히 중첩 반복문에서
next()
를 잘못 쓰면 큰 버그가 생긴다는 걸 보면서, for-each는 단순히 "간편함"을 넘어서 안정성과 신뢰성을 주는 문법이라고 이해하게 되었습니다.이제 반복문을 작성할 땐, "이 작업이 for-each로 가능한가?" 먼저 떠올리게 되었습니다.
Last updated