본문 바로가기
Web Programming Language/JAVA

스트림(Stream) - 스트림, 내부 반복자, 중간 처리와 최종 처리

by manchesterandthecity 2025. 3. 6.

자바 8부터 등장한 스트림(Stream) API컬렉션 데이터를 효율적으로 처리할 수 있도록 설계된 기능입니다.
스트림을 활용하면 코드를 간결하게 작성할 수 있으며, 병렬 처리(Parallel Processing)도 용이합니다.

이번 포스트에서는 스트림의 개념과 내부 반복자, 중간 처리 & 최종 처리에 대해 자세히 알아보겠습니다.

✅ 17.1 스트림이란?

🎯 1. 스트림(Stream)이란?

스트림(Stream)은 "데이터의 흐름"을 의미합니다.
데이터가 하나씩 흐르면서 처리되는 방식을 제공하는 것이 스트림 API입니다.

객체를 하나씩 흘려보내면서 처리하는 개념
컬렉션(리스트, 셋 등)에서 데이터를 하나씩 읽어와 처리하는 방식
✔ 기존 for문, while문을 이용한 외부 반복 방식과 다르게 내부 반복을 사용


🎯 2. 기존 방식(외부 반복자) vs. 스트림(내부 반복자)

외부 반복자(기존 방식)

List<String> list = Arrays.asList("홍길동", "김길동", "박길동");

// for문 사용 (외부 반복자)
for (int i = 0; i < list.size(); i++) {
    System.out.println(list.get(i));
}

// 향상된 for문 사용
for (String name : list) {
    System.out.println(name);
}

for문을 사용하여 요소를 하나씩 가져와 직접 처리
데이터를 가져오는 작업이 코드에 포함됨 (수동적 반복)

내부 반복자(스트림 방식)

list.stream().forEach(name -> System.out.println(name));

데이터를 직접 가져오는 것이 아니라 "흘러가는 데이터"를 처리하는 방식
컬렉션 내부에서 자동으로 데이터를 하나씩 흘려보내면서 처리
람다식을 활용하여 간결한 코드 작성 가능


✅ 17.2 내부 반복자 (Internal Iterator)

스트림 API내부 반복자를 활용하여 데이터를 자동으로 처리합니다.
내부 반복자는 컬렉션 내부에서 요소를 처리하는 방식을 의미합니다.

🎯 1. 외부 반복자 vs 내부 반복자 비교

반복 방식 코드 예시 특징
외부 반복자 for (String s : list) {} 직접 요소를 하나씩 가져와서 처리
내부 반복자 list.stream().forEach(s -> {}) 요소가 스트림을 따라 자동으로 처리됨

🎯 2. 내부 반복자 코드 예제

 
import java.util.Arrays;
import java.util.List;

public class StreamExample {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("홍길동", "김길동", "박길동");

        // 스트림을 이용한 내부 반복자
        list.stream().forEach(name -> System.out.println(name));
    }
}

스트림을 생성한 후 forEach()를 통해 요소를 하나씩 처리
코드가 훨씬 간결해지고, 유지보수도 쉬워짐


🎯 3. 내부 반복자가 더 좋은 이유

코드가 간결하고 직관적
데이터를 가져오는 과정이 자동화됨 → 속도 향상
병렬 처리가 용이하여 성능 최적화 가능


✅ 17.3 중간 처리 & 최종 처리

🎯 1. 스트림의 처리 과정

스트림의 데이터 처리는 "중간 처리" → "최종 처리" 단계로 나뉩니다.

처리 단계
설명 예제
중간 처리 데이터를 가공하는 과정 (필터링, 변환 등) filter(), map()
최종 처리 데이터를 최종적으로 사용하는 과정 (출력, 집계 등) forEach(), sum(), count()

 


🎯 2. 중간 처리 (Intermediate Operations)

중간 처리스트림을 변환하는 과정입니다.
대표적인 중간 처리 메소드로는 filter(), map(), sorted() 등이 있습니다.

필터링(filter) 예제

List<String> names = Arrays.asList("홍길동", "김철수", "이영희");

// 이름이 "김"으로 시작하는 데이터만 필터링
names.stream()
    .filter(name -> name.startsWith("김"))
    .forEach(System.out::println);

 

filter() : 조건에 맞는 요소만 걸러서 새로운 스트림 생성

변환(map) 예제

List<String> names = Arrays.asList("홍길동", "김철수", "이영희");

// 모든 이름을 대문자로 변환
names.stream()
    .map(String::toUpperCase)
    .forEach(System.out::println);

map() : 요소를 다른 형태로 변환하여 새로운 스트림 생성


🎯 3. 최종 처리 (Terminal Operations)

최종 처리스트림의 데이터를 집계하거나 출력하는 과정입니다.
대표적인 최종 처리 메소드로는 forEach(), sum(), count(), average() 등이 있습니다.

집계(count, sum, average) 예제

import java.util.Arrays;
import java.util.List;
import java.util.OptionalDouble;

public class StreamAggregationExample {
    public static void main(String[] args) {
        List<Integer> numbers = Arrays.asList(10, 20, 30, 40, 50);

        // 개수 카운트
        long count = numbers.stream().count();
        System.out.println("개수: " + count);  // 출력: 개수: 5

        // 총합
        int sum = numbers.stream().mapToInt(Integer::intValue).sum();
        System.out.println("총합: " + sum);  // 출력: 총합: 150

        // 평균
        OptionalDouble avg = numbers.stream().mapToInt(Integer::intValue).average();
        System.out.println("평균: " + avg.getAsDouble());  // 출력: 평균: 30.0
    }
}
 

count() : 요소 개수 구하기
sum() : 총합 구하기
average() : 평균 구하기



🎯 4. 중간 처리 + 최종 처리 (스트림 파이프라인)

중간 처리와 최종 처리를 연결하여 사용(파이프라인)할 수 있습니다.

 
List<String> names = Arrays.asList("홍길동", "김철수", "이영희");

// "김"으로 시작하는 이름만 필터링 후 대문자로 변환하여 출력
names.stream()
    .filter(name -> name.startsWith("김"))
    .map(String::toUpperCase)
    .forEach(System.out::println);
 

중간 처리(filter + map) : "김"으로 시작하는 데이터를 필터링 후 대문자로 변환
최종 처리(forEach) : 변환된 데이터를 출력


✅ 🎯 결론 (왜 스트림을 사용할까?)

내부 반복자 방식으로 데이터 처리 속도가 빠름
람다식과 함께 사용하여 코드가 간결하고 가독성이 좋음
필터링, 변환, 정렬, 집계 등 다양한 데이터 처리를 쉽게 구현 가능
병렬 처리(Parallel Processing)가 쉬움

 

 

 

 

 

참조:
이것이 자바다 _ 신용권

 

댓글