Подтвердить что ты не робот

Почему Map.of не разрешает нулевые ключи и значения?

В Java 9 были введены новые методы factory для интерфейсов 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)
..

Я не могу получить, , почему null не разрешен во втором случае.

Я знаю, что HashMap может принимать значение null как ключ, а также значение, но почему это было ограничено в случае Map.of?

То же самое происходит в случае java.util.Set.of("v1", "v2", null) и java.util.List.of("v1", "v2", null).

4b9b3361

Ответ 1

Как отмечали другие, контракт Map позволяет отклонять нули...

[S] ome реализации запрещают null ключи и значения [...]. Попытка вставить несоответствующий ключ или значение вызывает исключение, исключая, обычно NullPointerException или ClassCastException.

... и сборщики (а не только на картах) используют это.

Они запрещают ключи и значения null. Попытки создать их с помощью null ключей или значений приведут к NullPointerException.

Но почему?

Разрешение null в коллекциях теперь рассматривается как ошибка проектирования. Это имеет множество причин. Хорошей является удобство использования, где наиболее заметным создателем проблем является Map::get. Если он возвращает null, неясно, отсутствует ли ключ или значение null. Вообще говоря, коллекции, гарантированные null бесплатно, проще в использовании. Реализация, они также требуют меньше специальных корпусов, что делает код более удобным для обслуживания и более эффективным.

Вы можете слушать Stuart Marks, объясните это в этом разговоре, но JEP 269 (тот, который ввел методы factory), также суммирует его:

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

Так как HashMap уже вышел в тупик, когда это было медленно обнаружено, было уже слишком поздно менять его, не нарушая существующий код, а самые последние реализации этих интерфейсов (например, ConcurrentHashMap) больше не позволяют null, и новые коллекции для методов factory не являются исключением.

(Я думал, что другая причина заключалась в том, что явно использование значений null рассматривалось как вероятная ошибка реализации, но я ошибался. Это было похоже на дублирование ключей, которые также являются незаконными.)

Так что отказ от null имел некоторые технические причины, но он также был сделан для повышения надежности кода с помощью созданных коллекций.

Ответ 2

Точно - a HashMap разрешено сохранять значение null, а не Map, возвращаемое статическими методами factory. Не все карты одинаковы.

В общем, насколько я знаю, во-первых, ошибка допускает нули в ключах HashMap, но новые коллекции запрещают эту возможность.

Подумайте о случае, когда у вас есть запись в вашем HashMap, которая имеет определенный ключ и value == null. Вы получаете, он возвращает null. Что значит? Он имеет отображение null или его нет?

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

Ответ 3

В то время как HashMap разрешает нулевые значения, Map.of не использует HashMap и генерирует исключение, если оно используется как ключ или значение, как документированы:

Способы Map.of() и Map.ofEntries() static factory обеспечивают удобный способ создания неизменяемых карт. Экземпляры Map, созданные этими методами, имеют следующие характеристики:

  • ...

  • Они запрещают нулевые ключи и значения. Попытки создать их с помощью нулевых ключей или значений приводят к исключению NullPointerException.

Ответ 4

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

Итак, когда вы решите, что ваша карта должна иметь нулевой ключ или значение (возможно, по причинам, указанным ниже здесь), тогда вы можете это сделать.

Но "вариант 2" - это удобная вещь - вероятно, предназначенная для использования в константах. И люди, стоящие за Java, просто решили: "когда вы используете эти удобные методы, тогда результирующая карта будет нулевой".

Разрешение нулевых значений означает, что

 if (map.contains(key)) 

не совпадает с

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

что иногда может быть проблемой. Точнее: это то, что нужно помнить при работе с этим самым объектом карты.

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

Ответ 5

Не все карты допускают null как клавишу

Причина, упомянутая в docs of Map.

В некоторых реализациях карт есть ограничения на ключи и значения, которые они могут содержать. Например, некоторые реализации запрещают пустые ключи и значения, а некоторые имеют ограничения на типы их ключей. Попытка вставить недопустимый ключ или значение выдает исключение, исключающее исключение, обычно NullPointerException или ClassCastException.

Ответ 6

Разрешение нулей на картах было ошибкой. Мы можем видеть это сейчас, но, я думаю, было неясно, когда был введен HashMap. NullpointerException является наиболее распространенной ошибкой, видимой в производственном коде.

Я могу сказать, что JDK идет в сторону того, чтобы помочь разработчикам бороться с чумой NPE. Некоторые примеры:

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

Итак, запрет null в новых неизменяемых коллекциях (и карте) JDK9 просто идет в одном направлении. И мы все должны благодарить его!

Ответ 7

В документации не указано, почему null не разрешено:

Они запрещают клавиши null и значения. Попытки создать их с помощью null ключей или значений приведут к NullPointerException.

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

В то время как Map#put обычно используется путем заполнения карты во время выполнения, где могут возникать ключи/значения null.