Java 8 Collectors.toMap
löst ein NullPointerException
if aus, wenn einer der Werte 'null' ist. Ich verstehe dieses Verhalten nicht, Maps können problemlos Nullzeiger als Wert enthalten. Gibt es einen guten Grund, warum Werte nicht null sein können Collectors.toMap
?
Gibt es auch eine gute Java 8-Methode, um dies zu beheben, oder sollte ich auf die alte for-Schleife zurückgreifen?
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<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)
Dieses Problem besteht weiterhin in Java 11.
null
war immer ein bisschen problematisch, wie in TreeMap. Vielleicht ein schöner Moment zum AusprobierenOptional<Boolean>
? Andernfalls teilen und Filter verwenden.null
könnte ein Problem für einen Schlüssel sein, aber in diesem Fall ist es der Wert.null
,HashMap
zum Beispiel die man haben kannnull
Schlüssel und eine beliebige Anzahl vonnull
Werten, können Sie versuchen , ein benutzerdefinierte ErstellungCollector
mit ein ,HashMap
anstatt dem Standard eines verwenden.HashMap
- wie in der ersten Zeile von Stacktrace gezeigt. Das Problem ist nicht, dass aMap
keinennull
Wert halten kann, sondern dass das zweite Argument derMap#merge
Funktion nicht null sein kann.Antworten:
Sie können diesen bekannten Fehler in OpenJDK folgendermaßen umgehen:
Es ist nicht so hübsch, aber es funktioniert. Ergebnis:
( Dieses Tutorial hat mir am meisten geholfen.)
quelle
() -> new TreeMap<>(String.CASE_INSENSITIVE_ORDER)
darin, einenString
Schlüssel ohne Berücksichtigung der Groß- und Kleinschreibung zu erstellenTreeMap
.Map<Integer, Boolean> collect = list.stream().collect(HashMap<Integer, Boolean>::new, (m,v)->m.put(v.getId(), v.getAnswer()), HashMap<Integer, Boolean>::putAll);
. Ich hatte:incompatible types: cannot infer type-variable(s) R (argument mismatch; invalid method reference no suitable method found for putAll(java.util.Map<java.lang.Integer,java.lang.Boolean>,java.util.Map<java.lang.Integer,java.lang.Boolean>) method java.util.Map.putAll(java.util.Map) is not applicable (actual and formal argument lists differ in length)
HashMap
und rufen dannputAll()
für jeden einzelnen Eintrag auf. Persönlich würde ich unter bestimmten Umständen eine Nicht-Stream-Lösung wählen oderforEach()
wenn die Eingabe parallel ist.Mit den statischen Methoden von ist dies nicht möglich
Collectors
. Das Javadoc vontoMap
erklärt,toMap
das basiert aufMap.merge
:und der Javadoc von
Map.merge
sagt:Sie können die for-Schleife mithilfe der
forEach
Methode Ihrer Liste vermeiden .aber es ist nicht wirklich einfach als der alte Weg:
quelle
Map.merge
. Dieses IMHO ist ein Fehler in der Implementierung, der einen völlig akzeptablen Anwendungsfall einschränkt, der übersehen wurde. Die überladenen Methoden vontoMap
do geben die Verwendung von an,Map.merge
aber nicht die, die das OP verwendet.Ich habe eine geschrieben,
Collector
die im Gegensatz zur Standard-Java nicht abstürzt, wenn Sienull
Werte haben:Ersetzen Sie einfach Ihren
Collectors.toMap()
Anruf durch einen Aufruf dieser Funktion, und das Problem wird behoben.quelle
null
Werten und das VerwendenputIfAbsent
spielen nicht gut zusammen. Es erkennt keine doppelten Schlüssel, wenn sienull
…Ja, eine späte Antwort von mir, aber ich denke, es kann hilfreich sein zu verstehen, was unter der Haube passiert, falls jemand eine andere
Collector
Logik codieren möchte.Ich habe versucht, das Problem zu lösen, indem ich einen nativeren und direkteren Ansatz codiert habe. Ich denke es ist so direkt wie möglich:
Und die Tests mit JUnit und assertj:
Und wie benutzt du es? Verwenden Sie es einfach, anstatt
toMap()
wie in den Tests gezeigt. Dadurch sieht der aufrufende Code so sauber wie möglich aus.BEARBEITEN:
Holgers Idee unten implementiert, eine Testmethode hinzugefügt
quelle
(map1, map2) -> { int total = map1.size() + map2.size(); map1.putAll(map2); if(map1.size() < total.size()) throw new IllegalStateException("Duplicate key(s)"); return map1; }
accumulator()
eigentlich prüft. Vielleicht sollte ich einmal einige parallele Streams machen :)Hier ist ein etwas einfacherer Sammler als von @EmmanuelTouzery vorgeschlagen. Verwenden Sie es, wenn Sie möchten:
Wir ersetzen nur durch
null
ein benutzerdefiniertes Objektnone
und führen den umgekehrten Vorgang im Finisher durch.quelle
Wenn der Wert ein String ist, funktioniert dies möglicherweise:
map.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(), e -> Optional.ofNullable(e.getValue()).orElse("")))
quelle
Laut der
Stacktrace
Wann heißt das
map.merge
Es wird
null
als erstes eine Überprüfung durchführenIch benutze Java 8 nicht so oft, daher weiß ich nicht, ob es einen besseren Weg gibt, es zu reparieren, aber es ist ein bisschen schwierig, es zu reparieren.
Du könntest es tun:
Verwenden Sie den Filter, um alle NULL-Werte zu filtern. Wenn Sie im Javascript-Code prüfen, ob der Server keine Antwort auf diese ID gesendet hat, bedeutet dies, dass er nicht darauf geantwortet hat.
Etwas wie das:
Oder verwenden Sie peek, mit dem das Stream-Element für Element geändert wird. Mit peek können Sie die Antwort in etwas ändern, das für die Karte akzeptabler ist, aber es bedeutet, dass Sie Ihre Logik ein wenig bearbeiten.
Klingt so, als ob Sie das aktuelle Design beibehalten möchten, das Sie vermeiden sollten
Collectors.toMap
quelle
Ich habe die Implementierung von Emmanuel Touzery leicht modifiziert .
Diese Version;
Unit Tests:
quelle
Es tut mir leid, eine alte Frage erneut zu öffnen, aber da sie kürzlich bearbeitet wurde und besagt, dass das "Problem" immer noch in Java 11 besteht, wollte ich darauf hinweisen:
gibt Ihnen die Nullzeigerausnahme, da die Zuordnung keinen Nullwert zulässt. Dies ist sinnvoll
k
, da der zurückgegebene Wert bereits vorhanden ist, wenn Sie in einer Karte nach dem Schlüssel suchen und dieser nicht vorhanden istnull
(siehe javadoc). Wenn Sie alsok
den Wertnull
eingeben könnten, würde die Karte so aussehen, als würde sie sich merkwürdig verhalten.Wie jemand in den Kommentaren sagte, ist es ziemlich einfach, dies durch Filtern zu lösen:
Auf diese Weise werden keine
null
Werte in die Karte eingefügt, und STILL erhalten Sienull
als "Wert", wenn Sie nach einer ID suchen, die keine Antwort in der Karte hat.Ich hoffe das macht für alle Sinn.
quelle
answerMap.put(4, null);
ohne Probleme auskommen. Sie haben Recht, dass Sie mit Ihrer vorgeschlagenen Lösung das gleiche Ergebnis für anserMap.get () erhalten, wenn es nicht vorhanden ist, als würde der Wert als null eingefügt. Wenn Sie jedoch alle Einträge der Karte durchlaufen, gibt es offensichtlich einen Unterschied.quelle
Beibehaltung aller Fragen-IDs mit kleinen Änderungen
quelle
NullPointerException ist bei weitem die am häufigsten auftretende Ausnahme (zumindest in meinem Fall). Um dies zu vermeiden, gehe ich in die Defensive und füge eine Reihe von Nullprüfungen hinzu. Am Ende habe ich aufgeblähten und hässlichen Code. Java 8 führt Optional ein, um Nullreferenzen zu verarbeiten, damit Sie nullbare und nicht nullbare Werte definieren können.
Das heißt, ich würde alle nullbaren Referenzen in den optionalen Container einschließen. Wir sollten auch nicht die Abwärtskompatibilität brechen. Hier ist der Code.
quelle
Collectors.toMap()
nicht null Werten