Чому Map.of не дозволяє нульові ключі та значення?


78

З Java 9, нові фабричні методи були введені для List, Setі Mapінтерфейсів. Ці методи дозволяють швидко створити екземпляр об'єкта Map зі значеннями в одному рядку. Тепер, якщо врахувати:

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

Вищезазначене дозволяється без будь-якого винятку, тоді як, якщо ми робимо:

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

Він кидає:

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

Я не можу зрозуміти, чому в другому випадку нуль не допускається .

Я знаю, що HashMap може приймати null як ключ, так і як значення, але чому це було обмежено у випадку Map.of?

Те саме відбувається у випадку з java.util.Set.of("v1", "v2", null)і java.util.List.of("v1", "v2", null).


22
Питання неправильне. Має бути: чому HashMap дозволяє нулі?
Жека Козлов

2
@ZhekaKozlov Коли ми можемо обернути карту за допомогою HashMap, вона повинна дозволяти функції хеш-карти, або Hashmap не повинна мати можливості обертати її. Оскільки "з" fn пов'язане з незмінністю, воно повинно бути там у ConcurrentMap, яке може бути розширено ConcurrentHashmap або близько того.
привіт. Нітиш

1
@ hi.nitish: Можливо, пора прийняти відповідь. 😉
Ніколай Парлог,

1
заборона нульових ключів, які я можу зрозуміти, але нульові значення мені здаються життєздатними
mjj1409

Відповіді:


80

Як інші відзначили, контракт дозволяє відкинути анулює ...Map

[S] ome реалізації забороняють nullключі та значення [...]. Спроба вставити невідповідний ключ або значення викликає неперевірений виняток, як правило, NullPointerExceptionабо ClassCastException.

... і колекторські фабрики (не лише на картах) цим користуються .

Вони забороняють nullключі та значення. Спроби створити їх за допомогою nullключів або значень призводять до NullPointerException.

Але чому?

Дозвіл nullу колекціях на сьогодні розглядається як помилка дизайну. Це має безліч причин. Хороший варіант - це зручність використання, де найвідоміший виробник проблем Map::get. Якщо він повертається null, незрозуміло, чи відсутній ключ, чи значення було null. Взагалі кажучи, колекції, які гарантовано nullбезкоштовні, простіші у використанні. З точки зору реалізації, вони також вимагають менш спеціального корпусу, що робить код простішим у обслуговуванні та більш продуктивним.

Ви можете послухати, як Стюарт Маркс пояснює це в цій промові, але JEP 269 (той, що запровадив заводські методи) також резюмує це:

Нульові елементи, ключі та значення заборонені. (Жодні нещодавно представлені колекції не підтримують нульових значень.) Крім того, заборона нульових значень пропонує можливості для більш компактного внутрішнього представлення, швидшого доступу та меншої кількості особливих випадків.

Оскільки HashMapце вже було в дикій природі, коли це повільно виявили, було занадто пізно змінювати його, не порушуючи існуючий код, але найновіші реалізації цих інтерфейсів (наприклад ConcurrentHashMap) більше не дозволяють, nullі нові колекції для заводських методів не є винятком.

(Я думав, ще однією причиною було те, що явно використовували null значень розглядалося як ймовірна помилка реалізації, але я це помилився. Це збиралося дублювати ключі, які також є незаконними.)

Отже, заборона nullмала деякі технічні причини, але це також було зроблено для підвищення надійності коду за допомогою створених колекцій.


5
ну ... я можу подумати про випадок: коли у вашій HashMap є запис, який має певний ключ і значення == null. Ви робите get, це повертає null. Що означає? Він має відображення нуля або його немає? Крім того, обробка нульових клавіш завжди відрізняється, ви не можете обчислити хеш-код ... заборона його для початку полегшує роботу
Євген

2
Я б не розглядав це як технічну, а як причину юзабіліті. Все-таки хороший момент, тому я його включив.
Nicolai Parlog

6
JEP 269 має таке для null : "Нульові елементи, ключі та значення будуть заборонені. (Немає нещодавно представлених колекцій, які підтримують null.) Крім того, заборона null пропонує можливості для більш компактного внутрішнього представлення, швидшого доступу та меншої кількості особливих випадків . "
Stefan Zobel

2
Єдиною причиною юзабіліті було б getповернення null. Але замість розширення Mapновим методом, що повертає, було введено Optionalце дрібне обмеження, яке абсолютно нічого не вирішує, оскільки будь-який код, що має справу з Mapдосі, не може покладатися на те, що не nullповернеться. Тим часом люди, які хочуть передавати константу Mapкудись, що допустить nullзначення з поважної причини, не можуть скористатися цією дуже зручною функцією.
john16384

2
OptionalЄдина місія @ john16384- полягає у тому, щоб уникнути явної nullобробки, і якби ваша пропозиція була реалізована на практиці, кожен виклик Optionalметоду -returning повинен був би зробити nullперевірку і все одно мати справу з порожнім справою. Це недоречна пропозиція. Що стосується несподіванок під час виконання, ви знаєте, що Mapреалізації можуть безкоштовно кидати NPE, якщо ви намагаєтесь putотримати nullключ або значення, чи не так? ( ConcurrentHashMapробить це.) Як ти з цим справлявся до цього часу?
Ніколай Парлог,

13

Точно - це HashMapдозволено зберігати нуль, НЕMap повертається статичних фабричних методів. Не всі карти однакові.

Як правило, наскільки я знаю, в першу чергу є помилка, дозволяючи нулі в HashMapклавішах as, новіші колекції забороняють цю можливість починати з.

Згадайте випадок, коли у вас є запис, HashMapякий має певний ключ і значення == null. Ви все-таки отримуєте, воно повертається null. Що означає? Він має відображення nullабо його немає?

Те саме стосується і Key- хеш-код з такого нульового ключа повинен постійно оброблятися спеціально. Заборона нулів для початку - спростіть це.


Так, це може розглядатися як недолік, що залишився у реалізації, але так само як і багато інших речей, які не відповідають об'єктно-орієнтованим принципам. Однак розробники часто використовують нульове значення в хеш-капі, і вони перевіряють, чи є справа, щоб ініціалізувати багато інших речей у бізнес-логіці.
привіт. Нітиш

5
Мені подобається ваша відповідь - як це стосується того, чому саме речі?
GhostCat

9

Незважаючи на те, що HashMap дозволяє нульові значення, Map.ofне використовує a HashMapі видає виняток, якщо один використовується як ключ або значення, як задокументовано :

Статичні заводські методи Map.of () та Map.ofEntries () забезпечують зручний спосіб створення незмінних карт. Екземпляри Map, створені цими методами, мають такі характеристики:

  • ...

  • Вони забороняють нульові ключі та значення. Спроби створити їх за допомогою нульових ключів або значень призводять до NullPointerException.


Але чому заборонено, коли це дозволено в HashMap?
привіт. Нітиш

Не думаю, що хтось, крім розробників Java, може надійно відповісти, чому .
1615903

5
Тому що вони зрозуміли, що було помилкою дозволяти нульові значення в HashMap.
TimK

7

Дозвіл нулів на картах був помилкою. Ми можемо це побачити зараз , але, мабуть, не було зрозуміло, коли HashMapбуло представлено. NullpointerException- це найпоширеніша помилка, яка зустрічається у виробничому коді.

Я б сказав, що JDK йде у напрямку допомоги розробникам у боротьбі з чумою NPE. Кілька прикладів:

  • Введення Optional
  • Collectors.toMap(keyMapper, valueMapper)не дозволяє ні функції, keyMapperні valueMapperфункції повернути nullзначення
  • Stream.findFirst()і Stream.findAny()киньте NPE, якщо знайдене значенняnull

Отже, я думаю, що заборона nullв нових незмінних колекціях (і картах) JDK9 йде в тому ж напрямку.


5

Основна різниця полягає в тому, що коли ви створюєте власну карту "варіантом 1" ... тоді ви неявно говорите: "Я хочу мати повну свободу в тому, що я роблю".

Отже, коли ви вирішите, що ваша карта повинна мати нульовий ключ або значення (можливо, з причин, перелічених тут ), ви можете це зробити.

Але "варіант 2" стосується зручності - можливо, призначеної для використання для констант. І люди, що стоять за Java, просто вирішили: "коли ви використовуєте ці зручні методи, то отримана карта буде нульовою".

Дозвіл на нульові значення означає, що

 if (map.contains(key)) 

не те саме, що

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

що іноді може бути проблемою. Або точніше: це щось пам’ятати, маючи справу з тим самим об’єктом карти.

І лише анекдотичний натяк, чому це здається розумним підходом: наша команда сама застосувала подібні зручні методи. І вгадайте, що: нічого не знаючи про плани щодо майбутніх стандартних методів Java - наші методи роблять точно те саме: вони повертають незмінну копію вхідних даних; і вони повертають, коли ви надаєте нульові елементи. Ми навіть настільки суворі, що коли ви передаєте порожні списки / карти / ... ми також скаржимося.


Чому map.put (key, null) спочатку повертає true для map.contains (key)?
Педро Борхес,

3

У документації не вказано, чому null заборонено:

Вони забороняють nullключі та значення. Спроби створити їх за допомогою nullключів або значень призводять до NullPointerException.

На мій погляд, методи Map.of()and Map.ofEntries()static factory, які збираються створювати константу, в основному формуються розробником типу компіляції. Тоді, в чому причина зберігати a nullяк ключ чи значення?

Тоді Map#putяк зазвичай використовується, заповнюючи карту під час виконання, де nullможуть мати місце ключі / значення.


4
@Eugene, якби кожен читав документацію (і розумів її), на цьому веб-сайті буде лише частина запитань, які вона має зараз.
user85421

@CarlosHeuberger Можливо, існує тенденція до позбавлення від нуля в майбутніх jdks і, отже, примітивах. Я читаю Map.of () завжди повертаю незмінювану карту, так чи має значення null щось спільне з незмінністю тут?
привіт. Нітиш

3
@ hi.nitish насправді це дещо пов'язано з незмінністю: Map.of()повертає екземпляр ImmutableCollections.MapNякого використовує таблицю, яка nullповинна вказувати її кінець. Документ цього класу: "Існує єдиний масив" таблиця ", який * містить ключі та значення з чергуванням ... Розмір таблиці повинен бути парним. Він також повинен бути суворо більшим за розмір (кількість пар ключ-значення, що міститься в map), щоб завжди був принаймні один нульовий ключ "Чому це робиться таким чином, замість того, щоб просто використовувати int для утримання розміру, я не знаю; можливо, хешування ... (код трохи страшний )
user85421

1
Те, що ви не бачите причини, не означає, що немає дійсних випадків використання. Пара Mapз ключем / значенням, призначена для полів у базі даних, наприклад, отримує переваги від нульових значень. Я думаю, це повернуто до NULLзаповнювача для тих. Причина IMHO - дріб'язкова або просто надмірна реакція на антинульовий менталітет.
john16384

3

На мій погляд, ненульованість має сенс для ключів, але не для значень.

  1. MapІнтерфейс стає слабким контракт, ви не можете довіряти його поведінку , не дивлячись на реалізацію, і що якщо у вас є доступ , щоб побачити його. Java11 Map.of()не допускає нульових значень, поки HashMapце робить, але обидва вони реалізують один і той же Mapконтракт - як це?
  2. Наявність нульового значення або його відсутність взагалі не могло, і, можливо, не повинно розглядатися як окремий сценарій. Коли ви отримуєте значення ключа і отримуєте значення null, кого цікавить, було воно явно введено туди чи ні? просто немає значення для наданого ключа, карта не містить такого простого ключа. Null - це null, немає nullnull або null ^ 2
  3. Існуючі бібліотеки широко використовують карту як засіб передачі їм властивостей, багато з них необов’язкові, це робить Map.of()марною конструкцію таких структур.
  4. Kotlin забезпечує дозвіл на онулювання під час компіляції та дозволяє створювати карти з нульовими значеннями без відомих проблем.
  5. Причина недопущення нульових значень, мабуть, пов’язана з деталізацією реалізації та зручністю творця.

2

Не всі Карти дозволяють нуль як ключ

Причина, згадана в docs of Map.

Деякі реалізації карт мають обмеження на ключі та значення, які вони можуть містити. Наприклад, деякі реалізації забороняють нульові ключі та значення, а деякі мають обмеження на типи своїх ключів. Спроба вставити невідповідний ключ або значення викликає неперевірений виняток, як правило, NullPointerException або ClassCastException.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.