BiCollector

Peter Levart peter.levart at gmail.com
Sun Jun 17 09:08:58 UTC 2018



On 06/15/18 13:31, Tagir Valeev wrote:
> 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)

Yeah, it came to my mind after I posted the example implementation. A 
case where API design sometimes misleads even experienced programmers, 
because it re-uses an idiom that is usually right (for most other 
Set(s)), but fails for that particular case. EnumSet needs an explicit 
enum type, so it should have been provided as an explicit argument...

Regards, Peter

>
>
> With best regards,
> Tagir Valeev.
>
> On Mon, Jun 11, 2018 at 7:40 PM Peter Levart <peter.levart at gmail.com 
> <mailto:peter.levart at 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
>



More information about the core-libs-dev mailing list