전통적인 for문
각 경우에 따라 스트림이 제격인 작업이 있고 반복이 제격인 작업이 있다.
컬렉션 순회하기 - 더 나은 방법
package com.java.effective.item58;
public class Main {
public static void main(String[] args) {
for (Iterator<Element> 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<Suit> suits = Arrays.asList(Suit.values());
static Collection<Rank> ranks = Arrays.asList(Rank.values());
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()));
}
}
}
}
문제가 있다. 바깥 컬렉션(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<Suit> suits = Arrays.asList(Suit.values());
static Collection<Rank> ranks = Arrays.asList(Rank.values());
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()));
// }
// }
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()));
}
}
}
}
추가로 예를 들어보자.
주사위를 두번 굴렸을때 나올 수 있는 모든 경우의수를 출력하는 코드
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<Face> faces = EnumSet.allOf(Face.class);
for (Iterator<Face> i = faces.iterator(); i.hasNext();) {
for (Iterator<Face> 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<Face> 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 문을 사용하되, 앞서 언급되었던 문제들에 주의해야한다.
'Book > Effective Java' 카테고리의 다른 글
[교재 EffectiveJava] 아이템 60. 정확한 답이 필요하다면 float와 double은 피하라 (0) | 2021.12.02 |
---|---|
[교재 EffectiveJava] 아이템 59. 라이브러리를 익히고 사용하라 (0) | 2021.12.01 |
[교재 EffectiveJava] 아이템 57. 지역변수의 범위를 최소화하라 (0) | 2021.11.29 |
[교재 EffectiveJava] 아이템 56. 공개된 API 요소에는 항상 문서화 주석을 작성하라 (0) | 2021.11.28 |
[교재 EffectiveJava] 아이템 55. 옵셔널 반환은 신중히 하라 (0) | 2021.11.26 |