Collectors.toMap
throws a NullPointerException
if one of the values is null
. 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
?
Also, is there a nice Java 8 way of fixing this, or should I revert to plain old for loop?
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.
If a list contains null values, the toMap() method will throw NullPointerException in Collectors.
In Java, the java. lang. NullPointerException is thrown when a reference variable is accessed (or de-referenced) and is not pointing to any object. This error can be resolved by using a try-catch block or an if-else condition to check if a reference variable is null before dereferencing it.
What Causes NullPointerException. The NullPointerException occurs due to a situation in application code where an uninitialized object is attempted to be accessed or modified. Essentially, this means the object reference does not point anywhere and has a null value.
You can work around this known bug in OpenJDK with this:
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.)
EDIT:
Unlike Collectors.toMap
, this will silently replace values if you have the same key multiple times, as @mmdemirbas pointed out in the comments. If you don't want this, look at the link in the comment.
It is not possible with the static methods of Collectors
. The javadoc of toMap
explains that toMap
is based on 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)}
and the javadoc of Map.merge
says:
@throws NullPointerException if the specified key is null and this map does not support null keys or the value or remappingFunction is null
You can avoid the for loop by using the forEach
method of your list.
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()); }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With