[Java] 64. 스트림 API2 - 기능

2025. 7. 4. 23:54·Java/Modern Java(8~)

 

스트림 API2 - 기능

#Java/adv3


스트림 생성

  • 스트림이 제공하는 다양한 스트림 생성, 중간 연산, 최종 연산을 자세히 알아보자.
생성 방법 코드 예시 특징
컬렉션 list.stream() List, Set 등 컬렉션에서 스트림 생성
배열 Arrays.stream(arr) 배열에서 스트림 생성
Stream.of(...) Stream.of("a", "b", "c") 직접 요소를 입력해 스트림 생성
무한 스트림(iterate) Stream.iterate(0, n -> n + 2) 무한 스트림 생성(초깃값 + 함수)
무한 스트림(generate) Stream.generate(Math::random) 무한 스트림 생성 (Supplier 사용)
  • 컬렉션, 배열, Stream.of 은 기본적으로 유한한 데이터 소스로부터 스트림을 생성한다.
  • iterate, generate는 별도의 종료 조건이 없으면 무한히 데이터를 만들어내는 스트림을 생성한다.
    • 따라서 필요한 만큼만(limit) 사용해야 한다. 그렇지 않으면 무한 루프처럼 계속 스트림을 뽑아내므로 주의해야 한다.
    • infiniteStream.limit(3).forEach(System.out::println);
  • 스트림은 일반적으로 한 번 사용하면 재사용할 수 없다(소진되면 끝). 따라서, stream()으로 얻은 스트림을 여러 번 순회하려면, 다시 스트림을 생성해야 한다.

중간 연산

스트림 파이프라인에서 데이터를 변환, 필터링, 정렬 등을 하는 단계이다.

  • 여러 중간 연산을 연결하여 원하는 형태로 데이터를 가공할 수 있다.
  • 결과가 즉시 생성되지 않고, 최종 연산이 호출될 때 한꺼번에 처리된다는 특징이 있다(지연 연산).
연산 설명 예시
filter 조건에 맞는 요소만 남김 stream.filter(n -> n > 5)
map 요소를 다른 형태로 변환 stream.map(n -> n * 2)
flatMap 중첩 구조 스트림을 일차원으로 평탄화 stream.flatMap(list -> list.stream())
distinct 중복 요소 제거 stream.distinct()
sorted 요소 정렬 stream.sorted() / stream.sorted(Comparator.reverseOrder())
peek 중간 처리 (로그, 디버깅, (중간에 뭔가 찍기))
peek는 데이터를 변경하지 않는다.
stream.peek(System.out::println)
limit 앞에서 N개의 요소만 추출 stream.limit(5)
skip 앞에서 N개의 요소를 건너뛰고 이후 요소만 추출 stream.skip(5)
takeWhile 조건을 만족하는 동안 요소 추출 (Java 9+)
정렬된 데이터에 효과적.
stream.takeWhile(n -> n < 5)
dropWhile 조건을 만족하는 동안 요소를 버리고 이후 요소 추출 (Java 9+) stream.dropWhile(n -> n < 5)

FlatMap

스트림 중간 연산 중 하나

  • map 은 각 요소를 하나의 값으로 변환하지만, flatMap 은 각 요소를 스트림(또는 여러 요소)으로 변환한 뒤, 그 결과를 하나의 스트림으로 평탄화(flatten)해준다.

리스트 안의 리스트 중첩 구조인 경우

[
	[1, 2], 
	[3, 4], 
	[5, 6]
]

flatMap 을 사용하면 이 데이터를 다음과 같이 쉽게 평탄화(flatten)할 수 있다.
[1, 2, 3, 4, 5, 6]


// map
List<Stream<Integer>> mapResult = outerList.stream()
        .map(list -> list.stream())
        .toList();
System.out.println("mapResult = " + mapResult);

// flatMap
List<Integer> flatMapResult = outerList.stream()
        .flatMap(list -> list.stream())
        .toList();
System.out.println("flatMapResult = " + flatMapResult);
  • map의 경우 먼저 List<List<Integer>>를 Stream<List<Integer>>로 만든 뒤, 스트림의 각 원소인 List<Integer>를 stream()으로 변환한 다음, 다시 스트림을 List로 변환해 최종적으로 List<Stream<Integer>>로 변환했다.
    • map 을 쓰면 이중 구조가 그대로 유지된다.
  • flatMap 을 쓰면 내부의 Stream 들을 하나로 합쳐 List<Integer>를 얻을 수 있다.

  • flatMap(list -> list.stream())
    • Stream<List<Integer>> Stream<Integer>
    • flatMap() 을 호출하면 list -> list.stream() 이 호출되면서 내부에 있는 3개의 List<Integer> 를 Stream<Integer> 로 변환한다.
    • flatMap() 은 Stream<Integer> 내부의 값을 꺼내서 외부 Stream 에 포함한다. 여기서는 1, 2, 3, 4, 5, 6의 값을 꺼낸다.
    • 이렇게 꺼낸 1, 2, 3, 4, 5, 6 값 각각이 외부 Stream 에 포함된다. 따라서 Stream<Integer> 가 된다.

정리

  • flatMap 은 중첩 구조(컬렉션 안의 컬렉션, 배열 안의 배열 등)를 일차원으로 펼치는 데 사용된다.
  • 예를 들어, 문자열 리스트들이 들어있는 리스트를 평탄화하면, 하나의 연속된 문자열 리스트로 만들 수 있다.

Optional 간단 설명

자바 Optional 클래스

package java.util;

public final class Optional<T> {
    private final T value;
    // ...
    public boolean isPresent() {
        return value != null;
    }

    public T get() {
        if (value == null) {
            throw new NoSuchElementException("No value present");
        }
        return value;
    }
    // ...
}
  • Optional 은 내부에 하나의 값(value)을 가진다.
  • isPresent() 를 통해 그 값(value)이 있는지 없는지 확인할 수 있다.
  • get() 을 통해 내부의 값을 꺼낼 수 있다. 만약 값이 없다면 예외가 발생한다.
  • Optional 은 이름 그대로 필수가 아니라 옵션이라는 뜻이다.
    • 이 말은 옵셔널 내부에 값(value)이 있을 수도 있고 없을 수도 있다는 뜻이다.

Optional<Integer> optional1 = Optional.of(10);
if (optional1.isPresent()) { // 값이 있는지 확인할 수 있는 안전한 메서드 제공
    Integer i = optional1.get(); // Optional 안에 있는 값을 획득
    System.out.println("i = " + i);
}

Optional<Object> optional2 = Optional.ofNullable(null);
if (optional2.isPresent()) {
    Object o = optional2.get();
    System.out.println("o = " + o);
}
// 값이 없는 Optional에서 get()을 호출하면 NoSuchElementException이 발생한다.
Object o2 = optional2.get();
  • Optional 은 내부에 값을 담아두고, 그 값이 null 인지 아닌지를 체크할 수 있는 isPresent() 와 같은 안전한 체크 메서드를 제공한다. 따라서 안전한 체크 메서드를 통해 체크하고 난 다음에 값이 있을 때만 get() 으로 값을 꺼내는 방식으로 사용할 수 있다.
  • Optional 은 null 값으로 인한 오류(NullPointerException)를 방지하고, 코드에서 "값이 없을 수도 있다"는 상황을 명시적으로 표현하기 위해 사용된다. 간단히 말해, null 을 직접 다루는 대신 Optional 을 사용하면 값의 유무를 안전하게 처리할 수 있어 코드가 더 명확하고 안정적으로 작성할 수 있다.

최종 연산

  • 스트림 최종 연산은 스트림 파이프라인의 끝에 호출되어 실제 연산을 수행하고 결과를 만들어낸다. 최종 연산이 실행된 후에 스트림은 소모되어 더 이상 사용할 수 없다.
연산 설명 예시
collect Collector를 사용하여 결과 수집 (다양한 형태로 변환 가능) stream.collect(Collectors.toList())
toList (Java16+) 스트림을 불변 리스트로 수집 stream.toList()
toArray 스트림을 배열로 변환 stream.toArray(Integer[]::new)
forEach 각 요소에 대해 동작 수행 (반환값 없음) stream.forEach(System.out::println)
count 요소 개수 반환 long count = stream.count();
reduce 누적 함수를 사용해 모든 요소를 단일 결과로 합침. (초기값을 지정해줄 수 있음. 초깃값이 없으면 Optional로 반환) int sum = stream.reduce(0, Integer::sum);
min / max 최솟값, 최댓값을 Optional로 반환 stream.min(Integer::compareTo), stream.max(Integer::compareTo)
findFirst 조건에 맞는 첫 번째 요소 (Optional 반환) stream.findFirst()
findAny 조건에 맞는 아무 요소나 (Optional 반환)
(병렬 스트림에서 효율적으로 동작할 수 있다)
stream.findAny()
anyMatch 하나라도 조건을 만족하는지 (boolean) stream.anyMatch(n -> n > 5)
allMatch 모두 조건을 만족하는지 (boolean) stream.allMatch(n -> n > 0)
noneMatch 하나도 조건을 만족하지 않는지 (boolean) stream.noneMatch(n -> n < 0)

정리

  • 최종 연산이 호출되면, 그 동안 정의된 모든 중간 연산이 한 번에 적용되어 결과를 만든다.
  • 최종 연산을 한 번 수행하면, 스트림은 재사용할 수 없다.
  • reduce 를 사용할 때 초깃값을 지정하면, 스트림이 비어 있어도 초깃값이 결과가 된다. 초깃값이 없으면 Optional 을 반환한다.
    • 초깃값이 없는데, 스트림이 비어 있을 경우 빈 Optional (Optional.empty())을 반환한다.
  • findFirst(), findAny() 도 결과가 없을 수 있으므로 Optional을 통해 값 유무를 확인해야 한다.

기본형 특화 스트림:

스트림 API에는 기본형(primitive) 특화 스트림이 존재한다.

  • 자바에서는 IntStream, LongStream, DoubleStream 세 가지 형태를 제공하여 기본 자료형(int, long, double)에 특화된 기능을 사용할 수 있게 한다.
    • 예를 들어, IntStream 은 합계 계산, 평균, 최솟값, 최댓값 등 정수와 관련된 연산을 좀 더 편리하게 제공하고, 오토박싱/언박싱 비용을 줄여 성능도 향상시킬 수 있다.
스트림 타입 대상 원시 타입 생성 예시
IntStream int IntStream.of(1, 2, 3), IntStream.range(1, 10), mapToInt(...)
LongStream long LongStream.of(10L, 20L), LongStream.range(1, 10), mapToLong(...)
DoubleStream double DoubleStream.of(3.14, 2.78), DoubleStream.generate(Math::random), mapToDouble(...)

기본형 특화 스트림의 숫자 범위 생성 기능

  • range(int startInclusive, int endExclusive) : 시작값 이상, 끝값 미만
    • IntStream.range(1, 5) → [1, 2, 3, 4]
  • rangeClosed(int startInclusive, int endInclusive) : 시작값 이상, 끝값 포함
    • IntStream.rangeClosed(1, 5) → [1, 2, 3, 4, 5]

기본형 특화 스트림: 주요 기능 및 메서드

  • 합계, 평균 등 자주 사용하는 연산을 편리한 메서드로 제공한다. 또한, 타입 변환과 박싱/언박싱 메서드도 제공하여 다른 스트림과 연계해 작업하기 수월하다.
메서드 / 기능 설명 예시
sum() 모든 요소의 합계를 구한다. int total = IntStream.of(1, 2, 3).sum();
average() 모든 요소의 평균을 구한다. OptionalDouble을 반환. double avg = IntStream.range(1, 5).average().getAsDouble();
summaryStatistics() 최솟값, 최댓값, 합계, 개수, 평균 등이 담긴 IntSummaryStatistics (또는 Long/Double) 객체 반환 IntSummaryStatistics stats = IntStream.range(1,5).summaryStatistics();
asLongStream(), asDoubleStream() 타입 변환 LongStream longStream = IntStream.range(1, 5).asLongStream();
mapToInt(),
mapToLong(), mapToDouble()
매핑+ 타입 변환 : IntStream → LongStream, DoubleStream ...

Stream<Integer> IntStream
LongStream ls = IntStream.of(1,2).mapToLong(i -> i * 10L);

IntStream instream = Stream.of(1, 2, 3, 4, 5).mapToInt(i  i)
mapToObj() 객체 스트림으로 변환 : 기본형 → 참조형 Stream<String> s = IntStream.range(1,5).mapToObj(i -> "No: "+i);
boxed() 기본형 특화 스트림을 박싱(Wrapper)된 객체 스트림으로 변환 Stream<Integer> si = IntStream.range(1,5).boxed();
sum(), min(), max(), count() 합계, 최솟값, 최댓값, 개수를 반환 (타입별로 int/long/double 반환) long cnt = LongStream.of(1,2,3).count();
  • 기본형 특화 스트림(IntStream, LongStream, DoubleStream) 을 이용하면 숫자 계산(합계, 평균, 최대·최소 등)을 간편하게 처리하고, 박싱/언박싱 오버헤드를 줄여 성능상의 이점도 얻을 수 있다.
  • range(), rangeClosed() 같은 메서드를 사용하면 범위를 쉽게 다룰 수 있어 반복문 대신에 자주 쓰인다.
  • mapToXxx, boxed() 등의 메서드를 잘 활용하면 객체 스트림과 기본형 특화 스트림을 자유롭게 오가며 다양한 작업을 할 수 있다.
  • summaryStatistics() 를 이용하면 합계, 평균, 최솟값, 최댓값 등 통계 정보를 한 번에 구할 수 있어 편리하다.
  • 기본형 특화 스트림을 잘 이용하면 가독성, 성능 모두 잡을 수 있다. 따라서 숫자 중심의 연산에서는 적극 활용하는 것을 고려하자. (기본형 특화 스트림은 사용할 일이 가끔 있다~)

성능 - 전통적인 for문 vs 스트림 vs 기본형 특화 스트림

  • 전통적인 for문이 제일 빠르다.
  • 전통적인 for문이 스트림에 비해선 1.5~2배 더 빠르다.
    • 박싱/언박싱 오버헤드가 존재한다.
  • 기본형 특화 스트림은 전통적인 for문에 가까운 성능을 보인다. (거의 비슷하거나 for문이 10%~30% 빠름)
    • 박싱/언방식 오버헤드를 피할 수 있고, 내부적으로 최적화된 연산 수행이 가능하다.
  • 최적의 선택은 구현 가독성, 유지보수성, 극단적인 성능 필요 유무 등을 고려해야 한다.

Ref) 김영한의 실전 자바 - 고급 3편, 람다, 스트림, 함수형 프로그래밍 강의 | 김영한 - 인프런

'Java > Modern Java(8~)' 카테고리의 다른 글

[Java] 66. Optional  (1) 2025.07.04
[Java] 65. 스트림 API3 - 컬렉터  (0) 2025.07.04
[Java] 63. 스트림 API1 - 기본  (0) 2025.07.04
[Java] 62. 메서드 참조  (0) 2025.07.04
[Java] 61. 람다 vs 익명 클래스  (0) 2025.07.04
'Java/Modern Java(8~)' 카테고리의 다른 글
  • [Java] 66. Optional
  • [Java] 65. 스트림 API3 - 컬렉터
  • [Java] 63. 스트림 API1 - 기본
  • [Java] 62. 메서드 참조
lumana
lumana
배움을 나누는 공간 https://github.com/bebeis
  • lumana
    Brute force Study
    lumana
  • 전체
    오늘
    어제
    • 분류 전체보기 (457)
      • Software Development (27)
        • Performance (0)
        • TroubleShooting (1)
        • Refactoring (0)
        • Test (8)
        • Code Style, Convetion (0)
        • DDD (0)
        • Software Engineering (18)
      • Java (71)
        • Basic (5)
        • Core (21)
        • Collection (7)
        • 멀티스레드&동시성 (13)
        • IO, Network (8)
        • Reflection, Annotation (3)
        • Modern Java(8~) (12)
        • JVM (2)
      • Spring (53)
        • Framework (12)
        • MVC (23)
        • Transaction (3)
        • AOP (11)
        • Boot (0)
        • AI (0)
      • DB Access (1)
        • Jdbc (1)
        • JdbcTemplate (0)
        • JPA (14)
        • Spring Data JPA (0)
        • QueryDSL (0)
      • Computer Science (130)
        • Data Structure (27)
        • OS (14)
        • Database (10)
        • Network (21)
        • 컴퓨터구조 (6)
        • 시스템 프로그래밍 (23)
        • Algorithm (29)
      • HTTP (8)
      • Infra (1)
        • Docker (1)
      • 프로그래밍언어론 (15)
      • Programming Language(Sub) (77)
        • Kotlin (1)
        • Python (25)
        • C++ (51)
        • JavaScript (0)
      • FE (11)
        • HTML (1)
        • CSS (9)
        • React (0)
        • Application (1)
      • Unix_Linux (0)
        • Common (0)
      • PS (13)
        • BOJ (7)
        • Tip (3)
        • 프로그래머스 (0)
        • CodeForce (0)
      • Book Review (4)
        • Clean Code (4)
      • Math (3)
        • Linear Algebra (3)
      • AI (7)
        • DL (0)
        • ML (0)
        • DA (0)
        • Concepts (7)
      • 프리코스 (4)
      • Project Review (6)
      • LegacyPosts (11)
      • 모니터 (0)
      • Diary (0)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

  • 인기 글

  • 태그

  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.4
lumana
[Java] 64. 스트림 API2 - 기능
상단으로

티스토리툴바