在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.grouping可以在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.Jrouping的Javadoc并没有说它不应该映射一个空键。出于某种原因这种行为是否必要?
2)是否有另一种更简单的方法来接受一个空键,而不必创建我自己的收藏家?
答案 0 :(得分:36)
我遇到了同样的问题。 这失败了,因为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())));
答案 1 :(得分:8)
对于第一个问题,我同意skiwi不应该抛出NPE
。我希望他们会改变它(或者至少将它添加到javadoc)。同时,为了回答第二个问题,我决定使用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提供了另一个更复杂的答案,它允许您指定自己的地图和列表供应商。我没有测试过。
答案 2 :(得分:6)
首先,您使用了大量原始对象。这根本不是一个好主意 ,首先转换以下内容:
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
个密钥,然后应该允许它。
到目前为止,我没有看到任何其他方法更简单,我会尝试更多地了解它。
答案 3 :(得分:4)
在groupingBy之前过滤掉空实例。
这是一个例子
MyObjectlist.stream().filter(p -> p.getSomeInstance() != null).collect(Collectors.groupingBy(MyObject::getSomeInstance));
答案 4 :(得分:3)
关于你的第一个问题,来自文档:
无法保证返回的Map或List对象的类型,可变性,可序列化或线程安全性。
因为并非所有Map实现都允许使用null键,所以他们可能会添加此项以减少最常见的地图允许定义,以便在选择类型时获得最大的灵活性。
对于你的第二个问题,你只需要一个供应商,不是一个lambda工作?我仍然熟悉Java 8,也许更聪明的人可以添加更好的答案。
答案 5 :(得分:3)
我想我会花一点时间尝试消化你遇到的这个问题。如果我手动完成,我会将SSCE放在一起,以及groupingBy
实现的实际内容。
我不认为这是一个答案,但它是一个'奇怪为什么它是一个问题'的事情。此外,如果您愿意,可以随意破解此代码以获得一个无效的收集器。
修改:通用友好型实施:
/** 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值进行分组,(如果输入String为null,则返回null)):
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)