Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

NullPointerException in Collectors.toMap with null entry values

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.

like image 362
Jasper Avatar asked Jul 08 '14 11:07

Jasper


People also ask

Does collectors toMap allow null values?

If a list contains null values, the toMap() method will throw NullPointerException in Collectors.

How do I fix NullPointerException?

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 could be the reason if you are getting NullPointerException?

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.


2 Answers

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.

like image 148
kajacx Avatar answered Oct 15 '22 02:10

kajacx


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()); } 
like image 42
gontard Avatar answered Oct 15 '22 02:10

gontard