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

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

get(key) возвращает null в двух случаях:

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

🟢 Junior Level

Да, HashMap разрешает хранить любое количество null в качестве значений.

Пример:

HashMap<String, String> map = new HashMap<>();
map.put("name", null);
map.put("email", null);
map.put("age", "25");

System.out.println(map.get("name")); // null
System.out.println(map.size());      // 3 (все три элемента сохранены)

Проблема: Когда get(key) возвращает null, непонятно — ключа нет или значение равно null?

Решение: Используйте containsKey() для проверки:

if (map.containsKey("name")) {
    // Ключ есть, и его значение может быть null
}

🟡 Middle Level

Проблема неоднозначности (Ambiguity)

get(key) возвращает null в двух случаях:

  1. Ключа нет в карте
  2. Ключ есть, но его значение = null
Map<String, String> map = new HashMap<>();
map.put("key", null);

System.out.println(map.get("key"));      // null (значение)
System.out.println(map.get("missing"));  // null (нет ключа)
// Как отличить?

System.out.println(map.containsKey("key"));      // true
System.out.println(map.containsKey("missing"));  // false

Сравнение коллекций

Коллекция Null-значение Особенности
HashMap Разрешено Стандартное поведение
Hashtable Запрещено (NPE) Устаревшая
ConcurrentHashMap Запрещено (NPE) Многопоточная безопасность
TreeMap Разрешено Сортировка по ключам

Почему ConcurrentHashMap запрещает null?

В многопоточной среде нельзя атомарно проверить get() + containsKey():

// Поток A:
String val = map.get(key);     // null
// Поток B удаляет ключ между вызовами:
// Поток A:
boolean exists = map.containsKey(key); // false — но val уже был null!

Best Practices

  1. Избегайте null-значений — используйте Optional или Null Object
  2. Всегда проверяйте containsKey() перед обработкой null
  3. Документируйте — может ли ваш метод вернуть null из Map

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

  1. NPE при использовании значения:
    map.get("key").toString(); // NPE, если значение = null
    
  2. Путаница с отсутствием ключа — логическая ошибка

🔴 Senior Level

Liveness Problem

В ConcurrentHashMap запрет null — это устранение ambiguity в check-then-act:

// Гипотетический сценарий (CHM на самом деле запрещает null)
// Показывает ЗАЧЕМ CHM запрещает null:
if (map.get(key) == null) {        // Шаг 1: null
    if (!map.containsKey(key)) {   // Шаг 2: false (ключ добавлен другим потоком!)
        map.put(key, value);       // Шаг 3: перезаписываем чужое значение
    }
}

Запрет null делает get() == null однозначным индикатором отсутствия ключа.

Internal Implementation

В HashMap null-значение хранится как обычная запись:

// putVal:
Node<K,V> e;
// ...
e.value = value; // value может быть null

Значение не участвует в хешировании — никаких ограничений.

Optional как альтернатива

Map<String, Optional<Value>> map = new HashMap<>();

// Явное выражение намерений:
map.put("key", Optional.empty());  // Намеренно "ничего"
map.put("key", Optional.of(val));  // Есть значение

// Получение:
Optional<Value> opt = map.getOrDefault("key", Optional.empty());

Overhead: дополнительный объект Optional на каждую запись.

Null Object Pattern

class User {
    static final User EMPTY = new User("", 0);
    // ...
}

Map<String, User> map = new HashMap<>();
map.put("missing", User.EMPTY); // Вместо null

Zero overhead, явная семантика, NPE-safe.

Memory Implications

null-значение не занимает дополнительной памяти:

  • Ссылка на null = 0 байт (просто отсутствие объекта)
  • Node всё равно создаётся (заголовок + hash + key + value ref + next)

Production Diagnostics

Признаки проблем с null-значениями:

  • NPE в chain-методах: map.get(key).method()
  • Логическая ошибка: null трактуется как “нет данных”
  • В БД: null-значения в Map → null в колонке (может нарушить constraint)

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

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

  • HashMap допускает любое количество null-значений
  • Проблема: get(key) = null неоднозначно — ключа нет ИЛИ значение = null?
  • Решение: containsKey() для однозначной проверки
  • ConcurrentHashMap запрещает null-значения — чтобы устранить ambiguity в check-then-act
  • Null Object Pattern: создать константу EMPTY вместо null
  • Optional как альтернатива: Map<String, Optional> — явное выражение намерений

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

  • Почему CHM запрещает null-значения? — Liveness Problem: get=null должен однозначно означать «нет ключа» для атомарных операций
  • Как отличить null от отсутствия ключа? — containsKey()
  • Чем null-значение отличается от null-ключа? — null-значений много, null-ключ только один
  • Optional vs Null Object? — Optional = дополнительный объект (overhead), Null Object = zero overhead

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

  • «HashMap запрещает null-значения» — допускает
  • «ConcurrentHashMap допускает null-значения» — нет, NPE
  • «null-значение = 0 байт» — Node всё равно создаётся (~32-48 байт)

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

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