아이템 45에서 다루었다시피 stream이 제격인 작업이 있고 반복이 제격인 작업이 있습니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public static void main(String[] args) { | |
List<Integer> list = new ArrayList<>(); | |
for (Iterator<Integer> i = list.iterator(); i.hasNext(); ) { | |
Integer num = i.next(); | |
// num을 이용한 비즈니스 로직 | |
} | |
for (int i = 0; i < list.size(); i++) { | |
// list[i]를 이용한 비즈니스 로직 | |
} | |
} |
앞서 아이템 57에서도 언급했듯이 for문과 같은 광용구들은 while문보다는 낫지만 다음 이유 때문에 최선의 방법은 아닙니다.
- 진짜 필요한 것은 원소들 뿐이지만 위 코드처럼 작성할 경우 반복자와 인덱스 변수 모두 선언해야 하기 때문에 코드가 지저분해짐
- 컬렉션이나 배열이여냐에 따라 코드 형태가 달라짐
for-each문
- 정식 이름은 `향상된 for문(enhanced for statement)`
- for-each 문은 컬렉션과 배열은 물론 Iterable 인터페이스를 구현한 객체라면 무엇이든 순회 가능
- 반복자와 인덱스 변수를 사용하지 않기 때문에 코드가 깔끔해지고 오류가 날 일이 없음
- 하나의 관용구로 되어있어 컬렉션과 배열 모두 같은 코드 형태이며 성능도 동일
- 콜론(:)은 안의(in) 라고 읽으면 됨
- 따라서 아래 코드의 반복문은 `elements 안의 각 원소 e에 대해`라고 해석하면 됨
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for (Element e : elements) { | |
... //e로 무언가를 한다 | |
} |
1. 중첩 컬렉션에서 for-each문의 이점 극대화
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
public class Card { | |
private final Suit suit; | |
private final Rank rank; | |
// Can you spot the bug? | |
enum Suit {CLUB, DIAMOND, HEART, SPADE} | |
enum Rank { | |
ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, | |
NINE, TEN, JACK, QUEEN, KING | |
} | |
static Collection<Suit> suits = Arrays.asList(Suit.values()); | |
static Collection<Rank> ranks = Arrays.asList(Rank.values()); | |
Card(Suit suit, Rank rank) { | |
this.suit = suit; | |
this.rank = rank; | |
} | |
public static void main(String[] args) { | |
List<Card> deck = new ArrayList<>(); | |
// 버그 발생하는 코드 | |
for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ) { | |
for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); ) { | |
deck.add(new Card(i.next(), j.next())); | |
} | |
} | |
} | |
} |

코드 부연 설명
- 위 코드에서 Iterator를 사용할 경우 `deck.add(new Card(i.next(), j.next()));`에서 NoSuchElementException 예외 발생
- next()가 Suit 하나당 한 번식만 불려야 하는데
- 안쪽 반복문에서 호출되는 바람에 Rank 하나당 한 번씩 불렸기 때문
- 위 코드를 고친다고 해도 코드가 지저분해지는 단점 존재
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ) { | |
Suit suit = i.next(); | |
for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); ) { | |
deck.add(new Card(suit, j.next())); | |
} | |
} |
- 반면 for-each문을 사용하면 깔끔하고 간단하게 오류를 유발하지 않는 코드를 작성할 수 있음
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
for (Suit suit : suits) { | |
for (Rank rank : ranks) { | |
deck.add(new Card(suit, rank)); | |
} | |
} |
2. for-each문을 사용할 수 없는 케이스
2.1 파괴적인 필터링(destructive filtering)
- 리스트를 순회하면서 원소를 제거할 때, 리스트의 구조가 변경되면서 ConcurrentModificationException이 발생
- 컬렉션을 순회하면서 선택된 원소를 제거해야 할 경우 반복자의 remove 메서드를 호출해야 함
- 자바 8버전부터 Collection의 removeIf 메서드를 사용해 컬렉션을 명시적으로 순회하지 않아도 됨
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
List<Integer> numbers = new ArrayList<>(); | |
numbers.add(1); | |
numbers.add(2); | |
numbers.add(3); | |
numbers.add(4); | |
numbers.add(5); | |
for (Integer number : numbers) { | |
if (number % 2 == 0) { | |
numbers.remove(number); // ConcurrentModificationException 발생! | |
} | |
} | |
System.out.println(numbers); |

This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Iterator<Integer> iterator = numbers.iterator(); | |
while (iterator.hasNext()) { | |
Integer number = iterator.next(); | |
if (number % 2 == 0) { | |
iterator.remove(); // 반복자의 remove 메서드를 호출하여 안전하게 제거 | |
} | |
} | |
System.out.println(numbers); |

2.2 변형(transforming)
- 리스트나 배열을 순회하면서 원소 값 일부 혹은 전체를 교체하는 경우에는 인덱스를 사용해야 함
- ex) arr[i] = 1;
2.3 병렬 반복(parallel iteration)
- 여러 컬렉션을 병렬로 순회해야 할 경우 각각의 반복자와 인덱스 변수를 사용해 엄격하고 명시적으로 제어해야 함
- for-each문은 각각의 요소에 대해 반복할 때마다 내부적으로 반복자(iterator)를 사용하여 요소를 가져오는데, 이러한 반복자는 병렬 처리를 지원하지 않음
참고
이펙티브 자바
반응형
'JAVA > Effective Java' 카테고리의 다른 글
[아이템 60] 정확한 답이 필요하다면 float와 double은 피하라 (0) | 2024.03.17 |
---|---|
[아이템 59] 라이브러리를 익히고 사용하라 (0) | 2024.03.17 |
[아이템 57] 지역변수의 범위를 최소화하라 (0) | 2024.03.16 |
[아이템 56] 공개된 API 요소에는 항상 문서화 주석을 작성하라 (0) | 2024.03.13 |
[아이템 55] 옵셔널 반환은 신중히 하라 (0) | 2024.03.12 |