← Назад к вопросам
Зачем переопределять hashCode и equals в Dart?
2.0 Middle🔥 142 комментариев
#Dart#ООП и паттерны
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Переопределение hashCode и equals в Dart
Зачем это нужно?
equals() и hashCode() — это два критически важных метода в Dart, которые определяют, как объекты сравниваются и используются в хэш-таблицах. По умолчанию Dart сравнивает объекты по идентичности (ссылка в памяти), а не по значению.
class User {
final String name;
final int age;
User(this.name, this.age);
}
final user1 = User("Alice", 30);
final user2 = User("Alice", 30);
print(user1 == user2); // false (разные объекты в памяти)
print(identical(user1, user2)); // false
Это проблема, потому что два объекта с одинаковыми данными считаются разными.
Правильная реализация
class User {
final String name;
final int age;
User(this.name, this.age);
@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is User && other.name == name && other.age == age;
}
@override
int get hashCode => Object.hash(name, age);
}
final user1 = User("Alice", 30);
final user2 = User("Alice", 30);
print(user1 == user2); // true (сравнение по значениям)
Контракт между equals и hashCode
Это важно: если два объекта равны (==), то их hashCode должны быть одинаковыми. Иначе будут баги:
final user1 = User("Alice", 30);
final user2 = User("Alice", 30);
final set = {user1};
print(set.contains(user2)); // true только если hashCode совпадает!
final map = {user1: "value"};
print(map[user2]); // null если hashCode не совпадает!
Если нарушить контракт:
// ❌ ПЛОХО
class BadUser {
final String name;
BadUser(this.name);
@override
bool operator ==(Object other) => other is BadUser && other.name == name;
@override
int get hashCode => 42; // Одинаковый для всех объектов!
}
// Это создаст проблемы с Set и Map
final users = {BadUser("Alice"), BadUser("Bob"), BadUser("Alice")};
print(users.length); // 3 (должно быть 2!)
Когда нужно переопределять?
Всегда переопределяйте equals и hashCode, если:
- Объект используется как ключ в Map
- Объект добавляется в Set
- Нужно сравнивать объекты по значению, а не по ссылке
- Это value object (User, Product, Order и т.д.)
Не переопределяйте, если:
- Это сложный объект с изменяемым состоянием
- Нужно сравнивать по ссылке (identity)
Использование пакета equatable
Для упрощения можно использовать пакет equatable:
import package:equatable/equatable.dart;
class User extends Equatable {
final String name;
final int age;
const User(this.name, this.age);
@override
List<Object?> get props => [name, age];
}
final user1 = User("Alice", 30);
final user2 = User("Alice", 30);
print(user1 == user2); // true
Плюсы equatable:
- Автоматически генерирует equals и hashCode
- Код короче и понятнее
- Менее подвержен ошибкам
- Часто используется в BLoC-архитектуре
Пример реальной проблемы
// ❌ БЕЗ переопределения
class Product {
final String id;
final String name;
Product(this.id, this.name);
}
final cart = <Product>{};
final product1 = Product("1", "Apple");
final product2 = Product("1", "Apple");
cart.add(product1);
cart.add(product2);
print(cart.length); // 2 (баг! должно быть 1)
// ✅ С переопределением
class GoodProduct {
final String id;
final String name;
GoodProduct(this.id, this.name);
@override
bool operator ==(Object other) => other is GoodProduct && other.id == id;
@override
int get hashCode => id.hashCode;
}
final goodCart = <GoodProduct>{};
final goodProduct1 = GoodProduct("1", "Apple");
final goodProduct2 = GoodProduct("1", "Apple");
goodCart.add(goodProduct1);
goodCart.add(goodProduct2);
print(goodCart.length); // 1 (верно!)
Итоговые рекомендации
- Всегда переопределяйте equals и hashCode для value objects
- Помните о контракте: если
a == b, тоa.hashCode == b.hashCode - Используйте equatable для упрощения кода
- Тестируйте поведение equals и hashCode
- Будьте осторожны с изменяемыми объектами в Set/Map