收集器.分组依据不接受空键

2022-08-31 15:22:13

在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 允许空键,List.class.getSuperclass() 返回 null。但是 Collectors.groupingBy 发出一个 NPE,位于 Collectors.java,第 907 行:

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

如果我创建自己的收集器,并将此行更改为:

K key = classifier.apply(t);  

我的问题是:

1)Collectors.groupingBy的Javadoc并没有说它不应该映射空键。出于某种原因,此行为是否必要?

2)有没有另一种更简单的方法来接受空密钥,而不必创建我自己的收集器?


答案 1

我也有同样的问题。此操作失败,因为 groupingBy 对从分类器返回的值执行 Objects.requireNonNull:

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

使用可选,这有效:

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

答案 2

对于第一个问题,我同意skiwi的观点,即它不应该抛出一个.我希望他们会改变这一点(或者至少将其添加到javadoc中)。同时,为了回答第二个问题,我决定使用:NPECollectors.toMapCollectors.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给出了另一个更复杂的答案,允许您指定自己的地图和列表供应商。我还没有测试过。