Stream이란?
자바 8부터 도입된 기능으로, 데이터의 흐름을 함수형 스타일로 처리할 수 있도록 해주는 API이다.
- Stream은 탐색, 정렬, 필터링, 매핑 등을 선언형(무엇을 할지) 방식으로 처리할 수 있게 한다.
- 컬렉션(List, Set 등)의 데이터를 하나씩 순차적으로 처리하는 파이프라인이다.
파이프라인이란?
- 시스템의 효율을 높이기 위해 명령문을 수행하면서 몇 가지 특수한 작업들을 병렬 처리하도록 설계된 것을 말한다.
- 쉽게 말해, 데이터를 여러 단계에 걸쳐 처리하는 흐름을 의미함.
- Stream에서 데이터를 중간 처리 연산자들을 거쳐 최종 연산으로 보내는 일련의 흐름(체인 구조)를 말함.
명령형 방식 코드
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Alex");
List<String> result = new ArrayList<>(); //중간 연산 결과를 저장하기 위한 List
for(String name : names){
if(name.startsWith("A")){ //필터링
result.add(name.toUpperCase()); //대문자 변환
}
}
Collections.sort(result); //정렬
Stream 방식(선언형) 코드
//데이터 소스 → 중간 연산 1 → 중간 연산 2 → ... → 최종 연산
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Alex"); //데이터 소스(Stream 생성)
.filter(name -> name.startsWith("A")) //중간 연산 1 (필터)
.map(String::toUpperCase) //중간 연산 2 (변형)
.sorted() //중간 연산 3 (정렬)
.collect(Collectors.toList()); //최종 연산 (수집)
//결과적으로 "Alice", "Alex"만 남고, toUpperCase, sorted 연산으로 "ALEX","ALICE"로 정렬되어 결과에 담기게 된다.
파이프라인의 구성 요소
| 단계 | 종류 | 설명 | 예시 메서드 |
| 1단계 | 데이터 소스 | 처리할 데이터를 스트림으로 변환 | .stream(), .of(), .range() |
| 2단계 | 중간 연산 | 데이터 필터링, 매핑, 정렬 등 | .filter(), .map(), .sorted() |
| 3단계 | 최종 연산 | 결과 반환하고 스트림 종료 | .collect(), .forEach(), .count() |
주의 중요 특징 : 지연 평가
- filter, map, sorted와 같은 중간 연산은 실행되지 않고 대기 상태이다.
- 최종 연산이 호출되기 전까지 아무 일도 일어나지 않는다.
- 최종 연산이 호출되면, 그때서야 파이프라인이 실행되고, 처리됨.
Stream의 기능
| 기능 | 설명 | 예시 메서드 |
| 탐색 | 조건에 맞는 데이터를 찾음 | filter(), findFirst(), anyMatch() |
| 변형(매핑) | 데이터를 바꿔줌 | map(), mapToInt() |
| 정렬 | 정렬된 스트림 생성 | sorted() |
| 집계 | 결과를 합침 | count(), sum(), max(), collect() |
| 루프 | 반복 실행 | forEach() |
Stream의 특징
- Stream은 저장소가 아니라 흐름이다. → 데이터를 저장하는 것이 X
- 자동으로 정렬하는 것이 X → 중간 연산자 sorted()를 써야 정렬됨.
- 탐색만 하는 것이 X → 필터링, 변형, 집계 등 다양한 연산이 가능하다.
- 1회성 객체이다 → 한 번 쓰고 나면 재사용 X
- 만약 재사용 시도시, 'IllegalStateException' 오류가 발생함.
- 중간 연산은 지연 평가되며, 최종 연산이 실행될 때만 동작함.
//예시
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Alex");
Stream<String> stream = names.stream();
boolean hasAlex = stream.anyMatch(name -> name.equals("Alex")); //사용함.
boolean hasBob = stream.noneMatch(name -> name.equals("Bob")); //오류 발생.
//.anyMath() 연산으로 stream이 소비된 후 닫혔고,
//이후 noneMatch()연산으로 이미 닫힌 stream을 재사용했기 때문에 IllegalStateException 발생.
//해결 방법
//1. Stream을 다시 생성 -> names.stream()을 매번 새로 호출하면 됨.
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Alex");
boolean hasAlex = names.stream().anyMath(name -> name.equals("Alex"));
boolean hasBob = namse.stream().noneMatch(name -> name.equals("Bob"));
//해결 방법
//2. 결과를 저장해서 재사용
List<String> names = Arrays.asList("Alice", "Bob", "Charlie", "Alex");
Stream<String> stream = names.stream();
List<String> list = stream.collect(Collectors.toList()); // 스트림 종료 후 재사용
boolean hasAlex = list.contains("Alex");
boolean hasBob = !list.contains("Bob");'BackEnd > Java' 카테고리의 다른 글
| [JAVA] Map.put()의 반환 규칙 : null을 반환하는 이유(putIfAbsent 메서드) (1) | 2025.08.12 |
|---|---|
| [JAVA] Map과 배열, try-catch와 Character.isDigit() 성능 분석 (+스택 트레이스) (0) | 2025.05.23 |
| [JAVA] 자동 형변환 (char + char 연산) (0) | 2025.05.14 |