Pourquoi Map.of n’autorise-t-il pas les clés et les valeurs null?

Avec Java 9, de nouvelles méthodes d’usine ont été introduites pour les interfaces List , Set et Map . Ces méthodes permettent d’instancier rapidement un object Map avec des valeurs sur une seule ligne. Maintenant, si nous considérons:

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

Ce qui précède est autorisé sans aucune exception si nous le faisons:

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

Il jette:

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

Je ne suis pas en mesure d’obtenir, pourquoi null n’est pas autorisé dans le second cas.

Je sais que HashMap peut prendre null en tant que clé et valeur, mais pourquoi est-ce restreint dans le cas de Map.of?

La même chose se produit dans le cas de java.util.Set.of("v1", "v2", null) et java.util.List.of("v1", "v2", null) .

Comme d’autres l’ont fait remarquer, le contrat Map permet de rejeter les nulls …

Certaines implémentations interdisent null clés et les valeurs null […]. ClassCastException d’insérer une clé ou une valeur inéligible, une exception non contrôlée est ClassCastException , généralement NullPointerException ou ClassCastException .

… et les usines de collecte (pas seulement sur les cartes) en profitent .

Ils interdisent null clés et les valeurs null . Les tentatives de les créer avec null clés ou des valeurs null entraînent une NullPointerException .

Mais pourquoi?

Autoriser null valeurs null dans les collections est désormais considéré comme une erreur de conception. Cela a une variété de raisons. Un bon exemple est la facilité d’utilisation, où le principal fauteur de troubles est Map::get . Si elle renvoie null , il est difficile de savoir si la clé est manquante ou si la valeur était null . De manière générale, les collections garanties sans valeur sont plus faciles à utiliser. Du sharepoint vue de la mise en œuvre, ils nécessitent également un boîtier moins spécial, ce qui rend le code plus facile à maintenir et plus performant.

Vous pouvez écouter Stuart Marks l’expliquer dans cette présentation mais JEP 269 (celui qui a introduit les méthodes d’usine) le résume également:

Les éléments nuls, les clés et les valeurs seront interdits. (Aucune collection récemment introduite ne prend en charge les valeurs NULL.) En outre, l’interdiction des valeurs NULL offre des possibilités de représentation interne plus compacte, un access plus rapide et moins de cas spéciaux.

Comme HashMap était déjà dans la nature quand cela a été lentement découvert, il était trop tard pour le changer sans casser le code existant, mais la plupart des implémentations récentes de ces interfaces (par exemple ConcurrentHashMap ) n’autorisent plus pas exception.

(Je pensais qu’une autre raison était que l’utilisation explicite de valeurs null était considérée comme une erreur d’implémentation probable, mais je me suis trompé. C’était sur le sharepoint dupliquer les clés, qui sont également illégales.)

Donc, le rejet de null avait une raison technique, mais il a également été fait pour améliorer la robustesse du code en utilisant les collections créées.

Bien que HashMap autorise les valeurs NULL, Map.of n’utilise pas HashMap et lève une exception si l’une est utilisée comme clé ou valeur, comme HashMap dans la documentation :

Les méthodes d’usine statique Map.of () et Map.ofEnsortinges () offrent un moyen pratique de créer des cartes immuables. Les instances de carte créées par ces méthodes présentent les caractéristiques suivantes:

  • Ils interdisent les clés et les valeurs null. Les tentatives de les créer avec des clés ou des valeurs null entraînent une exception NullPointerException.

Exactement – un HashMap est autorisé à stocker null, pas la Map renvoyée par les méthodes de fabrique statique. Toutes les cartes ne sont pas identiques.

En général, pour autant que je sache, il a une erreur en premier lieu pour autoriser les HashMap dans les HashMap tant que clés, les nouvelles collections interdisent cette possibilité pour commencer.

Pensez au cas où vous avez une entrée dans votre HashMap qui a une certaine clé et valeur == null. Vous obtenez, il retourne null . Qu’est-ce que le moyen? Il a un mapping de null ou ce n’est pas présent?

Même chose pour une Key – le hashcode d’une telle clé nulle doit être traité spécialement tout le temps. Interdire les valeurs nulles pour commencer – simplifiez les choses.

La principale différence est la suivante: lorsque vous construisez votre propre carte de la manière “option 1” … alors vous dites implicitement : “Je veux avoir toute liberté dans ce que je fais”.

Ainsi, lorsque vous décidez que votre carte doit avoir une clé ou une valeur nulle (peut-être pour les raisons énumérées ici ), vous êtes libre de le faire.

Mais “option 2” concerne une chose pratique – probablement destinée à être utilisée pour les constantes. Et les personnes derrière Java ont simplement décidé: “lorsque vous utilisez ces méthodes pratiques, la carte résultante sera exempte de nullité”.

Autoriser des valeurs nulles signifie que

  if (map.contains(key)) 

n’est pas la même que

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

ce qui peut être un problème parfois. Ou plus précisément: il faut se souvenir de l’object de cette carte.

Et juste un indice anecdotique expliquant pourquoi cela semble être une approche raisonnable: notre équipe a mis en place des méthodes de commodité similaires. Et devinez quoi: sans rien savoir des plans concernant les futures méthodes standard Java – nos méthodes font exactement la même chose: elles renvoient une copie immuable des données entrantes; et ils jettent quand vous fournissez des éléments nuls. Nous sums même si ssortingcts que lorsque vous passez des listes / cartes vides / … nous nous plaignons également.

Autoriser les valeurs NULL dans les cartes a été une erreur. Nous pouvons le voir maintenant , mais je suppose que ce n’était pas clair quand HashMap été introduit. NullpointerException est le bogue le plus courant du code de production.

Je dirais que le JDK aide les développeurs à lutter contre le fléau des NPE. Quelques exemples:

  • Introduction de Optional
  • Collectors.toMap(keyMapper, valueMapper) n’autorise ni la fonction keyMapper ni la fonction valueMapper à renvoyer une valeur null
  • Stream.findFirst() et Stream.findAny() lancent NPE si la valeur trouvée est null

Ainsi, refuser la valeur null dans les nouvelles collections immuables JDK9 (et la carte) ne va que dans le même sens. Et nous devrions tous être remerciés!

Les cartes n’autorisent pas toutes les clés comme clé

La raison mentionnée dans les docs of Map .

Certaines implémentations de cartes ont des ressortingctions sur les clés et les valeurs qu’elles peuvent contenir. Par exemple, certaines implémentations interdisent les clés et les valeurs null et certaines ont des ressortingctions sur les types de leurs clés. Si vous tentez d’insérer une clé ou une valeur inéligible, une exception non contrôlée est générée, généralement NullPointerException ou ClassCastException.

La documentation ne dit pas pourquoi null n’est pas autorisé:

Ils interdisent null clés et les valeurs null . Les tentatives de les créer avec null clés ou des valeurs null entraînent une NullPointerException .

À mon avis, les méthodes de fabrique statique Map.of() et Map.ofEnsortinges() , qui vont produire une constante, sont principalement formées par un développeur du type compilation. Alors, quelle est la raison de garder un null comme clé ou valeur?

Alors que la Map#put est utilisée en remplissant une carte à l’exécution où null clés / valeurs null pourraient apparaître.