작성 정보

- 작성일 : 2026-05-29

- 관련 문제 : PG42579 베스트 앨범

forEach는 컬렉션의 각 원소에 대해 작업을 수행할 때 쓴다. 새 값을 만들어 반환하는 API가 아니라, 이미 있는 원소에 대해 동작을 실행하는 API라고 보는 편이 안전하다.

forEach가 어울리는 경우

각 장르의 노래 목록을 정렬하는 코드는 forEach와 잘 맞는다.

genreMap.values().forEach(g -> g.songs.sort(Song::compareTo));

여기서는 새 컬렉션을 만드는 것이 아니라, 이미 존재하는 각 Genresongs 리스트를 정렬한다. 즉 부수 효과가 목적이다.

일반 for문으로 쓰면 다음과 같다.

for (Genre genre : genreMap.values()) {
    genre.songs.sort(Song::compareTo);
}

둘은 의미상 거의 같다. 코딩테스트에서는 더 읽기 쉬운 쪽을 고르면 된다.

forEach가 덜 어울리는 경우

값을 변환해서 새 리스트를 만들고 싶다면 map, filter, collect가 더 맞다.

List<Integer> indexes = songs.stream()
        .map(song -> song.index)
        .collect(Collectors.toList());

이런 작업을 forEach로도 만들 수는 있다.

List<Integer> indexes = new ArrayList<>();
songs.forEach(song -> indexes.add(song.index));

하지만 스트림을 이미 쓰고 있다면 map이 의도를 더 잘 드러낸다. forEach는 "각각 실행"이고, map은 "변환"이다.

stream 안에서 forEach를 조심해야 하는 이유

스트림 중간에서 외부 리스트에 값을 추가하는 방식은 코드 흐름이 흐려질 수 있다.

List<Integer> answer = new ArrayList<>();

genreMap.entrySet().stream()
        .sorted(Map.Entry.comparingByValue())
        .forEach(e -> answer.add(...));

이 방식이 항상 틀린 것은 아니다. 다만 반환값을 만드는 흐름이라면 다음처럼 스트림의 마지막까지 연결하는 쪽이 더 읽기 쉽다.

int[] answer = genreMap.entrySet().stream()
        .sorted(Map.Entry.comparingByValue())
        .flatMap(e -> e.getValue().songs.stream().limit(2))
        .mapToInt(song -> song.index)
        .toArray();

기억할 기준

목적API
각 원소에 작업 실행forEach
원소를 다른 값으로 변환map
일부만 남기기filter
여러 스트림을 하나로 펴기flatMap
결과 컬렉션 만들기collect
primitive 배열 만들기mapToInt(...).toArray()

실수 체크

  • 새 값을 만들려는 작업에 forEach를 억지로 쓰지 않았는가
  • 기존 객체를 정렬하거나 갱신하는 작업이라 forEach가 자연스러운가
  • 스트림 결과를 반환해야 한다면 map, flatMap, collect, toArray 흐름이 더 맞지 않은가
  • 일반 for문이 더 읽기 쉬우면 무리해서 forEach를 쓰지 않았는가