전통적인 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을 구현하는 쪽으로 고민해보기 바란다.
'Effective Java > 정리' 카테고리의 다른 글
Item61. 박싱된 기본 타입보다는 기본 타입을 사용하라 (0) | 2023.08.08 |
---|---|
Item60. 정확한 답이 필요하다면 float과 double은 피하라 (0) | 2023.08.07 |
Item57. 지역변수의 범위를 최소화 하라 (0) | 2023.07.30 |
Item56. 공개된 API 요소에는 항상 문서화 주석을 작성하라 (0) | 2023.07.22 |
Item55. 옵셔널 반환은 신중히 하라 (0) | 2023.07.21 |