스트림
스트림(Stream)이란 (Java 8 이후부터)컬렉션, 배열 등 데이터 소스를 다루는 데 자주 사용되는 정렬, 필터링, 변환 등의 작업을 함수형 스타일로 처리할 수 있도록 돕는 연속적인 요소들의 흐름이다. 따라서 데이터를 저장하는 저장소가 아닌, 데이터를 처리하는 파이프라인 역할을 한다.
스트림의 핵심 특징 및 속성 표는 다음과 같다.
| 특징 | 설명 |
|---|---|
| 내부 반복(Internal Iteration) | 개발자가 직접 for, Iterator 연산자로 요소를 순회하는 외부 반복 대신, 스트림이 내부적으로 반복을 처리한다. 불필요한 코딩 과정을 줄여 코딩 가독성을 향상시킨다. |
| 비파괴적(Immutable) | 스트림 연산은 원본 데이터 소스(컬렉션/배열)를 변경하지 않는다. 데이터를 읽어와 처리한 후에 변경된 내용은 새로운 스트림이나 최종 결과로 산출된다. |
| 지연 실행(Lazy Evaluation) | 스트림에는 중간 연산과 최종 연산이 있다. 중간 연산을 여러 개 작성하더라도 실제로 실행되지 않고 하나의 파이프라인으로 합쳐지며, 최종 연산이 호출될 때 한 번에 처리된다. |
| 재사용 불가 | 최종 연산이 완료된 후 스트림은 닫히므로 재사용할 수 없다. 데이터를 저장하는 목적이 아니기 때문에, 다시 처리하려면 데이터 소스로부터 새로운 스트림을 생성해야 한다. |
스트림의 데이터 처리는 중간 연산과 최종 연산으로 구성된 파이프라인 형태로 진행된다.
| 연산 종류 | 특징 | 주요 예시 |
| 중간 연산(Intermediate) | 데이터를 처리하고 가공하여, 결과로 새로운 스트림을 반환하여 연쇄적인 파이프라이닝이 가능하다. | filter, map, flatMap, sorted 등 |
| 최종 연산(Terminal) | 파이프라인이 실질적인 결과를 산출하며 스트림을 닫는다. 즉시 실행되어 모든 중간 연산을 구동한다. | forEach, collect, count, sum 등 |
파이프라인 구성 예시 코드는 다음과 같다.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "David");
long count = names.stream() // 1. 스트림 생성
.filter(name -> name.length() > 3) // 2. 중간 연산 (길이가 3 초과)
.map(String::toUpperCase) // 2. 중간 연산 (대문자 변환)
.count(); // 3. 최종 연산 (개수 계산, 즉시 실행)
// .count()가 호출될 때 filter와 map 연산이 최적화되어 한 번에 실행된다.
스트림 병렬 처리의 장점
스트림은 멀티 코어 환경에서 병렬 처리에 유리하도록 설계되었다.
-
parallel() 메소드: 병렬 처리를 위해 공용 풀(Common Fork Join Pool)을 활용한다.
-
공용 풀 사용: 병렬 처리를 위해 사용하는 것으로 각 스레드가 개별 큐를 가지고 있다. 놀고 있는 스레드가 있으면, 일하는 스레드의 작업을 가져와 수행(Work Stealing)함으로 최적의 성능을 도출한다.
-
병렬 처리 성능 향상: 코어의 수가 많을 수록, 처리할 데이터의 수가 많을 수록, 데이터 당 처리 시간이 길 수록 성능 향상 효과가 크다.
-
유리한 데이터 소스: 배열,
ArrayList클래스처럼 인덱스를 통해 요소에 빠르게 접근할 수 있는 데이터 소스에 유리하다.LinkedList클래스처럼 순차 접근이 필요한 경우는 병렬 처리 효율이 낮을 수 있다.
-