Вопрос 24 · Раздел 10

Можно ли хранить null ключ в HashMap?

При put(null, value):

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

Да, HashMap разрешает использовать один null в качестве ключа. Если попробовать добавить второй null-ключ, значение просто перезапишется.

Пример:

HashMap<Object, String> map = new HashMap<>();
map.put(null, "First");
map.put(null, "Second"); // Перезаписывает

System.out.println(map.get(null)); // "Second"
System.out.println(map.size());    // 1 (только один null-ключ)

Как это работает: HashMap специально обрабатывает null — для него hash(null) = 0, и он всегда попадает в первый бакет (table[0]).

Другие Map:

  • ConcurrentHashMapнет (бросает NullPointerException)
  • TreeMapнет (нужно сравнивать ключи)
  • Hashtableнет (старая реализация)

🟡 Middle Level

Техническая реализация

static final int hash(Object key) {
    int h;
    return (key == null) ? 0 : (h = key.hashCode()) ^ (h >>> 16);
}

При put(null, value):

  1. hash(null) возвращает 0
  2. index = (n - 1) & 0 = 0
  3. Элемент кладётся в table[0]

Сравнение реализаций

Реализация Null-ключ Null-значение
HashMap Да (1) Да (много)
LinkedHashMap Да (1) Да
TreeMap Нет Да
ConcurrentHashMap Нет Нет
Hashtable Нет Нет

Почему это опасно?

  1. Неочевидность — null-ключ может быть багом, который сложно обнаружить
  2. Неявный null-ключ может вызвать NPE в коде, который не ожидает null среди ключей (например, key.length() без проверки на null)
  3. Несовместимость — замена на ConcurrentHashMap сломает код

Best Practices

  • Старайтесь не использовать null как ключ
  • Используйте паттерн Null Object или константу:
    static final Object EMPTY_KEY = new Object();
    map.put(EMPTY_KEY, defaultValue);
    

Типичные ошибки

  1. Случайный null — результат метода вернул null, и вы кладёте как ключ
  2. Итерация без проверки — NPE при использовании ключа

🔴 Senior Level

Internal Mechanics

Null-ключ обрабатывается отдельно в putVal:

if (key == null)
    hash = 0;
else
    hash = spread(key.hashCode());

При коллизиях в бакете 0:

// Сравнение: (p.key == key || (key != null && key.equals(p.key)))
// Для null: p.key == null → true (сравнение по ссылке)

Null сравнивается только через ==equals() не вызывается.

Security Implications

Null-ключ как вектор атаки:

  • При десериализации JSON/XML null-ключ может прийти из невалидного входного документа. Обработайте это явно, чтобы избежать NPE в коде, который не ожидает null среди ключей.

Why ConcurrentHashMap Forbids Null?

Ambiguity в многопоточности:

// В ConcurrentHashMap:
// get(key) = null однозначно = ключа нет
// Не нужно второго вызова containsKey()

В HashMap с null-ключами:

V val = map.get(key);
if (val == null) {
    // Ключа нет? Или значение = null?
    if (!map.containsKey(key)) {
        // TOCTOU race в многопоточности!
    }
}

Alternative Patterns

Null Object Pattern:

enum SpecialKey { NONE }
map.put(SpecialKey.NONE, defaultValue);

Optional:

Map<Optional<String>, Value> map; // Но это overkill

Production Diagnostics

При отладке null-ключей:

  • Null-ключ невидим в некоторых профайлерах
  • Heap dump покажет Entry с key = null
  • В логах: null=SomeValue — может выглядеть как баг форматирования

🎯 Шпаргалка для интервью

Обязательно знать:

  • HashMap допускает ОДИН null-ключ, hash(null) = 0 → table[0]
  • При повторном put(null, …) значение перезаписывается
  • null сравнивается через ==, equals() не вызывается
  • ConcurrentHashMap/TreeMap/Hashtable запрещают null-ключи (NPE)
  • При десериализации JSON/XML null-ключ может прийти из невалидного документа
  • Null Object Pattern: использовать константу EMPTY_KEY вместо null

Частые уточняющие вопросы:

  • Почему ConcurrentHashMap запрещает null-ключ? — ambiguity: get=null = нет ключа или значение=null?
  • Сколько null-ключей можно? — ровно один, второй перезаписывает
  • Чем опасен null-ключ? — NPE в коде, который не ожидает null среди ключей
  • Как защититься? — Null Object Pattern, Optional, или явная валидация на входе

Красные флаги (НЕ говорить):

  • «HashMap не допускает null-ключи» — допускает, один
  • «null-ключ = баг» — это допустимое поведение, хотя и не рекомендуемое
  • «ConcurrentHashMap допускает null-ключ» — нет, бросает NPE

Связанные темы:

  • [[25. Можно ли хранить null значение в HashMap]]
  • [[14. Какие требования к ключу HashMap]]
  • [[23. Что такое ConcurrentHashMap и чем он отличается от HashMap]]