On Jun 11, 2018, at 5:39 AM, Peter Levart <peter.lev...@gmail.com> wrote:
Hi,
Have you ever wanted to perform a collection of the same Stream into two different
targets using two Collectors? Say you wanted to collect Map.Entry elements into two
parallel lists, each of them containing keys and values respectively. Or you wanted
to collect elements into groups by some key, but also count them at the same time?
Currently this is not possible to do with a single Stream. You have to create two
identical streams, so you end up passing Supplier<Stream> to other methods
instead of bare Stream.
I created a little utility Collector implementation that serves the purpose
quite well:
/**
* A {@link Collector} implementation taking two delegate Collector(s) and
producing result composed
* of two results produced by delegating collectors, wrapped in {@link
Map.Entry} object.
*
* @param <T> the type of elements collected
* @param <K> the type of 1st delegate collector collected result
* @param <V> tye type of 2nd delegate collector collected result
*/
public class BiCollector<T, K, V> implements Collector<T, Map.Entry<Object, Object>,
Map.Entry<K, V>> {
private final Collector<T, Object, K> keyCollector;
private final Collector<T, Object, V> valCollector;
@SuppressWarnings("unchecked")
public BiCollector(Collector<T, ?, K> keyCollector, Collector<T, ?, V>
valCollector) {
this.keyCollector = (Collector) Objects.requireNonNull(keyCollector);
this.valCollector = (Collector) Objects.requireNonNull(valCollector);
}
@Override
public Supplier<Map.Entry<Object, Object>> supplier() {
Supplier<Object> keySupplier = keyCollector.supplier();
Supplier<Object> valSupplier = valCollector.supplier();
return () -> new AbstractMap.SimpleImmutableEntry<>(keySupplier.get(),
valSupplier.get());
}
@Override
public BiConsumer<Map.Entry<Object, Object>, T> accumulator() {
BiConsumer<Object, T> keyAccumulator = keyCollector.accumulator();
BiConsumer<Object, T> valAccumulator = valCollector.accumulator();
return (accumulation, t) -> {
keyAccumulator.accept(accumulation.getKey(), t);
valAccumulator.accept(accumulation.getValue(), t);
};
}
@Override
public BinaryOperator<Map.Entry<Object, Object>> combiner() {
BinaryOperator<Object> keyCombiner = keyCollector.combiner();
BinaryOperator<Object> valCombiner = valCollector.combiner();
return (accumulation1, accumulation2) -> new
AbstractMap.SimpleImmutableEntry<>(
keyCombiner.apply(accumulation1.getKey(), accumulation2.getKey()),
valCombiner.apply(accumulation1.getValue(),
accumulation2.getValue())
);
}
@Override
public Function<Map.Entry<Object, Object>, Map.Entry<K, V>> finisher() {
Function<Object, K> keyFinisher = keyCollector.finisher();
Function<Object, V> valFinisher = valCollector.finisher();
return accumulation -> new AbstractMap.SimpleImmutableEntry<>(
keyFinisher.apply(accumulation.getKey()),
valFinisher.apply(accumulation.getValue())
);
}
@Override
public Set<Characteristics> characteristics() {
EnumSet<Characteristics> intersection =
EnumSet.copyOf(keyCollector.characteristics());
intersection.retainAll(valCollector.characteristics());
return intersection;
}
}
Do you think this class is general enough to be part of standard Collectors
repertoire?
For example, accessed via factory method Collectors.toBoth(Collector coll1,
Collector coll2), bi-collection could then be coded simply as:
Map<String, Integer> map = ...
Map.Entry<List<String>, List<Integer>> keys_values =
map.entrySet()
.stream()
.collect(
toBoth(
mapping(Map.Entry::getKey, toList()),
mapping(Map.Entry::getValue, toList())
)
);
Map.Entry<Map<Integer, Long>, Long> histogram_count =
ThreadLocalRandom
.current()
.ints(100, 0, 10)
.boxed()
.collect(
toBoth(
groupingBy(Function.identity(), counting()),
counting()
)
);
Regards, Peter