Stream이란?
컬렉션 데이터를 다룰 때 효율적이고 직관적인 방법을 제공하는 API
다양한 데이터 소스를 표준화된 방법으로 다루기 위한 것
Stream을 통해 데이터의 필터링, 정렬, 변환, 집계 등을 함수형 프로그래밍 스타일로 구현할 수 있다.
특징
- 단일 사용 : Stream은 한 번만 사용 가능한 단일 데이터 흐림이다. 한 번 사용한 Stream은 재사용 할 수 없으며 새로운 Stream을 생성해야 한다.
- 내부 반복 : 기존의 외부 반복(for-each)을 대체하여 내부적으로 데이터 처리를 반복하는 방식이다.
- 지연 연산 : Stream에서 filter(), map() 같은 중간 연산은 즉시 실행되지 않고 최종 연산이 호출될 때 실행된다. 불필요한 연산을 줄일 수 있다.
- 병렬 처리 기능 : parallelStream() 을 통해 쉽게 병렬 처리를 할 수 있어 대용량 데이터 처리 시 성능 향상을 기대할 수 있다.
- Read-Only : Stream은 원본 데이터를 변경하지 않는다.
생성 -> 중간 연산(n번) -> 최종 연산(1번)
Stream 생성
주로 컬렉션, 배열, 범위 등의 데이터 소스에서 생성된다.
// 컬렉션에서 생성
List<String> list = Arrays.asList("apple", "banana", "cherry");
Stream<String> stream = list.stream();
// 배열에서 생성
String[] arr = {"apple", "banana", "cherry"};
Stream<String> streamFromArray = Arrays.stream(arr);
// 범위에서 생성
IntStream intStream = IntStream.range(1, 10); // 1 ~ 9
LongStream longStream = LongStream.rangeClosed(1, 10); // 1 ~ 10
중간 연산
중간 연산은 여러 개를 연이어 사용할 수 있으며 최종 연산이 호출될 때까지 실행되지 않는 지연 연산이다.
filter(Predicate<T> predicate) : 조건에 맞는 요소만 선택한다.
stream.filter(s -> s.startsWith("a"));
map(Function<T, R> mapper) : 요소를 변환한다.
stream.map(String::toUpperCase);
flatMap(Function<T, Stream<R>> mapper) : 중첩 구조를 평평하게 만든다.
Stream<List<String>> listStream = Stream.of(
Arrays.asList("a", "b"), Arrays.asList("c", "d"));
listStream.flatMap(Collection::stream);
distinct() : 중복된 요소를 제거한다.
stream.distinct();
sorted() : 요소를 정렬한다.
stream.sorted();
limit(long maxSize) : 지정한 개수만큼 요소를 선택한다.
stream.limit(3);
skip(long n) : 처음 n개의 요소를 건너뛴다.
stream.skip(2);
최종 연산
최종 연산은 스트림을 닫고 결과를 반환하며 최종 연산 후에는 스트림을 재사용할 수 없다.
forEach(Consumer<T> action) : 각 요소를 소비한다.
stream.forEach(System.out::println);
collect(Collector<T, A, R> collector) : 결과를 컬렉션으로 수집한다.
List<String> resultList = stream.collect(Collectors.toList());
count() : 요소의 개수를 반환한다.
long count = stream.count();
reduce(BinaryOperator<T> accumulator) : 모든 요소를 누적하여 하나의 결과로 만든다.
Optional<String> reduced = stream.reduce((s1, s2) -> s1 + s2);
allMatch(Predicate<T> predicate) : 모든 요소가 조건에 맞으면 true를 반환한다.
boolean allMatch = stream.allMatch(s -> s.length() > 1);
noneMatch(Predicate<T> predicate) : 조건에 맞는 요소가 하나도 없으면 true를 반환한다.
boolean noneMatch = stream.noneMatch(s -> s.startsWith("z"));
findFirst() : 첫 번째 요소를 반환한다.
Optional<String> firstElement = stream.findFirst();
findAny() : 아무 요소나 하나 반환한다.
Optional<String> anyElement = stream.findAny();
종합 활용 예시
List<String> words = Arrays.asList("apple", "banana", "avocado", "blueberry", "kiwi");
List<String> result = words.stream()
.filter(word -> word.startsWith("a")) // 'a'로 시작하는 단어 필터
.map(String::toUpperCase) // 대문자로 변환
.sorted() // 정렬
.collect(Collectors.toList()); // 리스트로 수집
System.out.println(result); // [APPLE, AVOCADO]