收藏家。如果其中一个值为空,toMap将抛出NullPointerException异常。我不理解这种行为,映射可以包含空指针作为值而没有任何问题。是否有很好的理由说明collections . tomap的值不能为空?

此外,是否有一个很好的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<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));
    }
}

堆栈跟踪:

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中仍然存在。


当前回答

对于收集器的静态方法,这是不可能的。toMap的javadoc解释了toMap是基于Map.merge的:

@param mergeFunction一个合并函数,用于解决与相同键相关联的值之间的冲突,提供给map# merge(Object, Object, bifuncfunction)}

和Map的javadoc。合并表示:

如果指定的键为空,则@抛出NullPointerException 不支持空键或值或remappingFunction是 零

您可以通过使用列表的forEach方法来避免for循环。

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

但它并不比旧方法简单:

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

其他回答

对于收集器的静态方法,这是不可能的。toMap的javadoc解释了toMap是基于Map.merge的:

@param mergeFunction一个合并函数,用于解决与相同键相关联的值之间的冲突,提供给map# merge(Object, Object, bifuncfunction)}

和Map的javadoc。合并表示:

如果指定的键为空,则@抛出NullPointerException 不支持空键或值或remappingFunction是 零

您可以通过使用列表的forEach方法来避免for循环。

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

但它并不比旧方法简单:

Map<Integer, Boolean> answerMap = new HashMap<>();
for (Answer answer : answerList) {
    answerMap.put(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$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)

什么时候叫做map。merge

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

它首先会做一个空检查

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

我不经常使用Java 8,所以我不知道是否有更好的方法来修复它,但修复它有点困难。

你可以这样做:

使用过滤器过滤所有的NULL值,并在Javascript代码中检查服务器是否没有为这个id发送任何答案,这意味着他没有回复它。

就像这样:

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

或者使用peek,它用来改变流元素的元素。使用peek你可以将答案更改为更适合map的东西,但这意味着编辑你的逻辑。

听起来,如果你想保持当前的设计,你应该避免使用collections . tomap

很抱歉重新打开一个老问题,但由于最近编辑说“问题”仍然存在于Java 11中,我觉得我想指出这一点:

answerList
        .stream()
        .collect(Collectors.toMap(Answer::getId, Answer::getAnswer));

为您提供空指针异常,因为映射不允许将空值作为值。 这是有意义的,因为如果在映射中查找键k而它不存在,则返回值已经为null(参见javadoc)。如果你能把k的值设为null,这个映射看起来就会很奇怪。

就像有人在评论中说的,使用过滤很容易解决这个问题:

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

通过这种方式,映射中不会插入空值,当在映射中寻找没有答案的id时,仍然会得到null作为“值”。

我希望这对每个人都有意义。

你可以在OpenJDK中使用以下方法来解决这个已知的错误:

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

虽然不是很漂亮,但很好用。结果:

1: true
2: true
3: null

(这篇教程对我帮助最大。)

编辑:

不像收藏家。toMap,如果你有相同的键多次,它会默默地替换值,就像@mmdemirbas在评论中指出的那样。如果你不想要这个,看看评论中的链接。

只需将可空值包装为可选值。非常优雅的解决方法。

    listOfValues.stream()
    .collect(Collectors.toMap(e -> e.getKey(), e -> 
    Optional.ofNullable(e.getValue())))