Collectors.toMap中的Java 8 NullPointerException

本文翻译自:Java 8 NullPointerException in Collectors.toMap

The Java 8 Collectors.toMap throws a NullPointerException if one of the values is 'null'. 如果值之一为“ null”,则Java 8 Collectors.toMap会引发NullPointerException I don't understand this behaviour, maps can contain null pointers as value without any problems. 我不了解这种行为,地图可以包含空指针作为值,而没有任何问题。 Is there a good reason why values cannot be null for Collectors.toMap ? 是否有充分的理由为什么Collectors.toMap值不能为null?

Also, is there a nice Java 8 way of fixing this, or should I revert to plain old for loop? 另外,是否有解决此问题的不错的Java 8方法,还是应该将其恢复为普通的for循环?

An example of my problem: 我的问题的一个例子:

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<Answer> answerList = new ArrayList<>();

        answerList.add(new Answer(1, true));
        answerList.add(new Answer(2, true));
        answerList.add(new Answer(3, null));

        Map<Integer, Boolean> answerMap =
        answerList
                .stream()
                .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));
    }
}

Stacktrace: 堆栈跟踪:

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)

This problem still exists in Java 11. Java 11中仍然存在此问题。


#1楼

参考:https://stackoom.com/question/1FlDB/Collectors-toMap中的Java-NullPointerException


#2楼

It is not possible with the static methods of Collectors . 使用Collectors的静态方法是不可能的。 The javadoc of toMap explains that toMap is based on Map.merge : toMap的javadoc解释说toMap基于Map.merge

@param mergeFunction a merge function, used to resolve collisions between values associated with the same key, as supplied to Map#merge(Object, Object, BiFunction)} @param mergeFunction一个合并函数,用于解决与相同键关联的值之间的冲突,如提供给Map#merge(Object, Object, BiFunction)}

and the javadoc of Map.merge says: 并且Map.merge的javadoc说:

@throws NullPointerException if the specified key is null and this map does not support null keys or the value or remappingFunction is null 如果指定键为null且此映射不支持空键,或者value或remappingFunction null,则@throws NullPointerException

You can avoid the for loop by using the forEach method of your list. 您可以通过使用列表的forEach方法来避免for循环。

Map<Integer,  Boolean> answerMap = new HashMap<>();
answerList.forEach((answer) -> answerMap.put(answer.getId(), answer.getAnswer()));

but it is not really simple than the old way: 但这并不比原来的方法简单:

Map<Integer, Boolean> answerMap = new HashMap<>();
for (Answer answer : answerList) {
    answerMap.put(answer.getId(), answer.getAnswer());
}

#3楼

According to the Stacktrace 根据Stacktrace

Exception in thread "main" java.lang.NullPointerException
at java.util.HashMap.merge(HashMap.java:1216)
at java.util.stream.Collectors.lambda$toMap$148(Collectors.java:1320)
at java.util.stream.Collectors$$Lambda$5/391359742.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 com.guice.Main.main(Main.java:28)
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)

When is called the map.merge 什么时候被称为map.merge

        BiConsumer<M, T> accumulator
            = (map, element) -> map.merge(keyMapper.apply(element),
                                          valueMapper.apply(element), mergeFunction);

It will do a null check as first thing 它将做一个null检查作为第一件事

if (value == null)
    throw new NullPointerException();

I don't use Java 8 so often so i don't know if there are a better way to fix it, but fix it is a bit hard. 我不经常使用Java 8,所以我不知道是否有更好的方法来修复它,但是修复它有点困难。

You could do: 您可以这样做:

Use filter to filter all NULL values, and in the Javascript code check if the server didn't send any answer for this id means that he didn't reply to it. 使用filter过滤所有NULL值,然后在Javascript代码中检查服务器是否没有为此ID发送任何答案,这意味着他没有答复。

Something like this: 像这样:

Map<Integer, Boolean> answerMap =
        answerList
                .stream()
                .filter((a) -> a.getAnswer() != null)
                .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));

Or use peek, which is used to alter the stream element for element. 或使用peek,用于更改元素的流元素。 Using peek you could change the answer to something more acceptable for map but it means edit your logic a bit. 使用peek,您可以将答案更改为地图更可接受的选项,但这意味着需要稍微编辑一下逻辑。

Sounds like if you want to keep the current design you should avoid Collectors.toMap 听起来,如果您想保留当前的设计,则应避免使用Collectors.toMap


#4楼

You can work around this known bug in OpenJDK with this: 您可以使用以下方法来解决OpenJDK中的此已知错误

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

It is not that much pretty, but it works. 它不是很漂亮,但是可以工作。 Result: 结果:

1: true
2: true
3: null

( this tutorial helped me the most.) 教程对我的帮助最大。)


#5楼

I wrote a Collector which, unlike the default java one, does not crash when you have null values: 我编写了一个Collector ,与默认的Java不同,它在具有null值时不会崩溃:

public static <T, K, U>
        Collector<T, ?, Map<K, U>> toMap(Function<? super T, ? extends K> keyMapper,
                Function<? super T, ? extends U> valueMapper) {
    return Collectors.collectingAndThen(
            Collectors.toList(),
            list -> {
                Map<K, U> 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;
            });
}

Just replace your Collectors.toMap() call to a call to this function and it'll fix the problem. 只需将您的Collectors.toMap()调用替换为对此函数的调用即可,它将解决此问题。


#6楼

Here's somewhat simpler collector than proposed by @EmmanuelTouzery. 这比@EmmanuelTouzery提出的收集器要简单一些。 Use it if you like: 如果喜欢,请使用它:

public static <T, K, U> Collector<T, ?, Map<K, U>> toMapNullFriendly(
        Function<? super T, ? extends K> keyMapper,
        Function<? super T, ? extends U> valueMapper) {
    @SuppressWarnings("unchecked")
    U none = (U) new Object();
    return Collectors.collectingAndThen(
            Collectors.<T, K, U> toMap(keyMapper,
                    valueMapper.andThen(v -> v == null ? none : v)), map -> {
                map.replaceAll((k, v) -> v == none ? null : v);
                return map;
            });
}

We just replace null with some custom object none and do the reverse operation in the finisher. 我们只是将null替换为某些自定义对象none然后在装订器中执行相反的操作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值