Cela semble être un cas d'utilisation idéal pour la future Collectors.teeing
dans le JDK 12. Voici la webrev et voici le CSR . Vous pouvez l'utiliser comme suit :
Map.Entry<Map<Integer, BigDecimal>, List<BigDecimal>> result = data.entrySet().stream()
.collect(Collectors.teeing(
Collectors.toMap(
Map.Entry::getKey,
i -> func1.apply(i.getValue())),
Collectors.mapping(
i -> func1.andThen(func2).apply(i.getValue()),
Collectors.toList()),
Map::entry));
Collectors.teeing
collecte à deux collecteurs différents, puis fusionne les deux résultats partiels dans le résultat final. Pour cette étape finale, j'utilise la fonction de collecte de JDK 9 Map.entry(K k, V v)
mais j'aurais pu utiliser n'importe quel autre conteneur, à savoir Pair
o Tuple2
etc.
Pour le premier collecteur, j'utilise votre code exact pour collecter vers un Map
tandis que pour le second collecteur, j'utilise Collectors.mapping
ainsi que Collectors.toList
en utilisant Function.andThen
pour composer votre func1
y func2
pour l'étape de mise en correspondance.
EDITAR: Si vous ne pouvez pas attendre la sortie du JDK 12, vous pouvez utiliser ce code en attendant :
public static <T, A1, A2, R1, R2, R> Collector<T, ?, R> teeing(
Collector<? super T, A1, R1> downstream1,
Collector<? super T, A2, R2> downstream2,
BiFunction<? super R1, ? super R2, R> merger) {
class Acc {
A1 acc1 = downstream1.supplier().get();
A2 acc2 = downstream2.supplier().get();
void accumulate(T t) {
downstream1.accumulator().accept(acc1, t);
downstream2.accumulator().accept(acc2, t);
}
Acc combine(Acc other) {
acc1 = downstream1.combiner().apply(acc1, other.acc1);
acc2 = downstream2.combiner().apply(acc2, other.acc2);
return this;
}
R applyMerger() {
R1 r1 = downstream1.finisher().apply(acc1);
R2 r2 = downstream2.finisher().apply(acc2);
return merger.apply(r1, r2);
}
}
return Collector.of(Acc::new, Acc::accumulate, Acc::combine, Acc::applyMerger);
}
Remarque : les caractéristiques des collecteurs en aval ne sont pas prises en compte lors de la création du collecteur retourné (laissé à titre d'exercice).
EDIT 2 : Votre solution est tout à fait correcte, même si elle utilise deux flux. Ma solution ci-dessus ne diffuse la carte originale qu'une seule fois, mais elle applique func1
à toutes les valeurs deux fois. Si func1
est coûteux, vous pouvez envisager mémorisation (c'est-à-dire en mettant en cache ses résultats, de sorte qu'à chaque fois qu'elle est appelée à nouveau avec la même entrée, vous renvoyez le résultat du cache au lieu de le calculer à nouveau). Vous pouvez également commencer par appliquer func1
aux valeurs de la carte originale, puis de collecter avec Collectors.teeing
.
La mémorisation est facile. Il suffit de déclarer cette méthode utilitaire :
public <T, R> Function<T, R> memoize(Function<T, R> f) {
Map<T, R> cache = new HashMap<>(); // or ConcurrentHashMap
return k -> cache.computeIfAbsent(k, f);
}
Et ensuite, utilisez-le comme suit :
Function<BigDecimal, BigDecimal> func1 = memoize(x -> x); //This could be anything
Maintenant vous pouvez utiliser ce mémo func1
et il fonctionnera exactement comme avant, sauf qu'il renverra les résultats du cache lorsque son apply
est invoquée avec un argument qui a été utilisé précédemment.
L'autre solution serait d'appliquer func1
d'abord et ensuite de collecter :
Map.Entry<Map<Integer, BigDecimal>, List<BigDecimal>> result = data.entrySet().stream()
.map(i -> Map.entry(i.getKey(), func1.apply(i.getValue())))
.collect(Collectors.teeing(
Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue),
Collectors.mapping(
i -> func2.apply(i.getValue()),
Collectors.toList()),
Map::entry));
Encore une fois, j'utilise l'interface de jdk9. Map.entry(K k, V v)
méthode statique.