Skip to content

Commit a84483f

Browse files
committed
Add example for stream gatherers
1 parent b9facd7 commit a84483f

File tree

3 files changed

+340
-0
lines changed

3 files changed

+340
-0
lines changed

README.md

+4
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,10 @@ If an API that was introduced in Java 9+ was later updated, the update is listed
125125

126126
## Updated APIs
127127

128+
* ⓧ Stream gatherers: [custom gatherers](src/main/java/dev/nipafx/demo/java_next/api/gather/CustomGatherers.java)
129+
(videos [1](https://www.youtube.com/watch?v=epgJm2dZTSg),
130+
[2](https://www.youtube.com/watch?v=pNQ5OXMXDbY);
131+
[JEP 461](https://openjdk.org/jeps/461))
128132
* ⑯ (server) socket channels: [Unix domain socket support](src/main/java/dev/nipafx/demo/java16/api/unix_sockets)
129133
([article](https://nipafx.dev/java-unix-domain-sockets/),
130134
[JEP 380](https://openjdk.java.net/jeps/380))
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,242 @@
1+
package dev.nipafx.demo.java_next.api.gather;
2+
3+
import java.util.ArrayList;
4+
import java.util.Comparator;
5+
import java.util.List;
6+
import java.util.concurrent.atomic.AtomicInteger;
7+
import java.util.concurrent.atomic.AtomicReference;
8+
import java.util.function.BiConsumer;
9+
import java.util.function.Function;
10+
import java.util.function.Predicate;
11+
import java.util.function.Supplier;
12+
import java.util.stream.Stream;
13+
14+
/* --- UNTIL JEP 461 IS MERGED --- */
15+
import dev.nipafx.demo.java_next.api.gather.stream.Gatherer;
16+
import dev.nipafx.demo.java_next.api.gather.stream.Gatherer.Downstream;
17+
import dev.nipafx.demo.java_next.api.gather.stream.Gatherer.Integrator;
18+
19+
/* --- AFTER JEP 461 IS MERGED --- */
20+
//import java.util.stream.Gatherer;
21+
//import java.util.stream.Gatherer.Downstream;
22+
//import java.util.stream.Gatherer.Integrator;
23+
24+
import static java.lang.StringTemplate.STR;
25+
26+
/**
27+
* This demo is built for JEP 461, which is not yet merged. To allow experimentation, I rebuilt the essential parts:
28+
*
29+
* <ul>
30+
* <li>the interface hierarchy in {@link dev.nipafx.demo.java_next.api.gather.stream.Gatherer}</li>
31+
* <li>the iteration mechanism in {@code apply_manually} (without any guarantee of correctness</li>
32+
* </ul>
33+
*
34+
* The code should work on any recent Java version as is.
35+
*/
36+
public class CustomGatherers {
37+
38+
public static void main(String[] args) {
39+
var letters = List.of("A", "B", "D", "C", "B", "F", "E");
40+
Predicate<String> isEven = letter -> ((int) letter.charAt(0)) % 2 == 0;
41+
42+
/* --- UNTIL JEP 461 IS MERGED --- */
43+
var result = apply_manually(
44+
letters,
45+
doNothing());
46+
/* --- AFTER JEP 461 IS MERGED --- */
47+
// var result = apply_jep461(
48+
// letters,
49+
// doNothing());
50+
51+
System.out.println(STR."""
52+
53+
in: \{letters}
54+
out: \{result}
55+
""");
56+
}
57+
58+
/* --- UNTIL JEP 461 IS MERGED --- */
59+
private static <R> List<R> apply_manually(List<String> letters, Gatherer<String, ?, R> gatherer) {
60+
// the raw type is needed because the compiler won't let us pass a `?` to a `?`
61+
var rawGatherer = (Gatherer) gatherer;
62+
var result = new ArrayList<R>();
63+
Downstream<? super R> downstream = result::add;
64+
65+
Object state = gatherer.initializer().get();
66+
boolean integrateMore = true;
67+
var iterator = letters.iterator();
68+
while (integrateMore && iterator.hasNext())
69+
integrateMore = rawGatherer.integrator().integrate(state, iterator.next(), downstream);
70+
rawGatherer.finisher().accept(state, downstream);
71+
72+
return result;
73+
}
74+
75+
/* --- AFTER JEP 461 IS MERGED --- */
76+
// private static <R> List<R> apply_jep461(List<String> letters, Gatherer<String,?, R> gatherer) {
77+
// return letters.stream()
78+
// .gather(gatherer)
79+
// .toList();
80+
// }
81+
82+
public static <T> Gatherer<T, ?, T> doNothing() {
83+
Integrator<Void, T, T> integrator = (_, element, downstream) -> {
84+
downstream.push(element);
85+
return true;
86+
};
87+
return Gatherer.of(integrator);
88+
}
89+
90+
public static <T, R> Gatherer<T, ?, R> map(
91+
Function<? super T, ? extends R> mapper) {
92+
Integrator<Void, T, R> integrator = (_, element, downstream) -> {
93+
R newElement = mapper.apply(element);
94+
downstream.push(newElement);
95+
return true;
96+
};
97+
return Gatherer.of(integrator);
98+
}
99+
100+
public static <T> Gatherer<T, ?, T> filter(
101+
Predicate<? super T> filter) {
102+
Integrator<Void, T, T> integrator = (_, element, downstream) -> {
103+
var passOn = filter.test(element);
104+
if (passOn)
105+
downstream.push(element);
106+
return true;
107+
};
108+
return Gatherer.of(integrator);
109+
}
110+
111+
public static <T> Gatherer<T, ?, T> flatMapIf(
112+
Predicate<? super T> test,
113+
Function<? super T, Stream<? extends T>> mapper) {
114+
Integrator<Void, T, T> integrator = (_, element, downstream) -> {
115+
var expand = test.test(element);
116+
if (expand)
117+
mapper.apply(element).forEach(downstream::push);
118+
else
119+
downstream.push(element);
120+
return true;
121+
};
122+
return Gatherer.of(integrator);
123+
}
124+
125+
public static <T> Gatherer<T, ?, T> takeWhileIncluding(
126+
Predicate<? super T> predicate) {
127+
Integrator<Void, T, T> integrator = (_, element, downstream) -> {
128+
downstream.push(element);
129+
return predicate.test(element);
130+
};
131+
return Gatherer.of(integrator);
132+
}
133+
134+
public static <T> Gatherer<T, ?, T> limit(int numberOfElements) {
135+
Supplier<AtomicInteger> initializer = AtomicInteger::new;
136+
Integrator<AtomicInteger, T, T> integrator = (state, element, downstream) -> {
137+
var currentIndex = state.getAndIncrement();
138+
if (currentIndex < numberOfElements)
139+
downstream.push(element);
140+
return currentIndex + 1 < numberOfElements;
141+
};
142+
return Gatherer.ofSequential(initializer, integrator);
143+
}
144+
145+
public static <T> Gatherer<T, ?, T> increasing(Comparator<T> comparator) {
146+
Supplier<AtomicReference<T>> initializer = AtomicReference::new;
147+
Integrator<AtomicReference<T>, T, T> integrator = (state, element, downstream) -> {
148+
T largest = state.get();
149+
var isLarger = largest == null || comparator.compare(element, largest) > 0;
150+
if (isLarger) {
151+
downstream.push(element);
152+
state.set(element);
153+
}
154+
return true;
155+
};
156+
return Gatherer.ofSequential(initializer, integrator);
157+
}
158+
159+
public static Gatherer<Integer, ?, Double> runningAverage() {
160+
class State {
161+
162+
private long sum;
163+
private long count;
164+
165+
}
166+
Supplier<State> initializer = State::new;
167+
Integrator<State, Integer, Double> integrator = (state, element, downstream) -> {
168+
state.sum += element;
169+
state.count++;
170+
double average = (double) state.sum / state.count;
171+
downstream.push(average);
172+
return true;
173+
};
174+
return Gatherer.ofSequential(initializer, integrator);
175+
}
176+
177+
public static <T> Gatherer<T, ?, List<T>> slidingWindow(int size) {
178+
Supplier<List<T>> initializer = ArrayList::new;
179+
Integrator<List<T>, T, List<T>> integrator = (state, element, downstream) -> {
180+
state.addFirst(element);
181+
if (state.size() > size) {
182+
state.removeLast();
183+
}
184+
var group = List.copyOf(state);
185+
downstream.push(group);
186+
return true;
187+
};
188+
return Gatherer.ofSequential(initializer, integrator);
189+
}
190+
191+
public static <T> Gatherer<T, ?, List<T>> fixedGroups(int size) {
192+
Supplier<List<T>> initializer = ArrayList::new;
193+
Integrator<List<T>, T, List<T>> integrator = (state, element, downstream) -> {
194+
state.add(element);
195+
if (state.size() == size) {
196+
var group = List.copyOf(state);
197+
downstream.push(group);
198+
state.clear();
199+
}
200+
return true;
201+
};
202+
BiConsumer<List<T>, Downstream<? super List<T>>> finisher = (state, downstream) -> {
203+
var group = List.copyOf(state);
204+
downstream.push(group);
205+
};
206+
return Gatherer.ofSequential(initializer, integrator, finisher);
207+
}
208+
209+
public static <T> Gatherer<T, ?, T> sorted(Comparator<? super T> comparator) {
210+
Supplier<List<T>> initializer = ArrayList::new;
211+
Integrator<List<T>, T, T> integrator = (state, element, _) -> {
212+
state.add(element);
213+
return true;
214+
};
215+
BiConsumer<List<T>, Downstream<? super T>> finisher = (state, downstream) -> {
216+
state.sort(comparator);
217+
state.forEach(downstream::push);
218+
};
219+
return Gatherer.ofSequential(initializer, integrator, finisher);
220+
}
221+
222+
public static <T> Gatherer<T, ?, List<T>> increasingSequences(Comparator<T> comparator) {
223+
Supplier<List<T>> initializer = ArrayList::new;
224+
Integrator<List<T>, T, List<T>> integrator = (state, element, downstream) -> {
225+
boolean isInSequence = state.isEmpty()
226+
|| comparator.compare(element, state.getLast()) >= 0;
227+
if (!isInSequence) {
228+
var group = List.copyOf(state);
229+
downstream.push(group);
230+
state.clear();
231+
}
232+
state.addLast(element);
233+
return true;
234+
};
235+
BiConsumer<List<T>, Downstream<? super List<T>>> finisher = (state, downstream) -> {
236+
var group = List.copyOf(state);
237+
downstream.push(group);
238+
};
239+
return Gatherer.ofSequential(initializer, integrator, finisher);
240+
}
241+
242+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
package dev.nipafx.demo.java_next.api.gather.stream;
2+
3+
import java.util.function.BiConsumer;
4+
import java.util.function.BinaryOperator;
5+
import java.util.function.Supplier;
6+
7+
public interface Gatherer<T, A, R> {
8+
9+
Supplier<A> initializer();
10+
Integrator<A,T,R> integrator();
11+
BinaryOperator<A> combiner();
12+
BiConsumer<A, Downstream<? super R>> finisher();
13+
14+
interface Integrator<A, T, R> {
15+
boolean integrate(A state, T element, Downstream<? super R> downstream);
16+
}
17+
18+
interface Downstream<R> {
19+
boolean push(R element);
20+
}
21+
22+
static <T, A, R> Gatherer<T, A, R> of(Integrator<A, T, R> integrator) {
23+
return new Gatherer<T, A, R>() {
24+
@Override
25+
public Supplier<A> initializer() {
26+
return () -> null;
27+
}
28+
29+
@Override
30+
public Integrator<A, T, R> integrator() {
31+
return integrator;
32+
}
33+
34+
@Override
35+
public BinaryOperator<A> combiner() {
36+
return (state1, state2) -> { throw new IllegalStateException(); };
37+
}
38+
39+
@Override
40+
public BiConsumer<A, Downstream<? super R>> finisher() {
41+
return (state, downstream) -> { };
42+
}
43+
};
44+
}
45+
46+
static <T, A, R> Gatherer<T, A, R> ofSequential(Supplier<A> initializer, Integrator<A, T, R> integrator) {
47+
return new Gatherer<T, A, R>() {
48+
@Override
49+
public Supplier<A> initializer() {
50+
return initializer;
51+
}
52+
53+
@Override
54+
public Integrator<A, T, R> integrator() {
55+
return integrator;
56+
}
57+
58+
@Override
59+
public BinaryOperator<A> combiner() {
60+
return (state1, state2) -> { throw new IllegalStateException(); };
61+
}
62+
63+
@Override
64+
public BiConsumer<A, Downstream<? super R>> finisher() {
65+
return (state, downstream) -> { };
66+
}
67+
};
68+
}
69+
70+
static <T, A, R> Gatherer<T, A, R> ofSequential(Supplier<A> initializer, Integrator<A, T, R> integrator, BiConsumer<A, Downstream<? super R>> finisher) {
71+
return new Gatherer<T, A, R>() {
72+
@Override
73+
public Supplier<A> initializer() {
74+
return () -> null;
75+
}
76+
77+
@Override
78+
public Integrator<A, T, R> integrator() {
79+
return integrator;
80+
}
81+
82+
@Override
83+
public BinaryOperator<A> combiner() {
84+
return (state1, state2) -> { throw new IllegalStateException(); };
85+
}
86+
87+
@Override
88+
public BiConsumer<A, Downstream<? super R>> finisher() {
89+
return finisher;
90+
}
91+
};
92+
}
93+
94+
}

0 commit comments

Comments
 (0)