본문 바로가기
Language/Java

[JAVA] Java Stream 중간 연산과 최종 연산의 차이점 완벽 분석

by Papa Martino V 2026. 1. 22.
728x90

Java Stream
Java Stream

 

Java 8부터 도입된 스트림(Stream)은 데이터를 처리하는 방식을 획기적으로 변화시켰습니다. 스트림의 핵심은 여러 연산을 연결하여 하나의 파이프라인(Pipeline)을 구성하는 것인데, 이때 연산은 크게 중간 연산(Intermediate Operation)최종 연산(Terminal Operation)으로 나뉩니다. 이 둘의 메커니즘을 정확히 이해하는 것은 효율적인 코드 작성뿐만 아니라 성능 최적화의 열쇠가 됩니다. 본 포스팅에서는 두 연산의 기술적 차이점과 함께 스트림의 효율성을 극대화하는 '지연 연산(Lazy Evaluation)'의 원리를 상세히 다룹니다.

1. 중간 연산 vs 최종 연산 핵심 비교

스트림 연산을 공장의 조립 라인에 비유하자면, 중간 연산은 제품을 깎거나 도색하는 '가공 단계'이고, 최종 연산은 완성된 제품을 박스에 담아 '출고하는 단계'입니다.

구분 중간 연산 (Intermediate) 최종 연산 (Terminal)
반환 타입 Stream 객체 (Stream<T>) 기본 타입, 컬렉션, void 등 (Stream 아님)
연산 횟수 여러 번 연결 가능 (Chaining) 단 한 번만 수행 가능
실행 시점 최종 연산이 호출될 때까지 지연됨 호출 즉시 연산 수행 및 스트림 닫힘
주요 메서드 filter, map, flatMap, sorted, distinct forEach, collect, reduce, count, anyMatch

2. 지연 연산(Lazy Evaluation)의 마법

중간 연산의 가장 큰 특징은 "지연(Lazy)"된다는 점입니다. 최종 연산이 호출되기 전까지 중간 연산은 아무런 작업도 수행하지 않습니다. 단지 어떤 작업을 할지에 대한 '설계도'를 그리는 과정입니다. 이 메커니즘 덕분에 JVM은 스트림 파이프라인을 최적화할 수 있습니다. 예를 들어, 100만 개의 데이터 중 filter로 3개만 거르고 limit(1)을 건다면, 전체를 다 훑는 것이 아니라 조건에 맞는 1개를 찾는 즉시 연산을 종료(Short-circuit)합니다.

3. 실전 샘플 예제 (Sample Example)

코드를 통해 중간 연산의 체이닝과 최종 연산의 마무리를 확인해 보겠습니다.

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class StreamOperationExample {
    public static void main(String[] args) {
        List<String> members = Arrays.asList("Kim", "Lee", "Park", "Choi", "Kang");

        // 스트림 파이프라인 시작
        List<String> result = members.stream()
            .filter(name -> {
                System.out.println("중간 연산 필터링: " + name);
                return name.length() >= 4;
            }) // 중간 연산 1
            .map(name -> {
                System.out.println("중간 연산 변환: " + name);
                return name.toUpperCase();
            }) // 중간 연산 2
            .sorted() // 중간 연산 3
            .collect(Collectors.toList()); // 최종 연산 (이때 모든 출력이 발생함)

        System.out.println("결과: " + result);
    }
}
    

4. 전문적인 성능 최적화 팁

  • 상태 없는 연산 vs 상태 있는 연산: filtermap은 이전 요소를 몰라도 처리 가능한 'Stateless' 연산이지만, sorteddistinct는 전체 데이터를 확인해야 하는 'Stateful' 연산입니다. Stateful 연산은 병렬 스트림에서 성능 저하를 일으킬 수 있으므로 주의해야 합니다. 최종 연산의 선택: 단순히 출력만 할 것이라면 forEach를 쓰되, 데이터를 가공하여 다시 사용해야 한다면 collect를 사용하는 것이 부수 효과(Side-effect)를 줄이는 정석적인 방법입니다.

5. 결론

Java 스트림의 중간 연산은 데이터 가공의 흐름을 정의하고, 최종 연산은 그 흐름을 실행하여 결과를 도출합니다. 이 두 단계의 분리는 코드의 선언적 가독성을 높여줄 뿐만 아니라, 불필요한 연산을 건너뛰는 지능적인 최적화를 가능케 합니다.


출처 및 참고문헌:

  • Oracle Java Documentation: Package java.util.stream
  • Modern Java in Action (Raoul-Gabriel Urma 외)
  • Baeldung: Java 8 Streams Intermediate and Terminal Operations
728x90