566 Stimmen

NullPointerException in Collectors.toMap with null entry values NullPointerException in Collectors.toMap mit null Eintragswerten

Collectors.toMap wirft eine NullPointerException, wenn einer der Werte null ist. Ich verstehe dieses Verhalten nicht, Maps können problemlos Null-Pointer als Wert enthalten. Gibt es einen guten Grund, warum Werte für Collectors.toMap nicht null sein können?

Gibt es auch einen schönen Java 8 Weg, um dieses Problem zu beheben, oder sollte ich wieder zur einfachen alten For-Schleife zurückkehren?

Ein Beispiel für mein 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 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));
    }
}

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)

Dieses Problem besteht auch in Java 11 weiter.

2voto

sigirisetti Punkte 189

Alle Frage-IDs mit kleinen Anpassungen behalten

Map answerMap = 
  answerList.stream()
            .collect(Collectors.toMap(Answer::getId, a -> 
                       Boolean.TRUE.equals(a.getAnswer())));

2voto

Henrik Langli Punkte 21

Zur Vollständigkeit poste ich eine Version von toMapOfNullables mit einem mergeFunction-Param:

public static  Collector> toMapOfNullables(Function keyMapper, Function valueMapper, BinaryOperator mergeFunction) {
    return Collectors.collectingAndThen(Collectors.toList(), list -> {
        Map result = new HashMap<>();
        for(T item : list) {
            K key = keyMapper.apply(item);
            U newValue = valueMapper.apply(item);
            U value = result.containsKey(key) ? mergeFunction.apply(result.get(key), newValue) : newValue;
            result.put(key, value);
        }
        return result;
    });
}

1voto

Simon Punkte 937

Wickeln Sie einfach den nullable-Wert als Optional ein. Ziemlich eleganter Workaround.

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

1voto

M. Justin Punkte 8707

Dieses Verhalten ist im JDK-Bug-System als JDK-8148463: Collectors.toMap scheitert bei Nullwerten dokumentiert.

Ein Kommentar zum Problem vom 10. Oktober 2023 beantwortet Ihre Frage, ob "es einen guten Grund gibt, warum Werte nicht null sein können". Es wird darauf hingewiesen, dass dieses Verhalten absichtlich zur Konsistenz beibehalten wird, aber dass das Verhalten besser dokumentiert werden muss:

Der aktuelle Code von Collectors.toMap() wurde durch JDK-8040892 aktualisiert, obwohl das vorherige Null-Ablehnungsverhalten meiner Meinung nach dasselbe war. Allerdings wurde das Problem mit der Nullbehandlung in seinem Überprüfungsthread diskutiert:
https://mail.openjdk.org/pipermail/core-libs-dev/2014-April/026578.html

Das Fazit war, dass die Null-Feindlichkeit das konsistenteste Verhalten über verschiedene Maps und Collectors hinweg bietet.

Es scheint, als ob die einzige Option darin besteht, die Spezifikation hier zu klären.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X