Warum erlaubt Map.of keine Nullschlüssel und -werte?

76

Mit Java 9 wurden neue Factory-Methoden für das List, Setund eingeführtMap Schnittstellen. Mit diesen Methoden kann ein Map-Objekt schnell mit Werten in einer Zeile instanziiert werden. Nun, wenn wir überlegen:

Map<Integer, String> map1 = new HashMap<Integer, String>(Map.of(1, "value1", 2, "value2", 3, "value3"));
map1.put(4, null);

Dies ist ausnahmslos zulässig, wenn wir Folgendes tun:

Map<Integer, String> map2 = Map.of(1, "value1", 2, "value2", 3, "value3", 4, null );

Es wirft:

Exception in thread "main" java.lang.NullPointerException
    at java.base/java.util.Objects.requireNonNull(Objects.java:221)
..

Ich kann nicht bekommen, verstehen warum null im zweiten Fall nicht erlaubt ist.

Ich weiß, dass HashMap sowohl Null als Schlüssel als auch Wert annehmen kann, aber warum wurde dies im Fall von Map.of eingeschränkt?

Das gleiche passiert im Fall von java.util.Set.of("v1", "v2", null)und java.util.List.of("v1", "v2", null).

hi.nitish
quelle
21
Die Frage ist falsch. Sollte sein: Warum erlaubt HashMap Nullen?
ZhekaKozlov
2
@ZhekaKozlov Wenn wir eine Map mit HashMap umschließen können, sollten die Funktionen von Hashmap zulässig sein, oder Hashmap sollte nicht umbrochen werden dürfen. Da "of" fn mit Unveränderlichkeit zusammenhängt, sollte es in der ConcurrentMap vorhanden sein, die möglicherweise durch ConcurrentHashmap oder so erweitert wird.
Hi.nitish
@ hi.nitish: Es kann Zeit sein, eine Antwort zu akzeptieren. 😉
Nicolai Parlog
Verbot von
Nullschlüsseln

Antworten:

77

Wie andere betonten, erlaubt der MapVertrag die Ablehnung von Nullen ...

Einige Implementierungen verbieten nullSchlüssel und Werte [...]. Der Versuch, einen nicht berechtigten Schlüssel oder Wert einzufügen, löst normalerweise NullPointerExceptionoder eine ungeprüfte Ausnahme aus ClassCastException.

... und die Sammelfabriken (nicht nur auf Karten) machen davon Gebrauch .

Sie verbieten nullSchlüssel und Werte. Versuche, sie mit nullSchlüsseln oder Werten zu erstellen, führen dazu NullPointerException.

Aber wieso?

Das Zulassen nullvon Sammlungen wird mittlerweile als Entwurfsfehler angesehen. Dies hat verschiedene Gründe. Eine gute ist die Benutzerfreundlichkeit, bei der der bekannteste Problemlöser ist Map::get. Wenn es zurückkehrt null, ist unklar, ob der Schlüssel fehlt oder der Wert war null. Im Allgemeinen sind Sammlungen, die garantiert nullkostenlos sind, einfacher zu verwenden. In Bezug auf die Implementierung erfordern sie auch weniger spezielles Gehäuse, wodurch der Code einfacher zu warten und leistungsfähiger ist.

Sie können Stuart Marks zuhören, der es in diesem Vortrag erklärt, aber JEP 269 (derjenige, der die Factory-Methoden eingeführt hat) fasst es auch zusammen:

Nullelemente, Schlüssel und Werte werden nicht zugelassen. (Keine kürzlich eingeführten Sammlungen haben Nullen unterstützt.) Darüber hinaus bietet das Verbot von Nullen die Möglichkeit einer kompakteren internen Darstellung, eines schnelleren Zugriffs und weniger Sonderfällen.

Da dies HashMapbereits in freier Wildbahn war, als dies langsam entdeckt wurde, war es zu spät, es zu ändern, ohne den vorhandenen Code zu beschädigen, aber die neuesten Implementierungen dieser Schnittstellen (z. B. ConcurrentHashMap) lassen dies nicht nullmehr zu und die neuen Sammlungen für die Factory-Methoden sind keine Ausnahme.

(Ich dachte, ein anderer Grund war die explizite Verwendung null Werten als wahrscheinlicher Implementierungsfehler angesehen wurde, aber ich habe das falsch verstanden. Das war dabei, Schlüssel zu duplizieren, die ebenfalls illegal sind.)

Das Nichtzulassen nullhatte also einen technischen Grund, wurde aber auch durchgeführt, um die Robustheit des Codes mithilfe der erstellten Sammlungen zu verbessern.

Nicolai Parlog
quelle
5
Nun ... ich kann mir einen Fall vorstellen: Wenn Sie einen Eintrag in Ihrer HashMap haben, der einen bestimmten Schlüssel und Wert == null hat. Wenn Sie dies tun get, wird null zurückgegeben. Was bedeutet das? Es hat eine Zuordnung von null oder ist es nicht vorhanden? Auch der Umgang mit Nullschlüsseln ist immer anders, Sie können keinen Hashcode berechnen ... Wenn Sie ihn zunächst nicht zulassen, wird die Arbeit einfacher
Eugene
2
Ich würde das nicht als technischen, sondern als Usability-Grund betrachten. Trotzdem ein guter Punkt, also habe ich ihn aufgenommen.
Nicolai Parlog
6
In JEP 269 ist dies null : "Nullelemente, Schlüssel und Werte werden nicht zugelassen. (Keine kürzlich eingeführten Sammlungen unterstützen Nullen.) Darüber hinaus bietet das Verbot von Nullen die Möglichkeit einer kompakteren internen Darstellung, eines schnelleren Zugriffs und weniger Sonderfällen . "
Stefan Zobel
2
Der einzige Grund für die Benutzerfreundlichkeit wäre die getRückkehr null. Aber anstatt Mapmit einer neuen Methode zu erweitern, die eine zurückgibt, wurde Optionaldiese geringfügige Einschränkung eingeführt, die absolut nichts löst, da sich jeder Code, der sich mit einem MapStill befasst, nicht darauf verlassen kann, nicht nullzurück zu kommen. In der Zwischenzeit können Personen, die Mapirgendwo eine Konstante übergeben möchten, die nullWerte mit gutem Grund zulässt , diese sehr praktische Funktion nicht nutzen .
john16384
2
@ john16384 Optional-s einzige Aufgabe ist es, eine explizite nullBehandlung zu vermeiden. Wenn Ihr Vorschlag in die Praxis umgesetzt würde, müsste jeder Aufruf einer OptionalRückgabemethode eine nullÜberprüfung durchführen und sich trotzdem mit dem leeren Fall befassen. Das ist ein absurder Vorschlag. In Bezug auf Laufzeitüberraschungen wissen Sie, dass MapImplementierungen NPEs frei auslösen können, wenn Sie versuchen, puteinen nullSchlüssel oder Wert zu ermitteln, oder? ( ConcurrentHashMaptut das.) Wie sind Sie bisher damit umgegangen?
Nicolai Parlog
13

Genau - a HashMapdarf null speichern, nicht die Mapvon den statischen Factory-Methoden zurückgegebenen. Nicht alle Karten sind gleich.

Soweit ich weiß, ist es in erster Linie ein Fehler, Nullen in den HashMapas-Schlüsseln zuzulassen. Neuere Sammlungen verbieten diese Möglichkeit zunächst.

Denken Sie an den Fall, wenn Sie einen Eintrag in Ihrem haben HashMap, der einen bestimmten Schlüssel und Wert == null hat. Sie bekommen, es kehrt zurück null. Was bedeutet das? Es hat eine Zuordnung von nulloder ist es nicht vorhanden?

Gleiches gilt für einen Key- Hashcode von einem solchen Nullschlüssel, der ständig speziell behandelt werden muss. Zu Beginn das Verbot von Nullen - machen Sie dies einfacher.

Eugene
quelle
Ja, es kann als Fehler in der Implementierung angesehen werden, aber auch viele andere Dinge, die nicht den objektorientierten Prinzipien entsprechen. Die Entwickler verwenden die Null jedoch häufig in der Hashmap und überprüfen sie, ob der Fall vorliegt, um viele andere Dinge in der Geschäftslogik zu initialisieren.
Hi.nitish
5
Ich mag deine Antwort - wenn es um das Warum geht!
GhostCat
9

Während HashMap Nullwerte zulässt, Map.ofverwendet a kein HashMapund löst eine Ausnahme aus, wenn einer entweder als Schlüssel oder als Wert verwendet wird, wie dokumentiert :

Die statischen Factory-Methoden Map.of () und Map.ofEntries () bieten eine bequeme Möglichkeit, unveränderliche Karten zu erstellen. Die mit diesen Methoden erstellten Map-Instanzen weisen die folgenden Merkmale auf:

  • ...

  • Sie verbieten Nullschlüssel und Werte. Versuche, sie mit Nullschlüsseln oder -werten zu erstellen, führen zu NullPointerException.

1615903
quelle
Aber warum nicht erlaubt, wenn es in HashMap erlaubt ist?
Hi.nitish
Ich glaube nicht, dass jemand außer Java-Entwicklern zuverlässig antworten kann, warum .
1615903
5
Weil sie erkannten, dass es ein Fehler war, Nullen in HashMap zuzulassen.
TimK
7

Das Zulassen von Nullen in Karten war ein Fehler. Wir können es jetzt sehen , aber ich denke, es war nicht klar, wann HashMapes eingeführt wurde. NullpointerExceptionist der häufigste Fehler im Produktionscode.

Ich würde sagen, dass das JDK in die Richtung geht, Entwicklern bei der Bekämpfung der NPE-Pest zu helfen. Einige Beispiele:

  • Einführung von Optional
  • Collectors.toMap(keyMapper, valueMapper)erlaubt weder der keyMappernoch der valueMapperFunktion, einen nullWert zurückzugeben
  • Stream.findFirst()und Stream.findAny()wirf NPE, wenn der gefundene Wert istnull

Ich denke also, dass das Nichtzulassen nullder neuen unveränderlichen JDK9-Sammlungen (und Karten) in die gleiche Richtung geht.

fps
quelle
5

Der Hauptunterschied ist: Wenn Sie Ihre eigene Karte auf die "Option 1" Weise erstellen ... dann sagen Sie implizit : "Ich möchte volle Freiheit in dem haben, was ich tue."

Wenn Sie also entscheiden, dass Ihre Karte einen Nullschlüssel oder -wert haben soll (möglicherweise aus den hier aufgeführten Gründen ), können Sie dies tun.

Aber „Option 2“ geht es um eine Bequemlichkeit Sache - wahrscheinlich gedacht für Konstanten verwendet werden. Und die Leute hinter Java haben einfach entschieden: "Wenn Sie diese praktischen Methoden verwenden, ist die resultierende Karte nullfrei."

Unter Berücksichtigung von null Werte bedeuten , dass

 if (map.contains(key)) 

ist nicht dasselbe wie

 if (map.get(key) != null)

Das könnte manchmal ein Problem sein. Genauer gesagt: Es ist etwas, an das man sich erinnern muss, wenn man sich mit genau diesem Kartenobjekt befasst.

Und nur ein anekdotischer Hinweis, warum dies ein vernünftiger Ansatz zu sein scheint: Unser Team hat selbst ähnliche Convenience-Methoden implementiert. Und raten Sie mal: Ohne etwas über Pläne für zukünftige Java-Standardmethoden zu wissen, machen unsere Methoden genau dasselbe: Sie geben eine unveränderliche Kopie der eingehenden Daten zurück. und sie werden angezeigt, wenn Sie Nullelemente bereitstellen. Wir sind sogar so streng, dass wir uns auch beschweren , wenn Sie leere Listen / Karten / ... einreichen.

GhostCat
quelle
Warum sollte map.put (key, null) für map.contains (key) überhaupt true zurückgeben?
Pedro Borges
3

Die Dokumentation sagt nicht, warum null nicht erlaubt ist:

Sie verbieten nullSchlüssel und Werte. Versuche, sie mit nullSchlüsseln oder Werten zu erstellen, führen dazu NullPointerException.

Meiner Meinung nach werden die Map.of()und Map.ofEntries()statischen Factory-Methoden, die eine Konstante erzeugen, meist von einem Entwickler des Kompilierungstyps gebildet. Was ist dann der Grund, a nullals Schlüssel oder Wert beizubehalten?

Während dies Map#putnormalerweise verwendet wird, wird zur Laufzeit eine Karte gefüllt, in der nullSchlüssel / Werte auftreten können.

Andrew Tobilko
quelle
4
@Eugene Wenn jeder die Dokumentation gelesen (und verstanden) hätte, hätte diese Seite nur einen Bruchteil der Fragen, die sie jetzt hat.
user85421
@CarlosHeuberger Es ist möglich, dass es eine Tendenz gibt, die Null in zukünftigen JDKS und damit die Primitiven, die ich denke, loszuwerden. Ich habe gelesen, dass Map.of () immer eine unveränderliche Map zurückgibt. Hat diese Null hier etwas mit Unveränderlichkeit zu tun?
Hi.nitish
3
@ hi.nitish Eigentlich hat es etwas mit der Unveränderlichkeit zu tun: Gibt Map.of()eine Instanz zurück, ImmutableCollections.MapNdie eine Tabelle verwendet, nulldie ihr Ende angeben muss. Dokument dieser Klasse: "Es gibt eine einzelne Array" -Tabelle ", die * verschachtelte Schlüssel und Werte enthält ... Die Tabellengröße muss gerade sein. Sie muss außerdem streng größer sein als die Größe (die Anzahl der Schlüssel-Wert-Paare in die Karte), so dass immer mindestens ein Nullschlüssel vorhanden ist "Warum es so gemacht wird, anstatt nur ein int zu verwenden, um die Größe zu halten, weiß ich nicht; Vielleicht Hashing ... (der Code ist ein bisschen beängstigend )
user85421
1
Nur weil Sie keinen Grund sehen können, heißt das nicht, dass es keine gültigen Anwendungsfälle gibt. A Mapmit Schlüssel / Wert-Paaren, die beispielsweise für Felder in einer Datenbank bestimmt sind, profitiert von Nullwerten. Ich denke, es ist zurück zu einem NULLPlatzhalterobjekt für diese. IMHO ist der Grund kleinlich oder einfach eine Überreaktion auf die Anti-Null-Mentalität.
john16384
3

Meiner Meinung nach ist Nicht-Nullfähigkeit für Schlüssel sinnvoll, aber nicht für Werte.

  1. Die MapSchnittstelle wird zu einem schwachen Vertrag. Sie können dem Verhalten nicht vertrauen, ohne sich die Implementierung anzusehen. Dies setzt voraus, dass Sie Zugriff darauf haben. Java11 Map.of()erlaubt zwar keine Nullwerte HashMap, aber beide implementieren denselben MapVertrag - wie kommt es?
  2. Einen Nullwert oder gar keinen Wert zu haben, konnte und sollte wohl nicht als eigenständiges Szenario betrachtet werden. Wenn Sie den Wert für einen Schlüssel erhalten und null erhalten, wen interessiert es dann, ob er explizit dort abgelegt wurde oder nicht? Es gibt einfach keinen Wert für den angegebenen Schlüssel, die Karte enthält keinen solchen Schlüssel, so einfach ist das. Null ist null, es gibt keine nullnull oder null ^ 2
  3. Bestehende Bibliotheken verwenden die Karte in großem Umfang, um Eigenschaften an sie zu übergeben, von denen viele optional sind. Dies macht sie Map.of()als Konstrukt für solche Strukturen unbrauchbar.
  4. Kotlin erzwingt die Nullfähigkeit beim Kompilieren und ermöglicht Karten mit Nullwerten ohne bekannte Probleme.
  5. Der Grund dafür, dass Nullwerte nicht zugelassen werden, liegt wahrscheinlich nur in den Implementierungsdetails und der Benutzerfreundlichkeit des Erstellers.
Pedro Borges
quelle
2

Nicht alle Karten erlauben Null als Schlüssel

Der in der docs of Map.

Einige Kartenimplementierungen haben Einschränkungen hinsichtlich der Schlüssel und Werte, die sie enthalten können. Beispielsweise verbieten einige Implementierungen Nullschlüssel und -werte, und einige haben Einschränkungen hinsichtlich der Schlüsseltypen. Beim Versuch, einen nicht berechtigten Schlüssel oder Wert einzufügen, wird eine ungeprüfte Ausnahme ausgelöst, normalerweise NullPointerException oder ClassCastException.

Suresh Atta
quelle