[교재 EffectiveJava] 아이템 58. 전통적인 for 문보다는 for-each문을...

[교재 EffectiveJava] 아이템 58. 전통적인 for 문보다는 for-each문을...

728x90

전통적인 for문

각 경우에 따라 스트림이 제격인 작업이 있고 반복이 제격인 작업이 있다.

컬렉션 순회하기 - 더 나은 방법

package com.java.effective.item58; public class Main { public static void main(String[] args) { for (Iterator i = c.iterator(); i.hasNext();) { element e = i.next(); ... // e 로 무언가를 한다. } } }

배열 순회하기

package com.java.effective.item58; public class Main { public static void main(String[] args) { for (int i = 0; i < a.length; i++) { ... // a[i] 로 무언가를 한다. } } }

이 관용구들은 while 문보다는 낫지만 가장 좋은 방법은 아니다. 반복자오 ㅏ인덱스 변수는 모두 코드를 지저분하게 할 뿐 우리가 사용할건 바로 원소다. 이처럼 쓰이는 요소 종류가 늘어나면 오류가 생길 가능성이 높아진다. 우리는 이를 for-each를 사용하여 해결할 수 있다.

for-each문

for-each 문의 정식 이름은 '향상된 for문 (enhanced for statement)'이다. 반복자와 인덱스 변수를 사용하지 않으니 코드가 깔끔해지고 오류가 날 일도 없다. 하나의 관용구로 컬렉션과 배열을 모두 처리할 수 있어서 어떤 컨테이너로 다루는지는 신경쓰지 않아도 된다.

package com.java.effective.item58; public class Main { public static void main(String[] args) { for (Element e : elements) { ... // e 로 무언가를 한다. } } }

: 을 "안의(in)"이라고 읽으면 된다. "elements 안의 각 원소 e에 대해"라고 읽는다. 반복대상이 컬렉션이든 배열이든, for-each문을 사용해도 속도는 그대로다. 컬렉션을 중첩해 순회해야한다면 for-each문의 이점이 더욱 커진다.

반복문을 중첩하는 코드를 보자.

package com.java.effective.item58; import java.util.*; public class Card { private final Suit suit; private final Rank rank; enum Suit {CLUB, DIAMOND, HEART, SPADE} enum Rank {ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING} Card (Suit suit, Rank rank ) { this.suit = suit; this.rank = rank; } static Collection suits = Arrays.asList(Suit.values()); static Collection ranks = Arrays.asList(Rank.values()); public static void main(String[] args) { List deck = new ArrayList<>(); for (Iterator i = suits.iterator(); i.hasNext();) { for (Iterator j = ranks.iterator(); j.hasNext();) { deck.add(new Card(i.next(), j.next())); } } } }

문제가 있다. 바깥 컬렉션(suits)의 반복자에서 next 메서드가 너무 많이 호출된다. 마지막 줄의 i.next()를 보자. 이 next()는 '숫자(Suit) 하나당' 한번씩만 불려야 하는데, 안쪽 반복문에서 호출되는 바람에 '카드(Rank) 하나당' 한번씩 불리고 있다. 이렇게되면 숫자가 바닥나서 반복문에서 NoSuchElementException을 던진다.

바깥 컬렉션의 크기가 안쪽 컬렉션의 크기의 배수라면 예외를 던지지 않고 종료될 수 있다.

좀더 나은 방법 - 해결방안

package com.java.effective.item58; import java.util.*; public class Card { private final Suit suit; private final Rank rank; enum Suit {CLUB, DIAMOND, HEART, SPADE} enum Rank {ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT, NINE, TEN, JACK, QUEEN, KING} Card (Suit suit, Rank rank ) { this.suit = suit; this.rank = rank; } static Collection suits = Arrays.asList(Suit.values()); static Collection ranks = Arrays.asList(Rank.values()); public static void main(String[] args) { List deck = new ArrayList<>(); // for (Iterator i = suits.iterator(); i.hasNext();) { // for (Iterator j = ranks.iterator(); j.hasNext();) { // deck.add(new Card(i.next(), j.next())); // } // } for (Iterator i = suits.iterator(); i.hasNext();) { Suit suit = i.next(); for (Iterator j = ranks.iterator(); j.hasNext();) { deck.add(new Card(suit, j.next())); } } } }

추가로 예를 들어보자.

주사위를 두번 굴렸을때 나올 수 있는 모든 경우의수를 출력하는 코드

package com.java.effective.item58; import java.util.*; public class DiceRolls { enum Face { ONE, TWO, THREE, FOUR, FIVE, SIX } public static void main(String[] args) { Collection faces = EnumSet.allOf(Face.class); for (Iterator i = faces.iterator(); i.hasNext();) { for (Iterator j = faces.iterator(); j.hasNext();) { System.out.println(i.next() + " " + j.next()); } } } }

결과

ONE ONE

TWO TWO

THREE THREE

FOUR FOUR

FIVE FIVE

SIX SIX

모든 경우의수를 출력하지않고 6쌍만 출력하고 종료된다. 이 문제를 해결하려면 바깥 반복문에 바깥 원소를 저장하는 변수를 하나 추가해야한다.

for-each문으로 해결

package com.java.effective.item58; import java.util.*; public class DiceRolls { enum Face { ONE, TWO, THREE, FOUR, FIVE, SIX } public static void main(String[] args) { Collection faces = EnumSet.allOf(Face.class); for (Face f1 : faces) { for (Face f2 : faces) { System.out.println(f1 + " " + f2); } } } }

for-each문이 사용 불가능한 상황

1) 파괴적인 필터링 (destructive filtering)

컬렉션을 순회하면서 선택된 원소를 제거해야 한다면 반복자의 remove 메서드를 호출해야한다. java8부터는 Collection의 removeIf 메서드를 사용하여 컬렉션을 명시적으로 순회하는 일을 피할 수 있다.

2) 변형 (transforming)

리스트나 배열을 순회하면서 그 원소의 값 일부 혹은 전체를 교체해야 한다면 리스트의 반복자나 배열의 인덱스를 사용해야한다.

3) 병렬 반복 (parallel iteration)

여러 컬렉션을 병렬로 순회해야한다면 각각의 반복자와 인덱스 변수를 사용해 엄격하고 명시적으로 제어해야한다.

위 3가지 상황 중 하나에 속할 경우 일반적으로 for 문을 사용하되, 앞서 언급되었던 문제들에 주의해야한다.

from http://devfunny.tistory.com/632 by ccl(A) rewrite - 2021-11-30 12:02:04