devseop08 님의 블로그
[API] 람다와 스트림 - Mordern Java - 4. 스트림 소개 본문
4.1 스트림이란 무엇인가?
- 스트림은 자바 8 API에 새로 추가된 기능이다.
- 스트림을 이용하면 선언형으로 컬렉션 데이터를 처리할 수 있다, 즉 데이터를 처리하는 임시 구현 코드 대신 질의로 표현할 수 있다는 것이다.
- 스트림을 이용하면 멀티스레드 코드를 구현하지 않아도 데이터를 투명하게 병렬로 처리할 수 있다.
- 다음 예제는 저칼로리의 요리명을 반환하고 칼로리를 기준으로 요리를 정렬하는 자바 7 코드다.
List<Dish> lowCaloricDishes = new ArrayList<>();
for (Dish dish : menu){
if(dish.getCalories() < 400){
lowCaloricDishes.add(dish);
}
}
Collections.sort(lowCaloricDishes, new Comparator<Dish>(){
public int compare(Dish dish, Dish dish2){
return Integer.compare(dish1, getCalories(), dish2,
dish2.getCalories());
}
});
List<String> lowCaloricDishesName = new ArrayList<>();
for(Dish dish: lowCaloricDishes){
lowCaloricDishesName.add(dish.getName());
} - 위 코드에서는 lowCaloricDishes 가비지 변수를 사용했다. 즉, lowCaloricDishes는 컨테이너 역할만 하는 중간 변수다
- 자바 8에서 이러한 세부 구현은 라이브러리 내에서 모두 처리한다.
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toiList;
List<String> lowCaloricDishesName = menu.stream()
.filter(dish -> dish.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList())- stream()을 parallelStream()으로 변경하면 이 코드를 멀티코어 아키텍처에서 병렬로 실행할 수 있다.
import static java.util.Comparator.comparing;
import static java.util.stream.Collectors.toiList;
List<String> lowCaloricDishesName = menu.parallelStream()
.filter(dish -> dish.getCalories() < 400)
.sorted(comparing(Dish::getCalories))
.map(Dish::getName)
.collect(toList())스트림의 기능이 소프트웨어 공학적으로 주는 이득
- 선언형으로 코드를 구현할 수 있다. 루프와 if 조건문 등의 제어 블록을 사용해서 어떻게 동작을 구현할 지 지정할 필요없이 같은 동작의 수행을 지정할 수 있다. 선언형 코드와 동작 파라미터화를 활용하면 변하는 요구사항에 쉽게 대응할 수 있다.
- filter, sorted, map, collect 같은 여러 빌딩 블록 연산을 연결해서 복잡한 데이터를 처리하는 스트림 파이프라인을 만들 수 있다.
filter, sorted, map, collect와 같은 연산은 [^1]고수준 빌딩 블록으로 이루어져 있으므로 특정 스레딩 모델에 제한되지 않고 자유롭게 어떤 상황에서든 사용할 수 있다. 또한 이들은 내부적으로 단일 스레드 모델에 사용할 수 있지만, 멀티코어 아키텍처를 최대한 투명하게 활용할 수 있게 구현되어 있다.
스트림 파이프라인에서의 연산을 이용하면 데이터 처리 과정을 병렬화하면서 스레드와 락을 걱정할 필요없이 코드를 작성할 수 있다.
스트림 연산을 연결해서 스트림 파이프라인 형성

4.2 스트림 시작하기
- 컬렉션 클래스의 stream() 메서드는 컬렉션에 대한 스트림을 반환하며, stream() 메서드 이외에도 숫자 범위나 I/O 자원에서 스트림 요소를 만드는 등 다양한 방법으로 스트림을 얻을 수 있다.
- 스트림이란 데이터 처리 연산을 지원하도록 소스에서 추출된 연속된 요소로 정의할 수 있다.
- 연속된 요소 : 컬렉션과 마찬가지로 스트림은 특정 요소 형식으로 이루어진 연속된 값 집합의 인터페이스를 제공
- 컬렉션은 시간과 공간의 복잡성과 관련된 요소 저장 및 접근 연산이 주를 이루지만 스트림은 표현 계산식이 주를 이룬다. 즉, 컬렉션의 주제는 데이터이고, 스트림의 주제는 계산이다.
- 소스 : 스트림은 데이터 제공 소스로부터 데이터를 소비한다. 리스트로 스트림을 만들면 스트림의 요소는 리스트의 요소와 같은 순서를 유지한다.
- 데이터 처리 연산 : 스트림은 함수형 프로그래밍 언어에서 일반적으로 지원하는 연산과 데이터베이스와 비슷한 연산을 지원한다.
- 파이프라이닝: 대부분의 스트림 연산은 스트림 연산끼리 연결해서 커다란 파이프라인을 구성할 수 있도록 스트림 자신을 반환한다.
- 내부 반복: 반복자를 이용해서 명시적으로 반복하는 컬렉션과 달리 스트림은 내부 반복을 지원한다.
4.3 스트림과 컬렉션
- 컬렉션과 스트림 모두 연속된 요소 형식의 값을 저장하는 자료구조와 인터페이스를 제공한다.
- '연속된'이라는 표현은 순서와 상관없이 아무 값에나 접속하는 것이 아니라 순차적으로 값에 접근한다는 것을 의미한다.
- 스트림과 컬렉션의 차이
- 데이터를 언제 계산하느냐가 컬렉션과 스트림의 가장 큰 차이이다.
- 컬렉션은 현재 자료구조가 포함하는 모든 값을 모두 메모리에 저장하는 자료구조다.
- 즉, 컬렉션의 모든 요소는 컬렉션에 추가하기 전에 계산돼야 하므로, 컬렉션에는 새로운 요소를 추가하거나 기존 요소를 제거할 수 있게 된다.
- 스트림은 요청을 할 때만, 요청한 스트림의 요소에 대해서만 계산을 하는 고정된 자료구조이므로 스트림에 새로운 요소를 추가하거나 기존 요소를 제거할 수 없다.
- 사용자가 요청하는 값만 스트림에서 추출한다는 것이 핵심이며, 스트림은 게으르게 만들어지는 컬렉션과 같다. 즉, 사용자가 데이터를 요청할 때만 값을 계산한다.
- 스트림은 생산자와 소비자 관계를 형성한다.
- 지연된 연산, 지연 연산, 지연 평가, lazy evaluation
4.3.1 딱 한 번만 탐색할 수 있다.
- 반복자와 마찬가지로 스트림도 한 번만 탐색할 수 있다. 탐색된 스트림의 요소는 소비되고, 한 번 탐색한 요소를 다시 탐색하려면 초기 데이터 소스에서 새로운 스트림을 만들어야 한다. (단, 스트림의 소스가 컬렉션처러 반복이 가능한 자료가 아닌 i/o 채널이라면 소스를 반복할 수 없으므로 새로운 스트림을 만들 수 없다.)
- 스트림은 시간적으로 흩어진 값의 집합, 컬렉션은 특정 시간에 모든 것이 존재하는 공간이라 할 수 있다.
4.3.2 외부 반복과 내부 반복
- 스트림 라이브러리의 내부 반복은 데이터 표현과 하드웨어를 활용한 병렬성 구현을 자동으로 선택하지만 for-each 구문을 이용한 외부 반복에서는 병렬성을 스스로 관리해야 한다.

기본형 스트림 - IntStream, LongStream, DoubleStream
- 오토 박싱 & 언박싱 비효율이 제거됨
- 숫자와 관련된 유용한 메서드를
Stream<T>보다 더 많이 제공 - 데이터 소스가 특정 기본형일 때
4.4 스트림 연산
- 연결할 수 있는 스트림 연산을 중간 연산, 스트림을 닫는 연산을 최종 연산이라 한다.
4.4.1 중간 연산
- filter나 sorted 같은 중간 연산은 다른 스트림을 반환한다.
- 중간 연산의 중요한 특징은 단말 연산을 스트림 파이프라인에 실행하기 전까지는 아무 연산도 수행하지 않는다는 것이다. 즉, 게으르다는 것이다.
- 중간 연산을 합친 다음에 합쳐진 중간 연산을 최종 연산으로 한 번에 처리하기 때문이다.
- 스트림의 게으른 특성 덕분에 몇 가지 최적화 효과를 얻을 수 있다.
- short circuit
- 중간 연산이 병합되는 루프 퓨전
4.4.2 최종 연산
- 최종 연산은 스트림 파이프 라인에서 결과를 도출한다.
4.4.3 스트림 이용하기
- 질의를 수행할 데이터 소스
- 스트림 파이프라인을 구성할 중간 연산 연결
- 스트림 파이프라인을 실행하고 결과를 만들 최종 연산
| 연산 | 형식 | 반환 형식 | 연산의 인수 | 함수 디스크립터 |
|---|---|---|---|---|
| filter | 중간 연산 | Stream< T > | Predicate< T > | T -> boolean |
| map | 중간 연산 | Stream< T > | Function<T, R> | T -> R |
| limit | 중간 연산 | Stream< T > | ||
| sorted | 중간 연산 | Stream< T > | Comparator< T > | (T, T) -> int |
| distinct | 중간 연산 | Stream< T > |
| 연산 | 형식 | 반환 형식 | 목적 |
|---|---|---|---|
| forEach | 최종 연산 | void | 스트림의 각 요소를 소비하면서 람다를 적용한다. |
| count | 최종 연산 | long | 스트림의 요소 개수를 반환한다. |
| collect | 최종 연산 | 스트림을 리듀스해서 리스트, 맵, 정수 형식의 컬레션을 만든다. |
'Language > Java' 카테고리의 다른 글
| [JVM] 클래스 파일 구조 (3) | 2025.06.07 |
|---|---|
| [API] 람다와 스트림 : Mordern Java - 6. 스트림으로 데이터 수집 (0) | 2025.06.01 |
| [API] 람다와 스트림 : Mordern Java - 5. 스트림 활용 (1) | 2025.05.31 |
| [API] 람다와 스트림 - Mordern Java - 1. 기초 (1) | 2025.05.31 |
| [API] 람다와 스트림: Modern Java - 3. 람다 표현식 (4) | 2025.05.30 |