NullPointerException在Collectors.toMap中,使用null条目值

18 浏览
0 Comments

NullPointerException在Collectors.toMap中,使用null条目值

Collectors.toMap在值为null时会抛出NullPointerException异常。我不理解这种行为,因为映射中可以包含值为null的指针而没有任何问题。是否有足够的理由导致Collectors.toMap中的值不能为null

此外,是否有一种优雅的Java 8方法来解决这个问题,还是应该回到旧的普通循环?

我的问题示例:

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
class Answer {
    private int id;
    private Boolean answer;
    Answer() {
    }
    Answer(int id, Boolean answer) {
        this.id = id;
        this.answer = answer;
    }
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public Boolean getAnswer() {
        return answer;
    }
    public void setAnswer(Boolean answer) {
        this.answer = answer;
    }
}
public class Main {
    public static void main(String[] args) {
        List answerList = new ArrayList<>();
        answerList.add(new Answer(1, true));
        answerList.add(new Answer(2, true));
        answerList.add(new Answer(3, null));
        Map answerMap =
        answerList
                .stream()
                .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));
    }
}

堆栈跟踪:

Exception in thread "main" java.lang.NullPointerException
    at java.util.HashMap.merge(HashMap.java:1216)
    at java.util.stream.Collectors.lambda$toMap$168(Collectors.java:1320)
    at java.util.stream.Collectors$$Lambda$5/1528902577.accept(Unknown Source)
    at java.util.stream.ReduceOps$3ReducingSink.accept(ReduceOps.java:169)
    at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(ArrayList.java:1359)
    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 Main.main(Main.java:48)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:483)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:134)

这个问题在Java 11中仍然存在。

0
0 Comments

NullPointerException in Collectors.toMap with null entry values问题的出现原因是,当使用Collectors.toMap()方法并且存在null值时,会抛出NullPointerException异常。这是因为允许使用null值并且使用putIfAbsent方法不兼容。putIfAbsent方法在存在null值的情况下不能检测到重复的键。

解决这个问题的方法是使用上述代码中提供的自定义Collector。这个Collector可以处理null值,并且在出现重复键的情况下会抛出IllegalStateException异常。

要解决这个问题,只需将原来的Collectors.toMap()调用替换为对自定义Collector的调用即可。这样就可以避免NullPointerException异常的出现。

下面是完整的代码示例:

public static 
        Collector> toMap(Function keyMapper,
                Function valueMapper) {
    return Collectors.collectingAndThen(
            Collectors.toList(),
            list -> {
                Map result = new HashMap<>();
                for (T item : list) {
                    K key = keyMapper.apply(item);
                    if (result.putIfAbsent(key, valueMapper.apply(item)) != null) {
                        throw new IllegalStateException(String.format("Duplicate key %s", key));
                    }
                }
                return result;
            });
}

通过使用这个自定义Collector,就可以在存在null值的情况下避免NullPointerException异常,并且在出现重复键时会抛出IllegalStateException异常。

0
0 Comments

NullPointerException in Collectors.toMap with null entry values出现的原因是在使用Collectors.toMap时,如果存在null的entry values,会抛出NullPointerException异常。这是OpenJDK中已知的一个bug。

解决方法是使用如下代码,来绕过这个bug:

Map collect = list.stream()
        .collect(HashMap::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap::putAll);

这段代码使用了HashMap作为收集器的容器,通过lambda表达式将每个entry的key和value放入HashMap中。

需要注意的是,与Collectors.toMap不同的是,这种解决方法会静默地替换具有相同key的多个entry的value。如果不希望发生替换,可以查看评论中的链接了解更多信息。

另外,还有一个问题是是否可以在使用TreeMap时通过传递比较器给new来使用类似的语法。答案是肯定的,可以通过传递一个没有参数且返回结果的供应者(即第一个参数)的lambda表达式来实现。例如,要创建一个不区分大小写的String作为key的TreeMap,可以使用以下lambda表达式:`() -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)`。

这种解决方法是有效的,但可能在处理大量数据时性能较低。如果输入数据较大,可以考虑使用非流式解决方法或者在输入数据是并行的情况下使用forEach()方法。

需要注意的是,这种解决方法与原始的toMap实现行为不同。原始实现会检测重复的key并抛出IllegalStatException异常,而这种解决方法会静默地接受最新的key。如果希望保持原始行为,可以参考Emmanuel Touzery的解决方法。

最后,还有一个相关的bug报告可以参考,该bug报告涵盖了甚至Java 17的情况。

通过以上整理,我们了解了NullPointerException in Collectors.toMap with null entry values问题的出现原因和解决方法,以及一些相关的讨论和注意事项。希望这篇文章对您有帮助!

0
0 Comments

NullPointerException in Collectors.toMap with null entry values

在使用Collectors的静态方法时,无法处理null值的情况。toMap方法的文档解释了toMap方法是基于Map.merge方法的:

mergeFunction参数是一个合并函数,用于解决与同一个键关联的值之间的冲突,该函数由Map.merge(Object, Object, BiFunction)提供。

而Map.merge方法的文档中说明了:

如果指定的键为null且该map不支持null键,或者值或remappingFunction为null,则抛出NullPointerException异常。

为了避免使用for循环,可以使用列表的forEach方法。

Map answerMap = new HashMap<>();

answerList.forEach((answer) -> answerMap.put(answer.getId(), answer.getAnswer()));

但这种方法并不比旧方式简单:

Map answerMap = new HashMap<>();

for (Answer answer : answerList) {

answerMap.put(answer.getId(), answer.getAnswer());

}

在这种情况下,我更愿意使用传统的for-each循环。我应该将这个问题视为toMerge的一个bug,因为使用这个合并函数实际上是一个实现细节,或者不允许toMap处理null值有一个很好的理由吗?

我在文档中没有看到这个问题,你引用的是toMap方法的不同重载函数的问题。我没有提供合并函数。不管怎样,感谢你的回答,对我来说有点失望,因为旧的方式仍然是最有吸引力的选项(我更愿意使用Java 8的方式)。

是的,这来自一个重载的toMap方法。这个方法是其他方法的基础方法,所以我想它的文档更完整。但你是对的,它在你使用的方法中没有明确指定。

从未想过在Map中使用null值会对标准API产生如此大的影响,我宁愿将其视为一个缺陷。

实际上,API文档没有关于使用Map.merge的说明。我认为这是一个实现上的缺陷,限制了一个完全可以接受的使用案例,被忽视了。toMap的重载方法确实说明了使用Map.merge,但没有使用OP使用的方法。

虽然它在接受mergeFunction的重载方法中提到了它,但在第一个方法中并没有。不管怎样,这显然是一个疏忽。

甚至有一个错误报告:https://bugs.openjdk.java.net/browse/JDK-8148463

这个答案不应该被接受。给出了正确的答案。

我不在乎它在底层做了什么。当我阅读Javadoc时,我只关心契约。Javadoc应该说明如果任何元素为null,则抛出NullPointerException异常!

同意100%。我今天注意到Map.ofEntries也有同样的缺陷。我想不出任何原因(除了JDK开发人员因为现在他们不必在equals和hashCode方法中进行null检查而变得懒惰)他们也禁止在那里使用null值。

是的,这就是规范所说的。然而,这看起来是规范中的一个错误,因为这没有意义。它可能应该写成:如果指定的键为null且该map不支持null键,或者remappingFunction的值为null,则抛出NullPointerException异常。

0