20 opérateurs implémentés avec les Gatherer - Java 22
L'API Stream de Java évolue avec l'introduction des Stream Gatherers
via la proposition JEP 461
. Cette nouvelle fonctionnalité permet de créer des opérateurs personnalisés, offrant ainsi une plus grande flexibilité et des possibilités de traitement de données enrichies. Voici 20 exemples concrets pour illustrer ces nouvelles capacités. Ces exemples montrent comment les gatherers
peuvent être intégrés dans des projets Java pour des transformations de données plus spécifiques et adaptées à divers besoins.
1. map
Transformer chaque élément du flux en un autre objet à l'aide de la fonction spécifiée.
public static <T, R> Gatherer<T, ?, R> map(Function<? super T, ? extends R> mapper) {
Gatherer.Integrator<Void, T, R> integrator = (_, element, downstream) -> {
downstream.push(mapper.apply(element));
return true;
};
return Gatherer.of(integrator);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<Integer> output = input.stream()
.gather(map(n -> n * n))
.toList();
// output: [1, 4, 9, 16, 25]
2. filter
Filtrer les éléments du flux en fonction d'un prédicat spécifié.
public static <T> Gatherer<T, ?, T> filter(Predicate<T> predicate) {
Gatherer.Integrator<Void, T, T> integrator = (_, element, downstream) -> {
if (predicate.test(element)) {
downstream.push(element);
}
return true;
};
return Gatherer.of(integrator);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<Integer> output = input.stream()
.gather(filter(n -> n % 2 == 0))
.toList();
// output: [2, 4]
3. flatMap
Transformer chaque élément du flux puis aplatir le résultat en un seul flux.
public static <T, R> Gatherer<T, ?, R> flatMap(Function<? super T,
Stream<? extends R>> mapper) {
Gatherer.Integrator<Void, T, R> integrator = (_, element, downstream) -> {
mapper.apply(element).forEach(downstream::push);
return true;
};
return Gatherer.of(integrator);
}
Exemple d'utilisation
List<List<Integer>> input = List.of(
List.of(1, 2), List.of(3, 4), List.of(5, 6));
List<Integer> output = input.stream()
.gather(flatMap(List::stream))
.toList();
// output: [1, 2, 3, 4, 5, 6]
4. distinct
Éliminer les doublons du flux.
public static <T> Gatherer<T, ?, T> distinct() {
Supplier<Set<T>> initializer = HashSet::new;
Gatherer.Integrator<Set<T>, T, T> integrator = (state, element, downstream) -> {
if (state.add(element)) {
downstream.push(element);
}
return true;
};
return Gatherer.ofSequential(initializer, integrator);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5, 5, 4, 3, 2, 1);
List<Integer> output = input.stream()
.gather(distinct())
.toList();
// output: [1, 2, 3, 4, 5]
5. limit
Limiter la taille du flux à n éléments.
public static <T> Gatherer<T, ?, T> limit(long maxSize) {
Supplier<AtomicLong> initializer = AtomicLong::new;
Gatherer.Integrator<AtomicLong, T, T> integrator = (state, element, downstream) -> {
long size = state.getAndIncrement();
if (size < maxSize) {
downstream.push(element);
}
return size + 1 < maxSize;
};
return Gatherer.ofSequential(initializer, integrator);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<Integer> output = input.stream()
.gather(limit(3))
.toList();
// output: [1, 2, 3]
6. skip
Ignorer les n premiers éléments du flux.
public static <T> Gatherer<T, ?, T> skip(long n) {
Supplier<AtomicLong> initializer = AtomicLong::new;
Gatherer.Integrator<AtomicLong, T, T> integrator = (state, element, downstream) -> {
long index = state.getAndIncrement();
if (index >= n) {
downstream.push(element);
}
return true;
};
return Gatherer.ofSequential(initializer, integrator);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<Integer> output = input.stream()
.gather(skip(2))
.toList();
// output: [3, 4, 5]
7. takeWhile
Accepter les éléments tant que la condition spécifiée est vraie.
public static <T> Gatherer<T, ?, T> takeWhile(Predicate<T> predicate) {
Gatherer.Integrator<Void, T, T> integrator = (_, element, downstream) -> {
if (predicate.test(element)) {
downstream.push(element);
return true;
}
return false;
};
return Gatherer.of(integrator);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<Integer> output = input.stream()
.gather(takeWhile(n -> n < 3))
.toList();
// output: [1, 2]
8. dropWhile
Ignorer les éléments tant que la condition spécifiée est vraie puis inclure tous les éléments restants.
public static <T> Gatherer<T, ?, T> dropWhile(Predicate<T> predicate) {
Supplier<AtomicBoolean> startKeeping = AtomicBoolean::new;
Gatherer.Integrator<AtomicBoolean, T, T> integrator = (state, element, downstream) -> {
boolean keep = state.get();
if (keep) {
downstream.push(element);
} else if (!predicate.test(element)) {
state.set(true);
downstream.push(element);
}
return true;
};
return Gatherer.ofSequential(startKeeping, integrator);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<Integer> output = input.stream()
.gather(dropWhile(n -> n < 3))
.toList();
// output: [3, 4, 5]
9. scan
Appliquer une fonction accumulatrice à chaque élément du flux.
public static <T, R> Gatherer<T, ?, R> scan(R initialValue, BiFunction<R, T, R> accumulator) {
Supplier<AtomicReference<R>> initializer = () -> new AtomicReference<>(initialValue);
Gatherer.Integrator<AtomicReference<R>, T, R> integrator = (state, element, downstream) -> {
state.set(accumulator.apply(state.get(), element));
downstream.push(state.get());
return true;
};
return Gatherer.ofSequential(initializer, integrator);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<Integer> output = input.stream()
.gather(scan(0, Integer::sum))
.toList();
// output: [1, 3, 6, 10, 15]
10. pairwise
Regrouper les éléments consécutifs du flux en paires à partir du deuxième élément.
public static <T> Gatherer<T, ?, List<T>> pairwise() {
Supplier<AtomicReference<T>> initializer = AtomicReference::new;
Gatherer.Integrator<AtomicReference<T>, T, List<T>> integrator = (state, element, downstream) -> {
T previous = state.getAndSet(element);
if (previous != null) {
downstream.push(List.of(previous, element));
}
return true;
};
return Gatherer.ofSequential(initializer, integrator);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<List<Integer>> output = input.stream()
.gather(pairwise())
.toList();
// output: [[1, 2], [2, 3], [3, 4], [4, 5], [5, 6]]
11. Peek
Appliquer une action à chaque élément du flux sans modifier les éléments eux-mêmes.
public static <T> Gatherer<T, ?, T> peek(Consumer<T> consumer) {
Gatherer.Integrator<Void, T, T> integrator = (_, element, downstream) -> {
consumer.accept(element);
downstream.push(element);
return true;
};
return Gatherer.of(integrator);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<Integer> output = input.stream()
.gather(peek(i -> System.out.println(STR."Processing: \{i}")))
.toList();
// output:
// Processing: 1
// Processing: 2
// Processing: 3
// Processing: 4
// Processing: 5
// [1, 2, 3, 4, 5]
12. sorted
Trier les éléments du flux selon un comparateur spécifié.
public static <T> Gatherer<T, ?, T> sorted(Comparator<T> comparator) {
Supplier<List<T>> initializer = ArrayList::new;
Gatherer.Integrator<List<T>, T, T> integrator = (state, element, _) -> {
state.add(element);
return true;
};
BiConsumer<List<T>, Gatherer.Downstream<? super T>> finisher = (state, downstream) -> {
state.sort(comparator);
state.forEach(downstream::push);
};
return Gatherer.ofSequential(initializer, integrator, finisher);
}
Exemple d'utilisation
List<Integer> input = List.of(5, 3, 2, 1, 4);
List<Integer> output = input.stream()
.gather(sorted(Comparator.<Integer>naturalOrder()))
.toList();
// output: [1, 2, 3, 4, 5]
13. reverse
Inverser l'ordre des éléments du flux.
public static <T> Gatherer<T, ?, T> reverse() {
Supplier<List<T>> initializer = ArrayList::new;
Gatherer.Integrator<List<T>, T, T> integrator = (state, element, _) -> {
state.add(element);
return true;
};
BiConsumer<List<T>, Gatherer.Downstream<? super T>> finisher = (state, downstream) -> {
for (int i = state.size() - 1; i >= 0; i--) {
downstream.push(state.get(i));
}
};
return Gatherer.ofSequential(initializer, integrator, finisher);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<Integer> output = input.stream()
.gather(reverse())
.toList();
// output: [5, 4, 3, 2, 1]
14.window
Regrouper les éléments du flux en fenêtres de taille n.
public static <T> Gatherer<T, ?, List<T>> window(int size) {
assert size > 0 : "Size must be greater than 0";
Supplier<List<T>> initializer = () -> new ArrayList<>(size);
Gatherer.Integrator<List<T>, T, List<T>> integrator = (state, element, downstream) -> {
state.add(element);
if (state.size() == size) {
downstream.push(List.copyOf(state));
state.clear();
}
return true;
};
BiConsumer<List<T>, Gatherer.Downstream<? super List<T>>> finisher = (state, downstream) -> {
if (!state.isEmpty()) {
downstream.push(List.copyOf(state));
}
};
return Gatherer.ofSequential(initializer, integrator, finisher);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<List<Integer>> output = input.stream()
.gather(window(2))
.toList();
// output: [[1, 2], [3, 4], [5]]
15. slidingWindow
Regrouper les éléments du flux en fenêtres glissantes de taille n.
public static <T> Gatherer<T, ?, List<T>> slidingWindow(int size) {
assert size > 0 : "Size must be greater than 0";
Supplier<Deque<T>> initializer = () -> new ArrayDeque<>(size);
Gatherer.Integrator<Deque<T>, T, List<T>> integrator = (state, element, downstream) -> {
state.add(element);
if (state.size() == size) {
downstream.push(List.copyOf(state));
state.removeFirst();
}
return true;
};
BiConsumer<Deque<T>, Gatherer.Downstream<? super List<T>>> finisher = (state, downstream) -> {
if (!state.isEmpty()) {
downstream.push(List.copyOf(state));
}
};
return Gatherer.ofSequential(initializer, integrator, finisher);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<List<Integer>> output = input.stream()
.gather(slidingWindow(2))
.toList();
// output: [[1, 2], [2, 3], [3, 4], [4, 5], [5]]
16. interleave
Entrelacer les éléments de deux flux.
public static <T> Gatherer<T, ?, T> interleave(Stream<T> other) {
Supplier<Iterator<T>> initializer = other::iterator;
Gatherer.Integrator<Iterator<T>, T, T> integrator = (state, element, downstream) -> {
downstream.push(element);
if (state.hasNext()) {
downstream.push(state.next());
}
return true;
};
BiConsumer<Iterator<T>, Gatherer.Downstream<? super T>> finisher = (state, downstream) -> {
while (state.hasNext()) {
downstream.push(state.next());
}
};
return Gatherer.ofSequential(initializer, integrator, finisher);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 3, 5, 7, 9);
List<Integer> output = input.stream()
.gather(interleave(Stream.of(2, 4, 6, 8, 10)))
.toList();
// output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
17. concat
Concaténer plusieurs flux en un seul flux.
@SafeVarargs
public static <T> Gatherer<T, ?, T> concat(Stream<T>... other) {
Gatherer.Integrator<Void, T, T> integrator = (_, element, downstream) -> {
downstream.push(element);
return true;
};
BiConsumer<Void, Gatherer.Downstream<? super T>> finisher = (_, downstream) -> {
for (Stream<T> stream : other) {
stream.forEach(downstream::push);
}
};
return Gatherer.of(integrator, finisher);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<Integer> output = input.stream()
.gather(
concat(Stream.of(6, 7, 8, 9, 10), Stream.of(11, 12, 13, 14, 15)))
.toList();
// output: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]
18. repeat
Répéter les éléments du flux n fois.
public static <T> Gatherer<T, ?, T> repeat(int n) {
Gatherer.Integrator<Void, T, T> integrator = (_, element, downstream) -> {
for (int i = 0; i < n; i++) {
downstream.push(element);
}
return true;
};
return Gatherer.of(integrator);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5);
List<Integer> output = input.stream()
.gather(repeat(3))
.toList();
// output: [1, 1, 1, 2, 2, 2, 3, 3, 3, 4, 4, 4, 5, 5, 5]
19. distinctUntilChanged
Éliminer les éléments consécutifs en double du flux.
public static <T> Gatherer<T, ?, T> distinctUntilChanged() {
Supplier<AtomicReference<T>> initializer = AtomicReference::new;
Gatherer.Integrator<AtomicReference<T>, T, T> integrator = (state, element, downstream) -> {
T lastValue = state.getAndSet(element);
if (!element.equals(lastValue)) {
downstream.push(element);
}
return true;
};
return Gatherer.ofSequential(initializer, integrator);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 1, 2, 2, 3, 3, 1, 1, 4, 4, 5, 5, 2, 2);
List<Integer> output = input.stream()
.gather(distinctUntilChanged())
.toList();
// output: [1, 2, 3, 1, 4, 5, 2]
20. groupBy
Regrouper les éléments du flux en fonction d'une fonction de classification.
public static <T, K> Gatherer<T, ?, List<T>> groupBy(Function<? super T, ? extends K> classifier) {
Supplier<Map<K, List<T>>> initializer = HashMap::new;
Gatherer.Integrator<Map<K, List<T>>, T, List<T>> integrator = (state, element, _) -> {
K key = classifier.apply(element);
state.computeIfAbsent(key, _ -> new ArrayList<>()).add(element);
return true;
};
BiConsumer<Map<K, List<T>>, Gatherer.Downstream<? super List<T>>> finisher = (state, downstream) -> {
state.forEach((_, values) -> downstream.push(List.copyOf(values)));
};
return Gatherer.ofSequential(initializer, integrator, finisher);
}
Exemple d'utilisation
List<Integer> input = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
List<List<Integer>> output = input.stream()
.gather(groupBy(i -> i % 2 == 0))
.toList();
// output: [[1, 3, 5, 7, 9], [2, 4, 6, 8, 10]]
Conclusion
L'introduction des Stream Gatherers
dans JEP 461 marque une avancée significative pour le développement Java. Ces opérateurs personnalisés enrichissent les capacités de traitement des flux de données, permettant des transformations plus fines et adaptées aux besoins spécifiques de chaque projet. En exploitant ces nouvelles possibilités, il est possible d'écrire un code plus clair, plus performant, et mieux aligné sur les exigences métiers. Les exemples illustrés montrent la polyvalence et la puissance des Stream Gatherers, et ouvrent la voie à une utilisation plus appropriée des flux de données dans les applications Java modernes.
Last modified: 16 July 2024