아이템 58. 전통적인 for문 보다는 for each문을 사용하라. - ksw6169/effective-java GitHub Wiki

전통적인 for문의 단점

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

// 반복자(Iterator)를 활용
for (Iterator<Element> i = c.iterator(); i.hasNext();) {
    Element e = i.next();
    ... // e로 무언가를 한다.
}

// index를 활용
for (int i=0; i<a.length; i++) {
    ... // a[i]로 무언가를 한다.
}

이 관용구들은 while 문보다는 낫지만, 반복자와 인덱스 변수는 모두 코드를 지저분하게 할 뿐 우리에게 진짜 필요한 건 원소들 뿐이다. 다음은 전통적인 for문의 단점이다.

  • 쓰이는 요소 종류(반복자, 인덱스 변수)가 늘어나면 오류가 생길 가능성이 높아진다.
  • 혹시라도 잘못된 변수를 사용했을 때 컴파일러가 잡아주리라는 보장도 없다.
  • 컬렉션이냐 배열이냐에 따라 코드 형태가 달라진다.

for-each문의 장점

for-each문의 정식 명칭은 '향상된 for문(enhanced for statement)' 으로 for-each문을 사용했을 때의 장점은 다음과 같다.

  • 반복자와 인덱스 변수를 사용하지 않으니 코드가 간결해지고 오류가 날 일도 없다.
  • 컬렉션을 중첩해 순회해야 하는 경우에도 변수를 잘못 사용할 일이 없어 좋다.
  • 하나의 관용구로 컬렉션과 배열을 모두 처리할 수 있다.
  • 반복 대상이 컬렉션이든 배열이든 for-each 문을 사용해도 속도는 그대로다.
for (Element e : elements) {
    ... // e로 무언가를 한다.
}

for-each문을 사용할 수 없는 상황

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

컬렉션을 순회하면서 특정 원소를 제거해야 한다면 반복자의 remove 메소드를 호출할 수 있지만, 현재 요소를 제거하면 컬렉션이 순차적으로 순회하는 것에 실패하면서 ConcurrentModificationException 이 발생한다. 자바 8부터는 이러한 문제를 방지하기 위해 Collection의 removeIf 메소드를 사용할 것을 권장한다. removeIf 문을 사용하면 컬렉션을 명시적으로 순회하는 일을 피할 수 있다. (내부적으로 Iterator를 사용해서 순회 처리한다.)

List<String> strs = new ArrayList<>();
strs.add("A");
strs.add("B");
strs.add("C");
strs.add("D");

for (String s : strs) {
    if (s.equals("B"))
        strs.remove(s); // ConcurrentModificationException 발생
}

2. 변형(transforming)

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


3. 병렬 반복(parallel iteration)

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


끝으로..

  • for-each문은 Iterable 인터페이스를 구현한 객체라면 무엇이든 순회할 수 있다.
  • 원소들의 묶음을 표현하는 타입을 작성해야 한다면 Iterable을 구현하는 쪽으로 고민하라. Iterable을 구현해두면 그 타입을 사용하는 다른 개발자들도 for-each문을 통해 유용하게 사용할 수 있기 때문이다.

핵심 정리

  • 전통적인 for문과 비교했을 때 for-each는 명료하고, 유연하고, 버그를 예방해준다.
  • 또한 성능 저하도 없으므로 가능한 모든 곳에서 for문이 아닌 for-each 문을 사용하자.

참고 자료

⚠️ **GitHub.com Fallback** ⚠️