Почему ConcurrentHashMap.get(null) бросает NullPointerException, а HashMap — нет

HashMap разрешает один null-ключ и любое количество null-значений. get(key), вернувший null, может значить два разных факта: ключа нет в map, либо ключ есть, а значение — null. В однопоточном коде эту неоднозначность разруливают через containsKey().

В ConcurrentHashMap та же неоднозначность становится гонкой. Doug Lea, автор класса, объясняет решение прямо в Javadoc: в конкурентной структуре нельзя надёжно отличить «ключа нет» от «значение null» без блокировки на чтение, которая убьёт смысл конкурентной коллекции. Поэтому null запретили целиком — и на ключ, и на значение, ещё на этапе put().

Ошибка всплывает не при разработке, а при миграции кода с HashMap на ConcurrentHashMap ради многопоточности — рабочий код падает на первом null, который раньше проходил незаметно.

Map m = new ConcurrentHashMap<>(); m.put("key", null); // NullPointerException

Спросят следом: как тогда безопасно хранить «значение отсутствует» в конкурентной map. Ответ — не null, а sentinel-объект или Optional.empty() как значение, либо явный computeIfAbsent() с проверкой снаружи.

Замена HashMap на ConcurrentHashMap — это не только про потокобезопасность, но и про то, что часть контракта поменялась вместе с ней.

Тренажёр: 600 вопросов, мок с таймером, план повторов

senior·base — что спрашивают на самом деле


В этом посте были ссылки, но мы их удалили по правилам Сетки