Peter, > EnumSet<Characteristics> intersection = EnumSet.copyOf(keyCollector.characteristics()); > intersection.retainAll(valCollector.characteristics()); > return intersection;
Please note that `copyOf` should either receive an EnumSet or non-empty Collection to obtain the enum class. This is not guaranteed by `characteristics()` implementation, thus failure is possible. Testcase: new BiCollector<>(Collectors.counting(), Collectors.toSet()).characteristics(); Fails with Exception in thread "main" java.lang.IllegalArgumentException: Collection is empty at java.base/java.util.EnumSet.copyOf(EnumSet.java:173) at BiCollector.characteristics(BiCollector.java:77) at Main.main(Main.java:21) With best regards, Tagir Valeev. On Mon, Jun 11, 2018 at 7:40 PM 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 > >