Какими могут быть ключи в HashMap (хэш-таблице)?

«Какими могут быть ключи в HashMap (хэш-таблице)?» — вопрос из категории Алгоритмы и структуры данных, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

В Dart (и, соответственно, в Flutter) ключом в HashMap (или в Map, который часто реализуется через хэш-таблицу) может быть любой объект, для которого корректно определены методы operator == (оператор равенства) и геттер hashCode.

Ключевое правило: Если два объекта считаются равными по ==, они обязаны иметь одинаковый hashCode. Обратное не обязательно: разные объекты могут иметь одинаковый хэш (коллизия), но это снижает производительность.

Примеры допустимых ключей в Dart:

  1. Встроенные типы с корректной реализацией: int, double, String, bool, DateTime, Uri.

    final Map<String, int> phoneBook = {'Alice': 12345, 'Bob': 67890};
    final Map<DateTime, String> events = {DateTime(2024, 1, 1): 'New Year'};
  2. Экземпляры пользовательских классов, где == и hashCode переопределены. Класс должен быть immutable (все поля final), чтобы его хэш-код не менялся после помещения в карту.

    class ProductKey {
      final String id;
      final String category;
    
      ProductKey(this.id, this.category);
    
      @override
      bool operator ==(Object other) =>
          identical(this, other) ||
          other is ProductKey &&
              runtimeType == other.runtimeType &&
              id == other.id &&
              category == other.category;
    
      @override
      int get hashCode => Object.hash(id, category); // Используем Object.hash
    }
    
    void main() {
      final inventory = HashMap<ProductKey, int>();
      final key = ProductKey('prod_1', 'electronics');
      inventory[key] = 100;
      print(inventory[ProductKey('prod_1', 'electronics')]); // 100
    }

Что НЕЛЬЗЯ использовать в качестве ключа (без переопределения методов):

  • List, Set, Map и другие коллекции по умолчанию. Их равенство сравнивает идентичность ссылок, а не содержимое.
  • Мутабельные (изменяемые) объекты. Если изменить поле объекта после того, как он использовался как ключ, его hashCode изменится, и найти значение по этому ключу станет невозможно.
  • Пользовательские классы без переопределения == и hashCode. Для них будет использоваться сравнение по ссылке (identical), что редко является нужным поведением.

Для автоматической генерации == и hashCode в Dart можно использовать пакет equatable или аннотации вроде @immutable и кодогенерацию.