Переопределение hashCode и ==
Часть раздела Объектно-ориентированное программирование путешествия по Dart на Coddy — урок 71 из 110.
По умолчанию два объекта в Dart равны только в том случае, если они являются одним и тем же экземпляром в памяти. Это означает, что два объекта с идентичными значениями все равно считаются разными:
class Point {
int x, y;
Point(this.x, this.y);
}
void main() {
var p1 = Point(3, 4);
var p2 = Point(3, 4);
print(p1 == p2); // false - разные экземпляры!
}Чтобы сравнивать объекты по их значениям, вам необходимо переопределить как оператор ==, так и hashCode. Эти два метода всегда должны переопределяться вместе — если два объекта равны, они должны иметь одинаковый хэш-код:
class Point {
final int x, y;
Point(this.x, this.y);
@override
bool operator ==(Object other) {
if (other is! Point) return false;
return x == other.x && y == other.y;
}
@override
int get hashCode => Object.hash(x, y);
}
void main() {
var p1 = Point(3, 4);
var p2 = Point(3, 4);
print(p1 == p2); // true - одинаковые значения!
}hashCode имеет решающее значение для таких коллекций, как ключи Set и Map. Вспомогательный метод Dart Object.hash() объединяет несколько значений в один хэш-код. Без правильного hashCode ваши объекты не будут корректно работать в хэш-коллекциях, даже если == переопределен.
Задание
ЛегкоДавайте создадим систему координат, в которой точки можно сравнивать по их значениям, а не по адресам в памяти! Вы создадите класс Coordinate, который правильно реализует равенство на основе значений, что позволит ему корректно работать с проверками на равенство и коллекциями на основе хешей, такими как Set.
Вы организуете свой код в двух файлах:
coordinate.dart: Создайте классCoordinate, представляющий точку в 2D-пространстве с полямиfinal int xиfinal int y. Переопределите оператор==иhashCodeтак, чтобы две координаты с одинаковыми значениями x и y считались равными. ИспользуйтеObject.hash()для генерации хеш-кода на основе обоих полей.main.dart: Импортируйте файл с координатами и продемонстрируйте, как равенство на основе значений меняет поведение при сравнении объектов:- Создайте два отдельных объекта
Coordinate, оба с x:5и y:10 - Выведите на экран, равны ли они, используя
== - Выведите на экран, являются ли они идентичными (один и тот же экземпляр), используя
identical() - Создайте
Set<Coordinate>и добавьте в него обе координаты - Выведите длину множества (она должна быть равна 1, так как они равны!)
- Создайте третью координату с x:
3и y:7, добавьте ее в множество - Выведите окончательную длину множества
- Создайте два отдельных объекта
Этот челлендж демонстрирует, почему hashCode и == всегда должны переопределяться вместе. Set использует хеш-коды для организации элементов, поэтому без правильного hashCode даже равные объекты могут храниться как дубликаты!
Ожидаемый вывод:
true
false
1
2Шпаргалка
По умолчанию два объекта в Dart равны только в том случае, если они являются одним и тем же экземпляром в памяти, а не если у них одинаковые значения:
var p1 = Point(3, 4);
var p2 = Point(3, 4);
print(p1 == p2); // false - разные экземпляры!Чтобы сравнивать объекты по их значениям, переопределите оператор == и hashCode. Их всегда нужно переопределять вместе:
class Point {
final int x, y;
Point(this.x, this.y);
@override
bool operator ==(Object other) {
if (other is! Point) return false;
return x == other.x && y == other.y;
}
@override
int get hashCode => Object.hash(x, y);
}Используйте Object.hash() для объединения нескольких значений в один хэш-код. hashCode имеет решающее значение для таких коллекций, как Set и Map — без него равные объекты не будут корректно работать в коллекциях на основе хэшей.
Чтобы проверить, являются ли два объекта одним и тем же экземпляром (а не просто имеют равные значения), используйте identical():
print(identical(p1, p2)); // false - разные экземплярыПопробуйте сами
import 'coordinate.dart';
void main() {
// TODO: Создайте два отдельных объекта Coordinate, оба с x: 5 и y: 10
// TODO: Выведите, равны ли они, используя ==
// TODO: Выведите, идентичны ли они (один и тот же экземпляр), используя identical()
// TODO: Создайте Set<Coordinate> и добавьте в него обе координаты
// TODO: Выведите длину множества (должна быть 1, так как они равны!)
// TODO: Создайте третью координату с x: 3 и y: 7, добавьте ее в множество
// TODO: Выведите окончательную длину множества
}
В этом уроке есть небольшой тест. Начните урок, чтобы ответить на вопросы и сохранить прогресс.
Все уроки раздела Объектно-ориентированное программирование
1Основы ООП
Внешние файлыБиблиотеки и импортВведение в ООПКлассы и объектыКлючевое слово thisМетодыПеременные экземпляраОсновы конструкторовИтоги — Простой калькулятор4Null-безопасность
Введение в Null-безопасностьNullable и Non-Nullable типыОператоры ? и !Ключевое слово Late и Null-безопасностьNull-aware операторыNull-безопасность в классахИтоги — Система профилей пользователей7Абстрактные классы и интерфейсы
Абстрактные классыАбстрактные методыИнтерфейсы в DartНеявные интерфейсыРеализация против наследованияМножественные интерфейсыИтоги — Калькулятор фигур10Коллекции и обобщения
Обзор List, Set, MapТипобезопасные коллекцииОбобщенные классыОбобщенные методыОграничения обобщенийIterable и IteratorИтоги: Обобщенное хранилище13Продвинутые концепции ООП
Композиция против наследованияМетоды расширенияВызываемые классыЗапечатанные классы (Dart 3)Записи (Dart 3)Паттерны и сопоставление (3.0)Перечисления с методами16Проект: Управление библиотекой
Обзор проектаКлассы Book и User2Конструкторы в Dart
Конструктор по умолчаниюИменованные конструкторыСписки инициализацииКонстантные конструкторыФабричные конструкторыПеренаправляющие конструкторыИтоги — Shape Builder5Инкапсуляция
Публичные и приватные членыСоглашение о префиксе _Приватность на уровне библиотекГеттеры и сеттеры: подробный разборСкрытие данныхИтоги — Записи о студентах8Mixins
Введение в MixinsСоздание MixinsИспользование нескольких MixinsКлючевое слово on в MixinsMixin и наследованиеMixin и интерфейсИтоги: Система животных11Специальные методы
Переопределение toString()Переопределение hashCode и ==Интерфейс ComparableМетод call()Переопределение noSuchMethodПовторение — Пользовательская коллекция14Паттерны проектирования. Часть 1
Введение в паттерны проектированияПаттерн SingletonПаттерн FactoryПаттерн ObserverПаттерн Strategy3Свойства классов
Члены экземпляра и статические членыПоля Final и ConstПеременные LateСтатические методы и поляГеттеры и сеттерыИтоги — Менеджер банковского счета6Наследование
Основы наследованияКлючевое слово superПереопределение методовАннотация @overrideКлючевое слово final для классовКонструкторы и наследованиеИтоги: Иерархия сотрудников9Полиморфизм
Основы полиморфизмаПолиморфизм через интерфейсыПроверка типов во время выполненияОператоры is и asКлючевое слово covariantИтоги — Обработчик платежей12Асинхронное ООП
Futures и async/awaitОсновы StreamsКонтроллеры StreamАсинхронные конструкторыАсинхронность в методах классовИтоги — Data Fetcher15Паттерны проектирования. Часть 2
Паттерн КомандаПаттерн АдаптерПаттерн ДекораторПаттерн Шаблонный методПаттерн СостояниеПаттерн КомпоновщикПаттерн Репозиторий