꾸준한 스터디
article thumbnail

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

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

 

전통적인 for 문으로 배열 순회하는 코드

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

 

이 관용구들은 while 문보다는 낫지만 가장 좋은 방법은 아니다.

반복자와 인덱스 변수는 모두 코드를 지저분하게 할 뿐 진짜 필요한건 원소들이다.

이처럼 쓰이는 요소 종류가 늘어나면 오류가 생길 가능성이 높아진다.

혹시라도 잘못된 변수를 사용했을 때 컴파일러가 잡아주리라는 보장도 없다.

마지막으로 컬렉션이냐 배열이냐에 따라 코드 형태가 달라지므로 주의해야 한다.

 

이상의 문제는 for-each 문을 사용하면 모두 해결된다.

반복자와 인덱스 변수를 사용하지 않으니 코드가 깔끔해지고 오류가 날 일도 없다.

하나의 관용구로 컬렉션과 배열을 모두 처리할 수 있어서 어떤 컨테이너를 다루는지 신경쓰지 않아도 된다.

for-each 문이 만들어내는 코드는 사람이 손으로 최적화한 것과 사실상 같아서 반복 대상이 컬렉션이든 배열이든 사용 속도는 그대로다.

 

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

반복문을 중첩할 때 흔히 저지르는 실수로 i.next()는 바깥쪽 반복문에서 사용해야하는데 13번의 순회가 도는 안쪽 반복문에서 실행되고 있어서 4번 순회 후 메서드 실행하면 요소를 찾을 수 없다는 NoSuchElementException을 던진다.

 

바깥 반복문에서 Suit 요소를 변수에 담아 안쪽 반복문에 넣어줬다.

 

 

정말 운이 나빠서 바깥 컬렉션의 크기가 안쪽 컬렉션의 크기의 배수라면 반복문이 예외를 던지지 않고 종료한다.

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

36개 조합이 나오는 것이 아니라 6개의 쌍만 출력하고 끝나버린다.

 

역시 바깥 반복문에서 첫번째 주사위 결과값을 저장 하고 안쪽 반복문에 넘겨주었다.

 

 

for-each 문을 사용하면 코드도 간결해지고 문제도 해결된다.

 

 

 

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

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

 

for-each 문은 컬렉션과 배열은 물론 Iterable 인터페이스를 구현한 객체라 무엇이든 순회할 수 있다.

Iterable을 처음부터 직접 구현하기는 까다롭지만, 원소들의 묶음을 표현하는 타입을 작성해야 한다면 Iterable을 구현하는 쪽으로 고민해보기 바란다.

profile

꾸준한 스터디

@StudyRecord

포스팅이 유익하셨다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!