63

Java 8では、これは機能します:

Stream<Class> stream = Stream.of(ArrayList.class);
HashMap<Class, List<Class>> map = (HashMap)stream.collect(Collectors.groupingBy(Class::getSuperclass));

しかし、これはしません:

Stream<Class> stream = Stream.of(List.class);
HashMap<Class, List<Class>> map = (HashMap)stream.collect(Collectors.groupingBy(Class::getSuperclass));

Maps は null キーを許可し、List.class.getSuperclass() は null を返します。ただし、Collectors.groupingBy は Collectors.java の 907 行目で NPE を発行します。

K key = Objects.requireNonNull(classifier.apply(t), "element cannot be mapped to a null key"); 

この行を次のように変更して、独自のコレクターを作成すると機能します。

K key = classifier.apply(t);  

私の質問は次のとおりです。

1) Collectors.groupingBy の Javadoc は、null キーをマップすべきではないとは言っていません。この動作は何らかの理由で必要ですか?

2) 独自のコレクターを作成せずに、null キーを受け入れる別の簡単な方法はありますか?

4

7 に答える 7

73

私は同じ種類の問題を抱えていました。groupingBy が分類子から返された値に対して Objects.requireNonNull を実行するため、これは失敗しました。

    Map<Long, List<ClaimEvent>> map = events.stream()
      .filter(event -> eventTypeIds.contains(event.getClaimEventTypeId()))
      .collect(groupingBy(ClaimEvent::getSubprocessId));

Optional を使用すると、これは機能します。

    Map<Optional<Long>, List<ClaimEvent>> map = events.stream()
      .filter(event -> eventTypeIds.contains(event.getClaimEventTypeId()))
      .collect(groupingBy(event -> Optional.ofNullable(event.getSubprocessId())));
于 2014-09-26T12:13:20.837 に答える
17

最初の質問については、NPE. 彼らがそれを変更することを願っています(または、少なくともjavadocに追加してください)。Collectors.toMap一方、代わりに使用することにした2番目の質問に答えるためにCollectors.groupingBy

Stream<Class<?>> stream = Stream.of(ArrayList.class);

Map<Class<?>, List<Class<?>>> map = stream.collect(
    Collectors.toMap(
        Class::getSuperclass,
        Collections::singletonList,
        (List<Class<?>> oldList, List<Class<?>> newEl) -> {
        List<Class<?>> newList = new ArrayList<>(oldList.size() + 1);
        newList.addAll(oldList);
        newList.addAll(newEl);
        return newList;
        }));

または、それをカプセル化します。

/** Like Collectors.groupingBy, but accepts null keys. */
public static <T, A> Collector<T, ?, Map<A, List<T>>>
groupingBy_WithNullKeys(Function<? super T, ? extends A> classifier) {
    return Collectors.toMap(
        classifier,
        Collections::singletonList,
        (List<T> oldList, List<T> newEl) -> {
            List<T> newList = new ArrayList<>(oldList.size() + 1);
            newList.addAll(oldList);
            newList.addAll(newEl);
            return newList;
            });
    }

そして、次のように使用します。

Stream<Class<?>> stream = Stream.of(ArrayList.class);
Map<Class<?>, List<Class<?>>> map = stream.collect(groupingBy_WithNullKeys(Class::getSuperclass));

rolf が別のより複雑な回答を提供したことに注意してください。これにより、独自のマップとリストのサプライヤーを指定できます。私はそれをテストしていません。

于 2014-03-26T02:14:03.400 に答える
8

groupingBy## の前にフィルターを使用する

groupingBy の前に null インスタンスを除外します。

ここに例があります
MyObjectlist.stream()
  .filter(p -> p.getSomeInstance() != null)
  .collect(Collectors.groupingBy(MyObject::getSomeInstance));
于 2015-08-25T15:08:37.737 に答える
3

少し時間を取って、あなたが抱えているこの問題を消化しようと思いました. groupingBy手動で行った場合に予想されることと、実装が実際に行うことについて、SSCE をまとめました。

これが答えだとは思いませんが、「なぜそれが問題なのか」ということです。また、必要に応じて、このコードを自由にハックして、null フレンドリーなコレクターを作成してください。

編集:ジェネリックフレンドリーな実装:

/** groupingByNF - NullFriendly - allows you to specify your own Map and List supplier. */
private static final <T,K> Collector<T,?,Map<K,List<T>>> groupingByNF (
        final Supplier<Map<K,List<T>>> mapsupplier,
        final Supplier<List<T>> listsupplier,
        final Function<? super T,? extends K> classifier) {

    BiConsumer<Map<K,List<T>>, T> combiner = (m, v) -> {
        K key = classifier.apply(v);
        List<T> store = m.get(key);
        if (store == null) {
            store = listsupplier.get();
            m.put(key, store);
        }
        store.add(v);
    };

    BinaryOperator<Map<K, List<T>>> finalizer = (left, right) -> {
        for (Map.Entry<K, List<T>> me : right.entrySet()) {
            List<T> 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 <T,K> Collector<T,?,Map<K,List<T>>> groupingByNF (Function<? super T,? extends K> classifier) {
    return groupingByNF(HashMap::new, ArrayList::new, classifier);
}

次のコードを検討してください (このコードは、String.length() (または入力 String が null の場合は null) に基づいて String 値をグループ化します)。

public static void main(String[] args) {

    String[] input = {"a", "a", "", null, "b", "ab"};

    // How we group the Strings
    final Function<String, Integer> 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<Map<Integer,List<String>>, String> combiner = (m, v) -> {
        Integer key = classifier.apply(v);
        List<String> store = m.get(key);
        if (store == null) {
            store = new ArrayList<String>();
            m.put(key, store);
        }
        store.add(v);
    };

    // The finalizer merges two maps together (right into left)
    // no special handling of null key values.
    BinaryOperator<Map<Integer, List<String>>> finalizer = (left, right) -> {
        for (Map.Entry<Integer, List<String>> me : right.entrySet()) {
            List<String> 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<Integer, List<String>> manual = Arrays.stream(input).collect(Collector.of(HashMap::new, combiner, finalizer));

    System.out.println(manual);

    // using the groupingBy collector.        
    Collector<String, ?, Map<Integer, List<String>>> collector = Collectors.groupingBy(classifier);

    Map<Integer, List<String>> result = Arrays.stream(input).collect(collector);

    System.out.println(result);
}

上記のコードは、次の出力を生成します。

{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)
于 2014-03-25T14:35:08.610 に答える
3

まず、生のオブジェクトをたくさん使用しています。これはまったく良い考えはありません。まず、次を変換します。

  • ClassClass<?>、すなわち。生の型の代わりに、未知のクラスを持つパラメーター化された型。
  • 強制的に a にキャストする代わりに、コレクターにHashMapa を提供する必要があります。HashMap

最初に、まだ NPE を気にせずに、正しく型付けされたコードを示します。

Stream<Class<?>> stream = Stream.of(ArrayList.class);
HashMap<Class<?>, List<Class<?>>> hashMap = (HashMap<Class<?>, List<Class<?>>>)stream
        .collect(Collectors.groupingBy(Class::getSuperclass));

ここで強制的なキャストを取り除き、代わりに正しく行います。

Stream<Class<?>> stream = Stream.of(ArrayList.class);
HashMap<Class<?>, List<Class<?>>> hashMap = stream
        .collect(Collectors.groupingBy(
                Class::getSuperclass,
                HashMap::new,
                Collectors.toList()
        ));

ここでgroupingByは、分類子のみを受け取る を、分類子、サプライヤー、およびコレクターを受け取るものに置き換えます。基本的にこれは以前と同じですが、正しく入力されるようになりました。

javadoc では をスローするとは述べられていないことは確かに正しいですNPE。必要なマップを提供することが許可されているため、スローする必要はないと思います。マップでnullキーが許可されている場合は、許可されている。

今のところもっと簡単にできる方法は他にないので、もっと調べてみます。

于 2014-03-25T08:09:57.177 に答える
3

ドキュメントからの最初の質問へ:

返される Map または List オブジェクトの型、可変性、直列化可能性、またはスレッド セーフ性は保証されません。

すべての Map 実装が null キーを許可しているわけではないため、タイプを選択する際に最大限の柔軟性を得るために、マップの最も一般的な許容可能な定義に減らすためにこれを追加した可能性があります。

2 番目の質問に対しては、サプライヤーが必要なだけです。ラムダは機能しませんか? 私はまだJava 8に精通しています。賢い人がより良い答えを追加できるかもしれません。

于 2014-03-25T04:16:43.943 に答える