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
>
>

Reply via email to