Почему 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 — что спрашивают на самом деле
В этом посте были ссылки, но мы их удалили по правилам Сетки