본문 바로가기
Programing

[Java] Stream 한방 정리 (👆JDK_1.8)

by 윾수 2025. 4. 29.

스트림(Stream) 개념 정리

1. 스트림이란

  • JDK 1.8 에서 등장한 개념
  • 데이터를 담고 있는 저장소가 아님
  • 데이터(컬렉션, 배열 등)를 흐르듯이 처리하는 방법
  • 함수형 프로그래밍 스타일로 데이터 변환/필터링/집계할 수 있음.
  • 데이터를 하나씩 꺼내서 가공하고, 필요하면 결과만 뽑아내는 흐름을 만드는것
  • 코드가 짧고 읽기 쉬워진다

👇 요약

배열이나 리스트를 for문 쓰지 않고 가볍게 가공하는 방식

2. 스트림 특징

특징 설명
데이터 소스 변경 X 스트림은 원본 데이터를 변경하지 않음
1회용 스트림은 한 번만 탐색하고 끝남(재사용 불가)
지연(lazy)연산 중간 연산은 실행 안 하고 모아뒀다가, 최종 연산 할 때 한번에 처리
자동 최적화 내부적으로 최적화(병렬화, 합성작업) 해서 성능 좋게 처리함
병렬 처리 가능 .paralleStream() 쓰면 멀티 코어로 동시에 처리가능

3. 스트림 구성 요소

단계 설명
스트림 생성 데이터를 스트림으로 만들어줌.
중간 연산 스트림을 변형하거나 거르는 과정, 여러개의 파이프 라인을 붙일 수 있다.
스트림을 리턴 할 뿐 실제 연산 결과는 바로 만들지 않는다.
데이터 변형, 필터링, 정렬 등 연결된 파이프라인에 대한 준비만 하며 최종 연산시 실행된다.
filter() -> map() -> sorted() 순으로 처리하면 효율적임
limit(), skip()도 결과 도출에 영향을 주지 않는 선에서 가능한 앞에서 처리
연산 순서를 잘 짜면 스트림이 lazy하게 동작하면서 비용을 줄일 수 있다.
최종 연산 스트림을 소비해서 결과를 만듦.
최종 연산이 호출되면 스트림은 더 이상 사용할 수 없다.
/**
* Stream<T>객체를 선언할 때도 
* 중간연산은 몇개가 껴있든 상관없지만
* 최종연산을 사용하면 컴파일 요류가 나는것을 확인할 수 있다.
*/
Stream<String> s = Stream.of("A", "B", "C")
	.filter()
	.map(String::toUpperCase);
    //.forEach(System.out::println);

4. 스트림 처리 흐름

[데이터 소스]
    ↓
[스트림 생성]
    ↓
[중간 연산 (필터, 변환, 정렬 등)] (파이프라인 구축)
    ↓
[최종 연산 (결과 뽑기, 집계 등)] (일괄 실행)
List<String> list = Arrays.asList("a", "bb", "ccc");

list.stream() 
    .filter(s -> s.length() >= 2) 
    .map(String::toUpperCase)
    .sorted()
    .forEach(System.out::println);
//기대 출력값 : "BB", "CCC"

System.out.println(list);
//기대 출력값 : "a", "bb", "ccc"

5. 메서드 총정리

생성 메서드 설명 예시
Stream.of(T...values) 여러 값을 스트림으로 만듦 Stream.of("A", "B", "C")
Arrays.stream(array) 배열을 스트림으로 변환 Arrays.stream(arr)
Collection.stream() 자바 Collection객체에서 스트림 생성 map.stream(), list.stream(), set.stream()
중간연산 메서드 설명 예시
filter(Predicate) 조건에 맞는 요소만 통과  
map(Function) 요소변환(ex.String -> length) Stream<Integer> i = strg.map(Integer::parseInt);
mapToInt(ToIntFunction) 요소를 int 타입 변환 mapToInt(User::getAge), mapToInt(Integer::parseInt)
mapToLong(ToLongFunction) 요소를 long 타입 변환 mapToLong(Long::parseLong)
mapToDouble(ToDoubleFunc) 요소를 Double 타입 변환 mapToDouble(Double::parseDouble)
flatMap(Function) 중첩된 스트림을 하나로 평탄화 flatMap(s -> Arrays.stream(s.split(" ")))
distinct() 중복제거 distinct()
sorted() 자연 정렬 sorted()
sorted(Comparator) Comparator기준으로 정렬 sorted(new Comparator ....)
peek(Consumer) 중간에서 요소를 훔쳐봄 (디버깅 용도) peek(s -> log.info("현재 값 = "+s))
limit(long n) 최대 n개까지만 자름 limit(5)
skip(long n) 앞에서 n개 건너뜀 skip(10)
최종연산 메서드 설명 예시
toArray() 배열로 변환 String[] arr = stream.toArray()
forEach(Consumer) 요소 하나씩 처리(리턴값 없음) forEach(System.out :: println)
forEachOrdered(Consumer) parallelStream 사용시 순서보장 forEachOrdered(System.out :: println)
collect(Collectors) 결과를 모음 (새 객체 리턴) java.Collection 객체(map, list, set)으로 리턴
toList() List<T> 로 리턴 collect(Collectors.toList())
toSet() Set<T> 로 리턴 collect(Collectors.toSet())
toMap() Map<T> 로 리턴 collect(Collectors.toMap(s->s, s->s.length))
joining 문자열 합치기 collect(Collectors.joining(", "))
groupingBy() 조건 결과별 그룹핑 분류 collect(Collectors.groupingBy(String::length))
partitioningBy 조건으로 true/false 그룹 분류 collect(Collectors.partitioningBy(s -> s.length > 4))
reduce(BinaryOperator) 요소를 하나로 합침
(합계, 문자 합치기)
reduce(0, (a,b) -> a+b) 
reduce("", (a,b) -> a + b)
count() 개수 세기 count()
min(Comparator) 최솟값 찾기 min(new Comparator ....)
max(Comparator) 최댓값 찾기 max(new Comparator ....)
anyMatch(Predicate) 하나라도 조건 만족하면 true anyMatch(s -> s.startsWith("A"))
allMatch(Predicate) 모두 만족하면 true allMatch(s -> s.startsWith("A"))
noneMatch(Predicate) 아무것도 만족 안하면 true noneMatch(s -> s.noneMatch("A"))
findFirst() 첫번째 요소 찾기 (Optional) Optional<String> first = list.stream().findFirst();
findAny() 아무거나 하나 찾기 (Optional) Optional first = list.stream(). findAny();
특수 스트림 메서드 설명 예시
** 특수 스트림(IntStream, LongStream, DoubleStream = primitive 타입) ** (중간스트림)
primitive stream은 성능이 좋지만 boxed()로 wrapper로 변경시 오토박싱 때문에 성능 저하가 발생 할 수 있다.
range(int start, int end) 범위스트림 생성 (이상, 미만) range(1,5) //1,2,3,4
rangeClosed(int start, int end) 범위스트림 생성 (이상, 이하) range(1,5) //1,2,3,4,5
sum() 합계 sum()
average() 평균 average()
min() / max() 최소 / 최대 min(), max()
boxed() primitive -> wrapper 변환 List<Integer> list = IntStream
    .rangeClosed(1, 3)
    .boxed()
    .collect(Collectors.toList());

6. 병렬 스트림(parallelStream) 사용법 및 주의사항

  • 내부적으로 ForkJoinPool.commonPool을 사용 하는 비동기/블로킹 방식
    commonPool()은 JVM기동시 core*1배수-1의 싱글톤 workerThreadPool로 정의되며 수정 불가
  • parallelStream의 요소 각자 독립적인 쓰레드에서 실행 되는 로직이다.
    첫번째 요소는 최종 연산까지 끝났는데 마지막 요소는 ForkJoinPool.commonPool 갯수제한으로
    첫관문도 넘지 못했을 수 있음.
  • 장점
    데이터가 많아서 병렬처리로 성능을 향상 하고싶다면 빠른 처리 가능
  • 단점
    내부 병렬처리용 쓰레드 풀의 갯수를 조절 할 수 없다.
    ⚠️ 내부 병렬 처리는 무조건 commonPool을 사용하기 때문⚠️
    이런 경우 CustomThreadPool + stream() 처리가 낫다
    순서가 중요한 연산(forEachOrdered, findFirst)에서는 예측 불가능한 결과
    stateful연산(limit, sorted)과 병렬 조합시 성능 저하 발생

7. Optional을 활용한 예외 처리

  • Optional<T> 타입으로 리턴하는 최종 스트림은 이점을 활용해서 예외 처리 해주면 좋다
findFirst() findAny() max(Comparator) min(Comparator)
reduce(BinaryOperator) collect(Collectors.maxBy()) collect(Collectors.minBy())  

8. 스트림과 컬렉션의 차이점 정리

비교 항목 Stream Collection
저장 vs 계산 계산 중심 데이터 저장
재사용 1회용 가능
지연 연산 있음 없음
멀티스레드 병렬처리 가능(parallelStream) 직접 처리 필요

'Programing' 카테고리의 다른 글

[Java] 함수형 인터페이스와 람다식  (0) 2025.05.09
[Java] Optional Class  (0) 2025.05.08
[Java] IO vs NIO  (1) 2025.04.30
메모리 덤프(dump) 분석 [ jps, jmap, jhat ]  (0) 2022.05.16
Scanner 클래스 사용법  (0) 2021.09.14