The JEP 461: Stream Gatherers Java 22 preview language feature adds support for gatherer operations, which could be used to filter the stream for duplicates:
void main() {
List<Integer> numbers = List.of(1, 2, 1, 3, 4, 4, 1);
List<Integer> duplicates = numbers.stream().gather(duplicates()).toList();
System.out.println(duplicates); // [1, 4]
}
static <T> Gatherer<T, ?, T> duplicates() {
Gatherer.Integrator.Greedy<Map<T, Integer>, T, T> integrator =
(state, element, downstream) -> {
Integer occurrences =
state.compute(element, (k, v) -> v == null ? 1 : v + 1);
if (occurrences == 2) {
return downstream.push(element);
} else {
return true;
}
};
return Gatherer.ofSequential(
HashMap::new,
Gatherer.Integrator.ofGreedy(integrator)
);
}
The custom gatherer keeps track of the number of occurrences of each value, and pushes an element downstream whenever the second instance of the value is encountered. This way, each distinct element is passed downstream once it is identified as a duplicate, and no other times.
Granted, this is a fair amount of code in isolation, but it can be reused as an intermediate operation in any stream that needs to operate only on duplicates.
Javadocs
Gatherer
:
An intermediate operation that transforms a stream of input elements into a stream of output elements, optionally applying a final action when the end of the upstream is reached. […]
[…]
There are many examples of gathering operations, including but not limited to: grouping elements into batches (windowing functions); de-duplicating consecutively similar elements; incremental accumulation functions (prefix scan); incremental reordering functions, etc. The class Gatherers
provides implementations of common gathering operations.
API Note:
A Gatherer
is specified by four functions that work together to process input elements, optionally using intermediate state, and optionally perform a final action at the end of input. They are:
Stream.gather(Gatherer<? super T,?,R> gatherer)
:
Returns a stream consisting of the results of applying the given gatherer to the elements of this stream.
Gatherer.ofSequential(initializer, integrator)
Returns a new, sequential, Gatherer
described by the given initializer
and integrator
.