Pull to refresh

Comments 47

Бага. А ведь даже в javadoc сказано что должно бросаться NPE.
Там есть оговорка, что NPE только если в Map есть порядок.
Ну так TreeMap и есть упорядоченная коллекция.
Я просто подумал, что под багом вы имели в виду то, что null принимается в HashMap. Да, с TreeMap — бага.
Вы неправильно поняли оговорку. Только если используется natural ordering (то есть не используется компаратор), либо компаратор не обрабатывает null.
Технически там написано верно. Если put бросил NPE, значит вы задали ключом null и либо используется natural ordering, либо компаратор не допускает null keys. Но там не сказано, что если эти условия верны, то обязательно будет брошен NPE. Логическая импликация несимметрична :-)
Т.е. вы считаете что зря это исправили в 7ой джаве? :-)
Ни в коей мере :-) Да и, пожалуй, я перемудрил. Всё же документация читается как «если данные условия выполнены, то будет исключение», так что действительно документация не соответствовала реализации.
Описанное поведение TreeMap поправлено в 7ой джаве. Возможно даже что фикс перенесен с одним из апдейтов в 6-ую.
А из 1ой части про HashMap вы не сделали (по моему мнению) важный вывод. Что для java hashMap возможен результат 'get(key)' который вернул null и 'containsKey(key)' который вернул true. Иногда это может привести к логической ошибке, если кто-то поленится написать оба условния :)
Каким именно образом оно исправлено? Сразу кидает NPE или всегда корректно с null работает?
Проверил — в 7-ке сразу падает с NPE, но там все хитро — ошибка будет если указанный в Map'e Comparator не поддерживает сравенения null. Компаратора по-умолчанию нет, поэтому мы пытаемся позвать null.compareTo(null) и тут уже падаем.
Именно поэтому guava создала свои имплементации мэп и коллекций, которые не допускают null (как очереди в джаве, между прочим). Очень многие считают null в джаве ошибкой. «I call it my billion-dollar mistake.» — Sir C. A. R. Hoare, on his invention of the null reference
С null сейчас сражаются все, кому ни лень. И Scala, и Kotlin, и Ceylon — все так или иначе имеют какие-то способы указать типизированно и объектно отсутствие элемента без прибегания к null.
Java(-сообщество) вырастает из коротких штанишек наследства С++ типа null и Exception ( habrahabr.ru/post/146042/ и habrahabr.ru/post/155477/ см. про класс Either<Resut, Error> и Option<Resut>).

С null самое плохое — на концептуальном уровне: возвращать null или кидать NPE — непонятно.
В шестёрке фиксить не стали:

This is way too risky to fix in a jdk6 update release.


Грубо говоря, представьте, что есть продакшен код, который эту фичу (случайно?) юзает. И вот люди обновляются с Java 1.6.0_50 на 1.6.0_51 и у н их всё грохается…

А в Java 7 такие мелки штуки фиксать можно и нужно. Тем паче, что это реально баг.
А ещё у
Map a = new HashMap();
a.put("a", a);
System.out.println(a);
в разных версиях Java разные по фатальности последствия…
> если кто-то поленится написать оба условния

Второе условие гарантирует первое, поэтому оба писать никогда не требуется. Скорее распространенной ошибкой можно назвать, когда «очищают ключ» через put(..., null) а не через remove().
> 1. Может ли null использоваться в качестве ключа в Map?
> 2. Может ли Set содержать null?

Ну так в документации на интерфейс Map прямо написано:

Some map implementations have restrictions on the keys and values they may contain. For example, some implementations prohibit null keys and values, and some have restrictions on the types of their keys. Attempting to insert an ineligible key or value throws an unchecked exception, typically NullPointerException or ClassCastException. Attempting to query the presence of an ineligible key or value may throw an exception, or it may simply return false; some implementations will exhibit the former behavior and some will exhibit the latter. More generally, attempting an operation on an ineligible key or value whose completion would not result in the insertion of an ineligible element into the map may throw an exception or it may succeed, at the option of the implementation. Such exceptions are marked as «optional» in the specification for this interface.


Про Set написано аналогично. Т.е. тут корректнее говорить не про Map/Set, а про конкретные реализации.
изначально это обощенный вопрос для собеседований на «поговорить». слово за слово, от интерфейсов к реализациям и их особенностям.
тут корректнее говорить не про Map/Set, а про конкретные реализации.

Ну вообще говоря, странно будет если поведение конкретных реализаций будет отличаться от поведения определенного контрактом коллекции.
Ещё раз, цитата из документации к Map:
Some map implementations have restrictions on the keys and values they may contain. For example, some implementations prohibit null keys and values, and some have restrictions on the types of their keys. Attempting to insert an ineligible key or value throws an unchecked exception, typically NullPointerException or ClassCastException.
Контракт-то как раз и не определяет.
Вообще, про это уже писали тут. На set расширяется тем, что HashSet основан на HashMap.
А что мешает не строить предположения, а скачать JDK и посмотреть что в том же HashMap случай с null отдельно обрабатывается?
Ответ №2: В пустой TreeMap можно положить единственный ключ-null, все остальные операции (кроме size() и clear(), кстати) после этого не работают. В непустой TreeMap положить null-ключ нельзя из-за обязательного вызова compareTo().
Я с таким ответом не согласен. Правильный ответ будет следующим:

В TreeMap можно помещать null в качестве ключа, только если используется компаратор (а не естественный порядок) и компаратор умеет обрабатывать null в качестве аргумента.

Вы ничего не сказали про компараторы, а ваш детальный разбор, что будет, если положить null в пустой или непустой TreeMap, бесполезен и даже вреден для разработчиков. Очевидно, такое поведение недокументировано, а значит, может измениться. Поэтому нужно считать, что складывать null при natural order просто нельзя. Неважно, единственный он или нет.

К примеру, в WinAPI есть функции, где в ранних версиях в документации какой-то параметр был описан как «reserved, must be 0». Это именно означает, что надо передать 0, а не «я пробовал передавать разные числа, ничего страшного не случилось, так что передавайте, что хотите». Сегодня не случилось, а завтра выйдет апдейт к винде, и случится.
К примеру, в WinAPI есть функции, где в ранних версиях в документации какой-то параметр был описан как «reserved, must be 0».
И эти люди запрещают мне ковыряться в носу! Сегодня, на Java, такая практика признана плохой. Но 1) во времена создания WinAPI ещё не было Java, 2) WinAPI, как говорит нам КО, вещь не объектно-ориентированная, даже без перегрузки функций 3) во времена создания WinAPI ООП ещё было несколько мутной вещью (вспомним язык Clipper).
WinAPI, как говорит нам КО, вещь… без перегрузки функций
В общем, всё остальное можно было не писать читать.
Очевидно, такое поведение недокументировано, а значит, может измениться.
Как-то раз я чуть не начал проходить собеседование в Oracle, в подразделение, занимающееся тестированием их родной Java-машины. Спасибо, вы меня натолкнули на понимание фразы интервьюера о «тестах, которые должны падать». Это как раз про такое поведение. Если его исправить, то с большой вероятностью у кучи народа всё повалится в production-е. Поэтому лучше отложить до следующей версии. А тесты на него уже есть.
Совершенно некорректные вопросы.
Вопрос: «Может ли null использоваться в качестве ключа в Map?»
Правильный ответ: Так как Map это интерфейс — то зависит от реализации.
Подсказка: курим реализации (а также спецификации) HashMap, ConcurrentHashMap & Hashtable (который тоже Map).

Вопрос: «Может ли Set содержать null?»
Ответ: ну вы поняли. ;)
документ на интерфейс является документом на любую имплементацию, если в имплеменации нету своей документации. Разве нет?

Правда в том, что во всех упомянутых коллекциях спека действительно переопределена :)
Правильный ответ: Так как Map это интерфейс — и спека у Map явно отдает принятие/запрет null'а в реализацию — то зависит от реализации.

Ну не должен же я все подряд писать. Некоторые очевидные вещи можно опускать. ;)
Заодно и будет повод для следующих вопросов про конкретные реализации.
Если бы меня на собеседовании спросили, можно ли класть null в Map, я бы честно ответил, что не знаю. А если они продолжили допытываться, послал бы их куда подальше. Не об этом надо говорить на собеседованиях.
Одно время я поражался тому, что на собеседованиях спрашивают такую фигню, которая потом не пригождается. Потом понял, что компетенция нужна с запасом: учиться потом на ходу фундаментальным вещам — некогда.
Вы считаете, что такие знания не нужны? Ну допустим взяли Вас на работу, Вы сами никогда класть null в map не стали бы, но есть такая вещь как code review… и Вам попадается на рвью код, где кто-то воспользовался такой фитчей. Что Вы будете делать? Правильно — тратить полчаса оплачиваемого времени на изучение этого вопроса.
Да не нужно мне тратить полчаса. В ходе code review я прежде всего смотрю, покрыт ли этот код тестами. Если чувак воспользовался этой фичей, и у него есть тесты, доказывающие, что код в результате работает как надо — зачем мне тратить полчаса?
А «чувак» то ещё поспорит — стоит ли такие фундаментальные вещи, которые описаны в javadocs стандартной библиотеки (причём в самых первых строчках) проверять в unit test-ах?
Нет, я имел в виду, что юнит-тесты должны проверять не функциональность HashMap, описанную в javadoc, а функциональность моей программы, которая этот HashMap использует. Если моя программа делает то, что от не требуется, и это доказано тестами, то абсолютно неважно, использует ли она Map и что там она в Map складывает.
Дык unit test-ы не могут охватить весь диапазон ключей, которые складываются в hash map… Т.о. тесты не могут служить абсолютным доказательством правильной работы. Потому в code review следует смотреть не только на тесты.
Точно так же и code review не может служить абсолютным доказательством правильной работы.

Я и не говорю, что надо смотреть только тесты. Я говорю, что не нужно знать, как реагирует Map на null — всего не запомнишь, всего не заметишь, всю документацию не прочитаешь. Если хочешь быть достаточно уверенным, что твоё приложние правильно работает, то зубрить javadoc'и всех стандартных Java классов — это неправильный путь.
Большинство работодателей (в т.ч. Google, Oracle, Facebook) думает иначе — если человек не знает как работают даже базовые классы стандартной библиотеки и не читал даже первых строчек их javadocs… то в общем такой человек в пролёте. И может быть в этом даже есть рациональное зерно — это косвенно говорит о недостаточности опыта.

В данном случае я говорил об уверенности, что чужое приложение работает правильно — без понимания того, как работают базовые классы стандартной библиотеки (включая тонкости), невозможно сделать быстро и качественно code review. И даже знания одних javadocs обычно мало (т.к. там часто вода и общие слова) — чтоб хорошо понимать как работает тот или иной класс лучше изучать исходники.
Map map = new TreeMap();
map.put(null,  "null"); // ошибки нет! 

Только что проверил, у меня бросает NullPointer (Java 1.7 и 1.8)

Sign up to leave a comment.

Articles

Change theme settings