개발/Java

Java Stream에서 map과 forEach의 차이

hanks 2025. 6. 21. 22:46

 

Java Stream API를 사용하다 보면 map()과 forEach()를 자주 만나게 된다. 두 메서드는 비슷해 보이지만 목적과 사용하는 상황이 다르다. 이 글에서는 map()과 forEach()의 차이를 실제 예제와 함께 정리한다.


1. map() : 변환(Transformation)에 사용

  • 설명: map()은 스트림의 각 요소를 특정 방식으로 변환할 때 사용한다.
  • 리턴값: 변환된 값들로 새로운 스트림(Stream)을 생성한다.
  • 주 용도: 데이터를 가공하거나 타입을 변경해야 할 때 사용한다.
  • 중간 연산: map은 중간 연산이다. 즉, map 이후에 또 다른 스트림 연산이 이어질 수 있다.

예시

List<String> names = Arrays.asList("홍길동", "이순신", "강감찬");
List<Integer> nameLengths = names.stream()
    .map(String::length) // 각 이름의 길이로 변환
    .collect(Collectors.toList()); // 결과를 리스트로 수집
  • map(String::length) 부분에서 각 문자열을 그 길이로 변환한다.
  • 변환된 값들은 새로운 스트림으로 이어진다.

2. forEach() : 소비(Consumption)에 사용

  • 설명: forEach()는 스트림의 각 요소를 소비(Consume)할 때 사용한다.
  • 리턴값: 반환값이 없다(void). 결과를 저장하지 않는다.
  • 주 용도: 최종적으로 무언가를 출력하거나, DB에 저장, 외부 시스템과 연동 등 부수효과(side-effect)가 필요할 때 사용한다.
  • 최종 연산: forEach는 최종 연산이다. forEach 이후에는 더 이상 스트림 연산이 이어질 수 없다.

예시

List<String> names = Arrays.asList("홍길동", "이순신", "강감찬");
names.stream()
    .forEach(name -> System.out.println(name)); // 각 요소를 출력
  • 각 요소에 대해 print 작업만 하고, 새로운 값을 만들지 않는다.

3. map() vs forEach() 차이 정리

구분map()forEach()

용도 변환(Transformation) 소비(Consumption)
리턴값 변환된 스트림 없음(void)
연산종류 중간 연산 최종 연산
주 사용처 값 가공/타입 변경/파이프라인 출력/저장/외부 연동 등 부수효과

4. map과 forEach를 함께 쓰는 경우

보통 데이터를 변환한 후, 그 결과를 소비해야 할 때 두 메서드를 조합해서 사용한다.

예시

List<String> names = Arrays.asList("홍길동", "이순신", "강감찬");
names.stream()
    .map(String::toUpperCase) // 모든 이름을 대문자로 변환
    .forEach(System.out::println); // 변환된 값을 출력
  • map으로 변환 후, forEach로 결과를 출력한다.

5. 주의할 점

  • map을 사용해도 forEach처럼 부수효과만 남기는 코드를 작성하면 안된다. (map은 반드시 변환 용도로만)
  • forEach에서 값을 가공해도 결과를 저장하지 않기 때문에 데이터 변환에는 적합하지 않다.

6. 결론

  • 데이터를 변환(가공)할 때는 map()
  • 데이터를 최종적으로 소비(출력, 저장)할 때는 forEach()
  • map은 중간 연산, forEach는 최종 연산

각 메서드의 역할과 용도를 잘 구분해서 사용해야 코드를 더 읽기 쉽고 유지보수하기 편하다.