ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • JEP 485: Stream Gatherers
    Java/Java25 2025. 12. 29. 14:11

    1. Gatherer란?

    Gatherer는 스트림의 요소를 “수집(gather)”하여, 상태(state)를 유지하면서 0개 이상 결과를 방출하는 중간 연산 구성요소 입니다.

    즉,

    • map보다 강력
    • collect보다 유연
    • 중간 연산이지만 상태를 가질 수 있습니다

     

    2. Stream Gatherers란 무엇인가?

    2-1. 기존 Stream API의 한계

    기존 Stream API는 다음 두 가지 연산만 제공했습니다.

    • 중간 연산 (intermediate)
      map, filter, flatMap 등 → 1:1 변환
    • 종단 연산 (terminal)
      forEach, reduce, collect 등 → Stream 종료

    하지만 다음과 같은 패턴은 표현하기 어려웠습니다.

    • N개의 요소를 묶어서 처리 (windowing, batching)
    • 이전 요소의 상태를 기억하며 다음 요소 처리
    • 1개의 입력 → 0개 또는 여러 개 출력
    • flatMap + mutable state 조합 없이 상태 기반 변환

    👉 이 공백을 메우기 위해 등장한 것이 Stream Gatherers입니다.

     

     

    Stream Pipeline 에서 활용

    source → intermediate(map/filter) → gather(gatherer) → intermediate → terminal

     

     

    3. Stream Gathers 를 image 와 diagram 으로 표현

    https://medium.com/@umeshcapg/the-gathering-revolution-jdk-24s-stream-gatherers-explained-e8727dee6877
    generated by Gemni

     

    연산자 사용 예

    Gatherers.windowFixed() example

    Input:   [1]---[2]---[3]---[4]---[5]---[6]---[7]---[8]---[9]--->
              |     |     |     |     |     |     |     |     |
              v     v     v     v     v     v     v     v     v
    Gatherer: [--- windowFixed(3) ---]
              |     |     |     |     |     |     |     |     |
              v     v     v     v     v     v     v     v     v
    Output:  [1,2,3]------[4,5,6]------[7,8,9]------------------>

    // windowFixed(3)
    List<List<Integer>> windows = Stream.of(1, 2, 3, 4, 5, 6, 7, 8)
      .gather(Gatherers.windowFixed(3))
      .toList();
    
    // 결과: [[1, 2, 3], [4, 5, 6], [7, 8]]

     

    Gatherers.windowSliding() example

    Input:   [1]---[2]---[3]---[4]---[5]---[6]---[7]---[8]---[9]--->
              |     |     |     |     |     |     |     |     |
              v     v     v     v     v     v     v     v     v
    Gatherer: [--- windowSliding(3) ---]
              |     |     |     |     |     |     |     |     |
              v     v     v     v     v     v     v     v     v
    Output:  [1,2,3]--[2,3,4]--[3,4,5]--[4,5,6]--[5,6,7]--[6,7,8]...

     

    연속된 숫자가 overlap 되는 차이가 있음  windowSliding(3) vs windowFixed(3)

    // windowSliding(3)
    List<List<Integer>> slidingWindows = Stream.of(1, 2, 3, 4, 5)
      .gather(Gatherers.windowSliding(3))
      .toList();
    
    // 결과: [[1, 2, 3], [2, 3, 4], [3, 4, 5]]

     

    Gatherers.scan(Integer::sum) example

    Input:   [1]---[2]---[3]---[4]---[5]---[6]--->
              |     |     |     |     |     |
              v     v     v     v     v     v
    Gatherer: [--- scan(Integer::sum) ---]
              |     |     |     |     |     |
              v     v     v     v     v     v
    Output:  [1]---[3]---[6]---[10]--[15]--[21]-->

     

    1st : 1

    2nd : 1+2 = 3

    3rd : 1+2+3 = 6

    4th : 1+2+3+4 = 10

    ...

      List<Integer> cumulativeSum = Stream.of(1, 2, 3, 4, 5, 6)
          .gather(Gatherers.scan(() -> 0, Integer::sum))
          .toList();
    
      System.out.println("출력: " + cumulativeSum);
      // 결과: [1, 3, 6, 10, 15, 21]

     

    Gatherers.doubler example

    Input:   [1]---[2]---[3]--->
              |     |     |
              v     v     v
    Gatherer: [--- doubler ---]
              |     |     |
              v     v     v
    Output:  [2]---[4]---[6]-->

     

     

    // Gatherer doubler 정의
    public class doubler {
      public static final Gatherer<Integer, Void, Integer> doubler =
          ofSequential(
              (state, element, downstream) -> downstream.push(element * 2)
          ); // state 는 불필요하여 사용하지 않음
    }
    
    
    // doubler 사용
    List<Integer> doublerList =
          Stream.of(1, 2, 3)
              .gather(doubler)
              .toList();
    
    System.out.println("doubler : 출력: " + doublerList);
    // doubler : 출력: [2, 4, 6]

     

     

    Gatherer  distinctBy 예

     

    Input Stream:  [foo]---[bar]---[baz]---[quux]--->
                    |       |       |       |
                    v       v       v       v
    Gatherer:      [--- distinctBy(String::length) ---]
                    |       |       |       |
                    v       v       v       v
    Output Stream: [foo]-----------------[quux]------->

     

    코드 예1, distinct 정의

    public class distinctby {
      static <T, U> Gatherer<T, ?, T> distinctBy(Function<? super T, ? extends U> keyExtractor) {
        Objects.requireNonNull(keyExtractor, "keyExtractor must not be null");
    
        return Gatherer.ofSequential(
            () -> new HashSet<U>(),
            Gatherer.Integrator.ofGreedy((seen, element, downstream) -> {
              U key = keyExtractor.apply(element); // 키 추출
              if (seen.add(key)) { // 처음 보는 키인가?
                return downstream.push(element); // 처음 이면 출력
              }
              return true;
            })
        );
      }
    }

     

    사용 예

    // 사용 예 1
    var distinctList = Stream.of("foo", "bar", "baz", "quux")
          .gather(distinctBy(String::length))
          .toList();
    
    System.out.println("distinctBy : 출력: " + distinctList);
    // distinctBy : 출력: [foo, quux]

     

    사용 예2,  나이별 1명 씩 distinct

    // 사용 예2 : 나이별 1명 씩 distinct
    record User(String id, String name, int age) {}
    
    List<User> users = List.of(
        new User("1", "Alice", 25),
        new User("2", "Bob", 30),
        new User("3", "Charlie", 25),
        new User("4", "Diana", 35)
    );
    
    List<User> uniqueByAge = users.stream()
        .gather(distinctBy(User::age))
        .toList();
    
    System.out.println("uniqueByAge : 출력: " + uniqueByAge);
    // uniqueByAge : 출력: [User[id=1, name=Alice, age=25], 
    //                     User[id=2, name=Bob, age=30], 
    //                     User[id=4, name=Diana, age=35]]

     

     

    위의 예제를 확인할 수 있는 github repository 입니다.

    github : https://github.com/gwagdalf/gather-example

     

    GitHub - gwagdalf/gather-example: Code example of Java 24’s Gather Stream API

    Code example of Java 24’s Gather Stream API. Contribute to gwagdalf/gather-example development by creating an account on GitHub.

    github.com

     

     

     

    Visualizing the Gatherer Process

    Input Stream: [A]---[B]---[C]---[D]---[E]--->
                    |     |     |     |     |
                    v     v     v     v     v
                 +-----------------------------+
                 |        Gatherer           |
                 |                           |
                 |  [State]                  |
                 |  |  |  |                  |
                 |  v  v  v                  |
                 |  [Integrator]             |
                 |  |  |  |                  |
                 |  v  v  v                  |
                 |  [Downstream]             |
                 +-----------------------------+
                    |     |     |     |     |
                    v     v     v     v     v
    Output Stream: [X]---[Y]---[Z]----------->

     

     

    제공되는 메소드들

    카테고리메소드

    Category Method
    Windowing windowFixed, windowSliding
    Folding fold, scan
    Filtering filtering, takeWhile, dropWhile
    Mapping mapConcurrent
    Control step 기반 sliding

     

     

    official docs : https://docs.oracle.com/en/java/javase/24/docs/api/java.base/java/util/stream/Gatherers.html

     

    Gatherers (Java SE 24 & JDK 24)

    Returns a Gatherer that performs an ordered, reduction-like, transformation for scenarios where no combiner-function can be implemented, or for reductions which are intrinsically order-dependent. Returns a Gatherer that gathers elements into windows -- enc

    docs.oracle.com

     

     

     

     

    발전 단계

    JEP 번호
    JDK 버전
    제목
    주요 변경 사항
    JEP 461
    JDK 22
    Stream Gatherers (Preview)
    Stream API에 사용자 정의 중간 연산 기능 도입 https://openjdk.org/jeps/461
    JEP 473
    JDK 23
    Stream Gatherers (Second Preview)
    피드백 반영 및 API 조정 https://openjdk.org/jeps/473
    JEP 485
    JDK 24
    Stream Gatherers
    최종. API 변경 없이 기능 확정 https://openjdk.org/jeps/485

     

    댓글

Designed by Tistory.