-
JEP 485: Stream GatherersJava/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 461JDK 22Stream Gatherers (Preview)Stream API에 사용자 정의 중간 연산 기능 도입 https://openjdk.org/jeps/461JEP 473JDK 23Stream Gatherers (Second Preview)피드백 반영 및 API 조정 https://openjdk.org/jeps/473JEP 485JDK 24Stream Gatherers최종. API 변경 없이 기능 확정 https://openjdk.org/jeps/485'Java > Java25' 카테고리의 다른 글
JEP 510: Key Derivation Function API (1) 2025.12.29 JEP 497: Quantum-Resistant Module-Lattice-Based Digital Signature Algorithm (ML-DSA) (0) 2025.12.28 JEP 496: Quantum-Resistant Module-Lattice-Based Key Encapsulation Mechanism (1) 2025.12.28 JEP 511: Module Import Declarations (0) 2025.12.27 Java 25 Performance Improvement (0) 2025.12.21