📌 for-each : 향상된 for문(enhanced for statement)
// 반복자를 이용한 반복문
for (Iterator<Element> i = c.iterator(); i.hasNext();) {
Element e = i.next();
...
}
// 인덱스 변수를 사용한 반복문
for (int i = 0; i < a.length; i++) { ... }
// for-each문을 사용한 반복문
for (Element e : elements) { ... }
- 불필요하게 코드를 복잡하게 만드는 요소를 모두 제거하고, 컬렉션의 원소에 집중할 수 있게 한다.
- 하나의 관용구로 컬렉션과 배열을 모두 처리할 수 있어, 어떤 컨테이너를 다루는지 신경쓸 필요 없다.
- 반복 대상에 상관없이 for-each문의 속도는 같다. for-each문이 만들어내는 코드는 사람이 손으로 최적화한 것과 사실상 같기 때문이다.
- for-each 문은 Collection, Arrays는 물론 Iterable 인터페이스를 구현한 객체라면 무엇이든 순회할 수 있다.
- 원소들의 묶음을 표현하는 타입을 작성해야 한다면 Collection 인터페이스는 구현하지 않더라도, Iterable을 구현하는 쪽으로 고민해보라.
📌 As-is. 중첩 반복문
enum Suit {
CLUB, DIAMOND, HEART, SPADE
}
enum Rank {
ACE, DEUCE, 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;
}
}
public class Main {
static Collection<Suit> suits = Arrays.asList(Suit.values());
static Collection<Rank> ranks = Arrays.asList(Rank.values());
public static void main(String[] args) {
Collection<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()));
System.out.println(deck.size());
}
}
- 바깥 컬렉션 suits의 반복자에서 next 메서드가 너무 자주 호출되고 있다.
- 'Suit 하나당'이 아니라 'Rank 하나당' 한 번씩 불리므로 숫자가 바닥나면 NoSuchElementException을 던진다.
enum Face { ONE, TWO, THREE, FOUR, FIVE, SIX }
public class Main {
public static void main(String[] args) {
for (Iterator<Face> i = faces.iterator(); i.hasNext(); )
for (Iterator<Face> j = faces.iterator(); j.hasNext(); )
System.out.println(i.next() + " " + j.next());
}
}
- 바깥 컬렉션 크기가 안쪽 컬렉션 크기 배수라면, 예외를 던지지 않고 잘못된 결과를 반환한 채 끝날 수도 있다.
이 문제를 단순하게 해결하는 방법은 바깥 반복문에 바깥 원소를 임시 저장하는 변수를 추가해야 한다.
Collection<Card> deck = new ArrayList<>();
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를 사용하면 아주 간단하게 해결할 수 있다.
for (Suit suit : suits)
for (Rank rank : ranks)
deck.add(new Card(suit, rank));
📌 for-each 문을 사용할 수 없는 경우
- 파괴적인 필터링(destructive filtering)
- 컬렉션을 순회하면서 선택된 원소를 제거해야 하는 경우엔 Iterator의 remove 메서드가 필요하다.
- Java 8부터는 Collection의 removeIf 메서드로 Collection을 명시적을 순회하지 않아도 된다.
- 변형(transforming)
- 리스트나 배열을 순회하면서 원소의 값 일부 혹은 전체를 교체하는 경우엔 index 값이 필요하다.
- 병렬 반복(parallel iteration)
- 여러 컬렉션을 병렬 순회하는 경우, 각각의 Iterator와 index 변수를 사용해 엄격하고 명시적으로 제어해야 한다.
✒️ removeIf 예제
List<Integer> numbers = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9));
System.out.println("numbers: " + numbers.toString());
numbers.removeIf(n -> (n % 3 == 0));
System.out.println("numbers(after remove): " + numbers.toString());
numbers: [1, 2, 3, 4, 5, 6, 7, 8, 9]
numbers(after remove): [1, 2, 4, 5, 7, 8]
✒️ 병렬 반복
List<Integer> numbers1 = new ArrayList<>(Arrays.asList(1, 2, 3, 4, 5));
List<Integer> numbers2 = new ArrayList<>(Arrays.asList(6, 7, 8, 9, 10));
for (int i = 0; i < numbers1.size(); i++) {
System.out.println(numbers1.get(i) + ", " + numbers2.get(i));
}