← Назад к вопросам

Зачем переопределять 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