My expirience with gatherers
Olexandr Rotan
rotanolexandr842 at gmail.com
Fri Oct 17 18:45:19 UTC 2025
Greetings to the core libs folks. I have been using Gatherers extensively
for my job in a past few months, and would like to share some of the
gatherers that I have been extensively using, so maybe some of them may be
a source of inspiration for evolving the Gatherers class.
1. eagerlyConsume()
Implementation:
public static <T> Gatherer<T, ?, T> eagerlyConsume() {
return Gatherer.of(
ArrayList<T>::new,
(list, val, downstream) -> {
list.add(val);
return true;
},
(left, right) -> {
left.addAll(right);
return left;
},
(list, downstream) -> {
for (var item : list) {
downstream.push(item);
}
}
);
}
Purpose: many times, i need to perform a concurrent mapping of jpa entities
to dtos. Unfortunately, mapConcurrent does not accept custom executors,
which i need in order to propagate auth, transaction and other contexts.
So, therefore, I previously have used following pattern:
stream().map(COmpletableFuture.supplyAsync(...,
executor)).toList().stream().map(CompletableFuture::join)
toList is required here to eagerly start all futures, as otherwise the will
actually launch sequentially due to the pulling nature of streams. With
gatherer, on the other hand, i can achieve following:
stream().map(COmpletableFuture.supplyAsync(..., executor))..gather
(eagerlyConsume()) .map(CompletableFuture::join), which looks much more
readable, and (presumably, haven't actually verified it) should have better
performance
2. ofCollector
Implementation:
public <T, A, R> Gatherer<T, A, R> ofCollector(Collector<T, A, R> collector) {
return Gatherer.of(
collector.supplier(),
(a, t, _) -> {
collector.accumulator().accept(a, t);
return true;
},
collector.combiner(),
(state, downstream) ->
downstream.push(collector.finisher().apply(state))
);
}
Pretty self explanatory, this is just an adapter of collector to gatherer,
allowing arbitrary collector-defined folds
3. collectThenFlatten & co
Implementations:
public static <T, A, R extends Collection<T>> Gatherer<T, A, T>
collectThenFlatten(Collector<T, A, R> collector) {
return Gatherer.of(
collector.supplier(),
(a, t, _) -> {
collector.accumulator().accept(a, t);
return true;
},
collector.combiner(),
(state, downstream) -> {
for (var item : collector.finisher().apply(state)) {
downstream.push(item);
}
}
);
}
public static <T, A, K, V, R extends Map<K, V>> Gatherer<T, A,
Map.Entry<K, V>> collectThenFlattenEntries(Collector<T, A, R>
collector) {
return Gatherer.of(
collector.supplier(),
(a, t, _) -> {
collector.accumulator().accept(a, t);
return true;
},
collector.combiner(),
(state, downstream) -> {
for (var entry : collector.finisher().apply(state).entrySet()) {
downstream.push(entry);
}
}
);
}
These are more specialized adapters of collector adapters, mostly a
convenience for avoiding flatMapping results, THough, I would like to note
that collectThenFlattenEntries is mostly used specifically with groupingBy
collector, to avoid following nasty chains:
collect(groupingBy(...)).entrySet().stream()
Maybe it's just my personal preferences, but i really dislike this back n
forth from stream to map, then to set and to stream again, so this gatherer
seems pretty pleasant to use
That's basically all that I wanted to share regarding this topic, hope this
experience will have some value for core libs maintainers
Best regards
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://mail.openjdk.org/pipermail/core-libs-dev/attachments/20251017/62262b28/attachment.htm>
More information about the core-libs-dev
mailing list