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キーを受け入れる別の簡単な方法はありますか?
最初の質問については、skiwiがNPE
を投げるべきではないことに同意します。彼らがそれを変えてくれることを願っています(あるいは、少なくともそれをjavadocに追加してください)。一方、2番目の質問に答えるために、Collectors.toMap
の代わりに 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));
Rolflは、より複雑な別の回答を提供しました。これにより、独自のマップおよびリストサプライヤを指定できます。私はそれをテストしていません。
同じような問題がありました。 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())));
GroupingByの前にnullインスタンスを除外します。
MyObjectlist.stream().filter(p -> p.getSomeInstance() != null).collect(Collectors.groupingBy(MyObject::getSomeInstance));
まず第一に、あなたはたくさんの生のオブジェクトを使用しています。これは良い考えではありませんすべて、最初に以下を変換します:
Class
からClass<?>
、つまり。生の型の代わりに、未知のクラスを持つパラメータ化された型。HashMap
にキャストする代わりに、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
キーを許可する場合、許可する必要があります。
今のところ、これをもっと簡単にする他の方法はありません。もっと詳しく調べてみます。
最初の質問に、ドキュメントから:
返されるMapまたはListオブジェクトの型、可変性、直列化可能性、またはスレッドセーフ性についての保証はありません。
すべてのMap実装でnullキーが許可されているわけではないため、おそらくこれを追加して、マップの最も一般的な許容定義を減らして、タイプを選択する際の柔軟性を最大限に高めます。
2番目の質問に対して、サプライヤーが必要なだけです。ラムダは機能しませんか?私はまだJava 8に精通しています。おそらく、より賢い人がより良い答えを追加できるかもしれません。
少し時間がかかり、この問題を消化しようと思いました。手動で行った場合に期待すること、および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()に基づいてストリング値をグループ化します(入力ストリングがヌルの場合はヌルです))。
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)