Почему java.util.Map.containsKey генерирует исключение нулевого указателя для карт, которые не поддерживают нулевые ключи?

#java #hashmap #treemap

#java #hashmap #treemap

Вопрос:

В документах Java говорится следующее:

Выдает:

ClassCastException — если ключ имеет неподходящий тип для этой карты (необязательно)

Исключение NullPointerException — если указанный ключ равен нулю, и эта карта не допускает нулевые ключи (необязательно)

Я уверен, что для этого решения есть веская причина, но я точно не знаю, какая именно. Разве containsKey не должен всегда возвращать false, даже если карта не допускает нулевые значения в качестве ключей? Очевидно, что Null не является ключом на карте, если карта не допускает ключей, поэтому я думаю, что он должен возвращать false .

Конкретный экземпляр, о котором я думаю, — это TreeMap . Я изменил экземпляр Map с HashMap на TreeMap по соображениям производительности, и это вызвало небольшую ошибку, потому что containsKey для HashMap не генерирует исключение с нулевым указателем. Я чувствую, что нет веской причины для такого изменения, чтобы вызвать подобную незначительную ошибку.


Редактировать Я осведомлен о (необязательном) характере исключения NullPointerException . Его необязательный характер, на мой взгляд, не объясняет, почему это разрешено

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

В java docs для Object.equals говорится

Для любого ненулевого ссылочного значения x, x.equals(null) должен возвращать false .

Поэтому я бы подумал, что containsKey должен возвращать false, если карта не поддерживает нулевые ключи, поскольку реализация «должна» быть эквивалентна перебору ключей с помощью k.equals(ввод).

Ответ №1:

Ваш аргумент в равной степени применим к ClassCastException : Не containsKey должен всегда возвращать false, даже если карта не допускает этот тип значения в качестве ключей?

Ответ: Вместо того, чтобы заставлять реализации проверять правильность данного ключа, API (документированное поведение) позволяет создавать исключение для недопустимых значений ключа.

Обратите внимание, что оба исключения перечислены как «(необязательно)», что означает, что это зависит от реализации, делать ли это. Реализация может выбрать простой возврат false или может выбрать одно из этих исключений.

Реализации, вероятно, предпочтут не добавлять специальную логику для этого, поэтому, если алгоритм «изначально» генерирует исключение, пусть будет так. API был задокументирован, чтобы разрешить это поведение по умолчанию.

Например, TreeMap будет изначально выбрасывать ClassCastException , потому что это произойдет при попытке вызвать compare() / compareTo() . HashMap не заботится о типе объекта, поэтому никогда не будет выбрасывать ClassCastException .

Логика containsKey() для карты, которая не поддерживает нулевые ключи, может или не может выполнять что-то, что выдает NullPointerException , если данный ключ равен нулю, и это допустимый результат, поэтому логике не нужно тратить дополнительный код на его обработку.

Ответ №2:

Я считаю, что это было принято дизайнерское решение.

Возможны два сценария:

1. Вы используете containsKey на карте, которая поддерживает нулевой ключ. Как пользователь этой функции, вы ожидаете получить либо true, либо false . Нет абсолютно никаких причин возвращать исключение NullPointerException, поскольку мы видим ‘null’ как любой другой ключ, так что, как и вы, можно ожидать, что ‘someKey’ вернет false, если он не существует, то же самое для null .

2. Вы используете containsKey на карте, которая не поддерживает нулевой ключ. Возврат false был бы странным решением, потому что это не возможный ключ. Также у пользователя может быть ссылка на какой-либо объект, который он пытается использовать в качестве ключа. Пользователь предпочел бы получить исключение NullPointerException, чтобы он знал об этой ситуации, пытаясь получить / установить нулевой объект в качестве ключа.

Ответ №3:

Прежде всего, containsKey(Object key) объявляется в Map<K, V> интерфейсе, который ничего не объявляет, следовательно, это зависит от реализации.

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

Обратите внимание, что:

  1. Ваш ключ может быть подтипом определенного типа ключа, поэтому его может потребоваться привести. Поскольку это внутреннее приведение, среда выполнения понятия не имеет, что вы собираетесь предоставить в качестве аргумента. Следовательно, полезно использовать это;
  2. Ваш ключ может быть либо явно, либо get где-то за кулисами (например, в многопоточном сценарии), get null , что также является причиной обработки этого сценария.

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