티스토리 뷰

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

 

🔎 for-each문이 전통적인 for문보다 무엇이 좋을까?

 

다음은 전통적인 for문으로 컬렉션을 순회하는 코드다.

순차적으로 발전하는 코드를 보여주면서 for-each문의 간결함을 볼 수 있을 것이다.

 

1. 전통적인 for문으로 컬렉션과 배열을 순회한다.

// 컬렉션 순회
for (Iterator<Element> i = c.iterator() ; i.hasNext(); ){
	Element e = i.next();
    ... // e로 무언가를 한다.
}

// 배열 순회
for (int i=0 ; i<a.length ; i++){
	... // e로 무언가를 한다.
}

위와 같은 코드로 작성했을 때 나타날 수 있는 위험은 다음과 같다.

  1. 쓰이는 요소 종류가 늘어나면 오류가 생길 가능성이 높아진다.
  2. 1회 반복에서 반복자는 세 번 등장하며, 인덱스는 네 번이나 등장하는데, 이럴 경우 변수를 잘못 사용할 틈새가 넓어진다.
  3. 혹시라도 잘못된 변수를 사용했을 때 컴파일러가 잡아준다는 보장도 없다.
  4. 컬렉션이냐 배열이냐에 따라 코드 형태가 상당히 달라지므로 주의해야 한다.

 

다음과 같은 문제들은 for-each문을 사용하면 모두 해결된다.

  • 1,2,3 -> 반복자와 인덱스 변수를 사용하지 않으니 코드가 깔끔해지고 오류가 날 일도 없다.
  • 4 -> 하나의 관용구로 컬렉션과 배열을 모두 처리할 수 있어서 어떤 컨테이너를 다루는지는 신경 쓰지 않아도 된다.

 

컬렉션을 중첩해 순회해야 한다면 for-each문의 이점은 더욱 커진다.

 

2. 

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());

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()));

위 코드는 버그가 있는 코드다.

바깥 컬렉션 즉, 마지막 줄의 i.next()는 '숫자(Suit) 하나당' 한 번씩만 불려야 하는데, 안쪽 반복문에서 호출되는 바람에 '카드(Rank) 하나당' 한 번씩 불리고 있다.

따라서 숫자가 바닥나면 반복문에서 NoSuchElementException을 던진다.

 

그렇다면, 위 코드를 다음과 같은 코드로 고칠 수 있다.

3. 

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문이다. 코드가 놀라자빠질만큼 짧아진다.

4.

for (Suit suit : suits)
	for (Rank rank : ranks)
    	deck.add(new Card(suit, rank));

 

 

하지만 완벽한 for-each여도 사용할 수 없는 상황이 존재한다. 무려 3가지!

  1. 파괴적인 필터링 (destructive filtering)
    • 컬렉션을 순회하면서 선택된 원소를 제거해야 한다면 반복자의 remove 메서드를 호출해야 한다.
    • 자바 8부터는 Collection의 removeIf 메서드를 사용해 컬렉션을 명시적으로 순회하는 일을 피할 수 있다.
  2. 변형 (transforming)
    • 리스트나 배열을 순회하면서 그 원소의 값 일부 혹은 전체를 교체해야 한다면, 리스트의 반복자나 배열의 인덱스를 사용해야 한다.
  3. 병렬 반복 (parallel iteration)
    • 여러 컬렉션을 병렬로 순회해햐 한다면 각각의 반복자와 인덱스 변수를 사용해 엄격하고 명시적으로 제어해야 한다.

 

 

🔎 for-each의 특징

  • for-each문은 컬렉션과 배열은 물론, Iterable 인터페이스를 구현한 객체라면 무엇이든 순회할 수 있다. (하지만 Iterable을 처음부터 직접 구현하기는 까다롭다. )
  • 명료하다
  • 유연하다
  • 버그를 예방해준다.
  • 성능저하도 없다.

 

 

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함