본문 바로가기
language/java

Stream 개선사항(Java 9~21)

by 죄니안죄니 2026. 6. 17.
반응형

이번 글은 단순히

"Java 9에서 이런 메서드가 추가됐습니다."

를 나열하는 글이 아니다.

실무에서

 
stream()

.filter()

.map()

.collect(...)
 

만 사용하는 개발자가

Java 21 수준의 Stream을 사용할 수 있도록 흐름 중심으로 설명한다.


Stream 개선사항(Java 9~21)

Java 9 이후 Stream API는 "더 적은 코드로 더 안전하고 더 읽기 쉬운 데이터 처리"를 목표로 계속 발전해 왔다.


Java 8의 Stream 한계

Java 8에서는

 
List<Integer> numbers =
        List.of(1, 2, 3, 4, 5, 6);
 

앞에서 조건이 깨질 때까지 가져오고 싶다면

 
List<Integer> result =

numbers.stream()

       .filter(n -> n < 4)

       .collect(Collectors.toList());
 

처럼 작성했다.


하지만 이것은

1

2

3

4

5

6
 

모든 데이터를 끝까지 검사한다.


사실 우리가 원하는 것은

1

2

3

↓

4 발견

↓

즉시 종료
 

이다.


takeWhile() (Java 9)

조건이 참인 동안만 가져오고 처음 거짓이 나오면 즉시 종료한다.


 
List<Integer> result =

numbers.stream()

       .takeWhile(n -> n < 4)

       .toList();
 

실행

1 → true

2 → true

3 → true

4 → false

↓

종료
 

결과

[1, 2, 3]
 

filter()와 차이

filter

 
numbers.stream()

       .filter(n -> n < 4)
 

실행

1 확인

2 확인

3 확인

4 확인

5 확인

6 확인
 

끝까지 검사한다.


takeWhile

 
numbers.stream()

       .takeWhile(n -> n < 4)
 

실행

1 확인

2 확인

3 확인

4 확인

↓

즉시 종료
 

dropWhile() (Java 9)

반대 기능이다.


 
List<Integer> result =

numbers.stream()

       .dropWhile(n -> n < 4)

       .toList();
 

실행

1 제거

2 제거

3 제거

4 발견

↓

이후 모두 유지
 

결과

[4, 5, 6]
 

ofNullable() (Java 9)

기존

 
String name = getName();

Stream<String> stream =
        name == null

        ? Stream.empty()

        : Stream.of(name);
 

Java 9

 
Stream<String> stream =
        Stream.ofNullable(name);
 

동작

null

↓

빈 Stream

------------

Kim

↓

Stream("Kim")
 

실무에서 Optional과 함께 많이 사용된다.


iterate() 개선 (Java 9)

Java 8

 
Stream.iterate(

    1,

    n -> n + 1

)
.limit(5);
 

limit을 반드시 사용해야 했다.


Java 9

 
Stream.iterate(

    1,

    n -> n <= 5,

    n -> n + 1

);
 

실행

1

2

3

4

5

6 → false

↓

종료
 

limit 없이도 종료 조건을 지정할 수 있다.


toList() (Java 16)

기존

 
List<String> result =

users.stream()

     .map(User::getName)

     .collect(Collectors.toList());
 

Java 16

 
List<String> result =

users.stream()

     .map(User::getName)

     .toList();
 

훨씬 간결하다.


그런데 정말 같은 것일까?

많은 사람들이

toList()

=

Collectors.toList()
 

라고 생각한다.


사실은 다르다.


Collectors.toList()

 
List<String> list =

stream.collect(Collectors.toList());

list.add("Kim");
 

가능하다.

보통 Mutable List(ArrayList)가 생성된다.


Stream.toList()

 
List<String> list =

stream.toList();

list.add("Kim");
 

결과

UnsupportedOperationException
 

Immutable List를 반환한다.


실무에서 중요한 이유

 
List<UserDto> users =

repository.findAll()

          .stream()

          .map(UserDto::from)

          .toList();
 

이 결과는

조회 결과

↓

수정하지 않음

↓

Immutable

↓

안전
 

이라는 의도를 표현할 수 있다.


mapMulti() (Java 16)

조금 어려운 기능이다.


기존

 
users.stream()

     .flatMap(user ->

         user.getOrders()

             .stream()

     );
 

flatMap은

매번

List

↓

Stream 생성

↓

합치기
 

를 수행한다.


mapMulti

 
users.stream()

     .<Order>mapMulti(

         (user, consumer) -> {

             for (Order order : user.getOrders()) {

                 consumer.accept(order);

             }

         }

     );
 

중간 Stream을 생성하지 않는다.


즉,

flatMap

↓

Stream 생성

↓

객체 증가

----------------

mapMulti

↓

consumer에 직접 전달

↓

객체 감소
 

대량 데이터에서는 더 효율적이다.


Optional.stream() (Java 9)

Optional도 Stream으로 바꿀 수 있다.


기존

 
if (optional.isPresent()) {

    stream.add(optional.get());

}
 

Java 9

 
optional.stream();
 

동작

Optional.empty()

↓

빈 Stream

------------

Optional(User)

↓

Stream(User)
 

실무 예제

여러 Optional을 처리할 때

 
users.stream()

     .map(User::getAddress)

     .flatMap(Optional::stream)

     .toList();
 

굉장히 많이 사용하는 패턴이다.


Stream.iterate + takeWhile

조합도 가능하다.

 
Stream.iterate(

        1,

        n -> n + 1

)

.takeWhile(n -> n <= 5)

.toList();
 

실행

1

2

3

4

5

6

↓

takeWhile 종료
 

Java 21에서 Stream은 달라졌을까?

새로운 API가 크게 추가된 것은 아니다.


대신

Virtual Thread

Structured Concurrency

Pattern Matching

Record
 

등과 함께 사용할 수 있도록

전체적인 생태계가 발전했다.


실무에서 가장 많이 사용하는 최신 API

매우 자주 사용

 
stream.toList();
 
 
Stream.ofNullable(value);
 
 
takeWhile(...)
 

가끔 사용

 
dropWhile(...)
 
 
Optional.stream()
 

특정 성능 최적화

 
mapMulti()
 

Java 8 코드와 비교

Java 8

 
users.stream()

     .collect(Collectors.toList());
 

Java 21

 
users.stream()

     .toList();
 

Java 8

 
value == null

?

Stream.empty()

:

Stream.of(value);
 

Java 21

 
Stream.ofNullable(value);
 

Java 8

 
filter(...)
 

Java 21

 
takeWhile(...)

dropWhile(...)
 

면접 포인트

Java 9 이후 Stream에서 가장 많이 사용하는 기능은?

  • takeWhile()
  • dropWhile()
  • Stream.ofNullable()
  • Optional.stream()

Stream.toList()와 Collectors.toList() 차이는?

Stream.toList()Collectors.toList()
Immutable List 일반적으로 Mutable List
Java 16 Java 8

takeWhile()와 filter() 차이는?

  • filter() : 끝까지 검사한다.
  • takeWhile() : 조건이 처음 거짓이 되는 순간 즉시 종료한다.

mapMulti()는 왜 추가되었는가?

flatMap()이 중간 Stream 객체를 생성하는 비용을 줄이기 위해, Consumer에 직접 데이터를 전달하는 방식으로 성능을 개선하기 위해 추가되었다.


최신 Java 문법 챕터 마지막 글

다음은 Virtual Thread(Java 21) 이다.

이 글에서는

  • 왜 Thread가 비싼 자원인지
  • Platform Thread와 Virtual Thread의 차이
  • JVM 내부에서 어떻게 동작하는지
  • Spring Boot 3.2에서 Virtual Thread를 사용하는 방법
  • @Async, ExecutorService, ThreadPool과의 관계
  • 언제 사용해야 하고 언제 사용하면 안 되는지

까지 최신 Java에서 가장 중요한 주제를 깊이 있게 설명하겠다.

반응형

'language > java' 카테고리의 다른 글

Reflection과 Spring DI  (0) 2026.06.17
Virtual Thread(Java 21)  (0) 2026.06.17
Optional 심화  (0) 2026.06.17
sealed class(Java 17)  (0) 2026.06.17
record(Java 16)  (0) 2026.06.17

댓글