Питання 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]]