Ответ
В Dart (и, соответственно, в Flutter) ключом в HashMap (или в Map, который часто реализуется через хэш-таблицу) может быть любой объект, для которого корректно определены методы operator == (оператор равенства) и геттер hashCode.
Ключевое правило: Если два объекта считаются равными по ==, они обязаны иметь одинаковый hashCode. Обратное не обязательно: разные объекты могут иметь одинаковый хэш (коллизия), но это снижает производительность.
Примеры допустимых ключей в Dart:
-
Встроенные типы с корректной реализацией:
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'}; -
Экземпляры пользовательских классов, где
==и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 и кодогенерацию.
Ответ 18+ 🔞
А, ну вот, опять про эти ваши хэш-мапы в Dart. Слушай, история проще пареной репы, но если её не понять, то потом будешь как тот самый Гамлет — всех посылать нахуй, потому что код не работает, а почему — хуй поймёшь.
Так вот, смотри. В HashMap (или в обычной Map, она часто под капотом на хэшах) ключом может быть любой объект, который не мудак. Шучу. Точнее, любой объект, у которого нормально определены два штуки: оператор == (равенство) и геттер hashCode.
Главное правило, которое надо выжечь на жопе: Если два объекта по == равны, то их hashCode обязаны быть одинаковыми. Это как закон. Обратное — не обязательно, разные объекты могут случайно иметь одинаковый хэш (это коллизия, бывает), но тогда производительность просядет, как твоё настроение в понедельник утром.
Что можно пихать в ключи, не бздя:
-
Встроенные типы, которые уже не лохи:
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'}; -
Твои кастомные классы, но только если ты не распиздяй. Надо переопределить
==и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; // И хэш считаем на основе тех же полей. Используем `Object.hash` — не надо велосипед изобретать. @override int get hashCode => Object.hash(id, category); } void main() { final inventory = HashMap<ProductKey, int>(); final key = ProductKey('prod_1', 'electronics'); inventory[key] = 100; // Вот тут магия: создаём НОВЫЙ объект с теми же полями, но он находит значение, потому что `==` и `hashCode` совпали. print(inventory[ProductKey('prod_1', 'electronics')]); // 100 }
А что категорически НЕЛЬЗЯ, если ты не самоубийца?
List,Set,Mapи прочие коллекции по дефолту. Их равенство — это сравнение ссылок, а не содержимого. Полный пиздец, а не ключ.- Изменяемые объекты. Я уже говорил, но повторю: если после добавления в карту поменять поля ключа, это всё — накрылось медным тазом. Значение потеряется навсегда, и доверия к такой мапе будет ноль ебать.
- Твои классы без переопределения
==иhashCode. Для них будет работать сравнение по ссылке (identical). Это почти никогда не то, что нужно. Получится, чтоProductKey('a', 'b')иProductKey('a', 'b')— это два разных ключа. Ёперный театр!
Короче, чтобы не ебаться с этим вручную, можно использовать пакет equatable или всякие кодогенераторы. Они за тебя эту рутину нагенерируют. Главное — э бошка думай, что ты делаешь, и не пытайся впихнуть невпихуемое.