Collectors.groupingBy doesn't accept null keys

前端 未结 6 2061
囚心锁ツ
囚心锁ツ 2020-12-15 02:49

In Java 8, this works:

Stream stream = Stream.of(ArrayList.class);
HashMap> map = (HashMap)stream.collect(Collect         


        
6条回答
  •  佛祖请我去吃肉
    2020-12-15 03:21

    I figured I would take a moment and try to digest this issue you have. I put together a SSCE for what I would expect if I did it manually, and what the groupingBy implementation actually does.

    I don't think this is an answer, but it is a 'wonder why it is a problem' thing. Also, if you want, feel free to hack this code to have a null-friendly collector.

    Edit: A generic-friendly implementation:

    /** groupingByNF - NullFriendly - allows you to specify your own Map and List supplier. */
    private static final  Collector>> groupingByNF (
            final Supplier>> mapsupplier,
            final Supplier> listsupplier,
            final Function classifier) {
    
        BiConsumer>, T> combiner = (m, v) -> {
            K key = classifier.apply(v);
            List store = m.get(key);
            if (store == null) {
                store = listsupplier.get();
                m.put(key, store);
            }
            store.add(v);
        };
    
        BinaryOperator>> finalizer = (left, right) -> {
            for (Map.Entry> me : right.entrySet()) {
                List target = left.get(me.getKey());
                if (target == null) {
                    left.put(me.getKey(), me.getValue());
                } else {
                    target.addAll(me.getValue());
                }
            }
            return left;
        };
    
        return Collector.of(mapsupplier, combiner, finalizer);
    
    }
    
    /** groupingByNF - NullFriendly - otherwise similar to Java8 Collections.groupingBy */
    private static final  Collector>> groupingByNF (Function classifier) {
        return groupingByNF(HashMap::new, ArrayList::new, classifier);
    }
    

    Consider this code (the code groups String values based on the String.length(), (or null if the input String is null)):

    public static void main(String[] args) {
    
        String[] input = {"a", "a", "", null, "b", "ab"};
    
        // How we group the Strings
        final Function classifier = (a) -> {return a != null ? Integer.valueOf(a.length()) : null;};
    
        // Manual implementation of a combiner that accumulates a string value based on the classifier.
        // no special handling of null key values.
        BiConsumer>, String> combiner = (m, v) -> {
            Integer key = classifier.apply(v);
            List store = m.get(key);
            if (store == null) {
                store = new ArrayList();
                m.put(key, store);
            }
            store.add(v);
        };
    
        // The finalizer merges two maps together (right into left)
        // no special handling of null key values.
        BinaryOperator>> finalizer = (left, right) -> {
            for (Map.Entry> me : right.entrySet()) {
                List target = left.get(me.getKey());
                if (target == null) {
                    left.put(me.getKey(), me.getValue());
                } else {
                    target.addAll(me.getValue());
                }
            }
            return left;
        };
    
        // Using a manual collector
        Map> manual = Arrays.stream(input).collect(Collector.of(HashMap::new, combiner, finalizer));
    
        System.out.println(manual);
    
        // using the groupingBy collector.        
        Collector>> collector = Collectors.groupingBy(classifier);
    
        Map> result = Arrays.stream(input).collect(collector);
    
        System.out.println(result);
    }
    

    The above code produces the output:

    {0=[], null=[null], 1=[a, a, b], 2=[ab]}
    Exception in thread "main" java.lang.NullPointerException: element cannot be mapped to a null key
      at java.util.Objects.requireNonNull(Objects.java:228)
      at java.util.stream.Collectors.lambda$groupingBy$135(Collectors.java:907)
      at java.util.stream.Collectors$$Lambda$10/258952499.accept(Unknown Source)
      at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
      at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
      at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
      at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
      at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
      at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
      at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
      at CollectGroupByNull.main(CollectGroupByNull.java:49)
    

提交回复
热议问题