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

Как переопределить операторы сравнения в Dart?

2.2 Middle🔥 171 комментариев
#Dart#Архитектура Flutter

Комментарии (1)

🐱
claude-haiku-4.5PrepBro AI26 мар. 2026 г.(ред.)

Ответ сгенерирован нейросетью и может содержать ошибки

Ответ

Переопределение операторов сравнения в Dart — это мощный механизм, позволяющий создавать интуитивные сравнения для собственных классов. Dart позволяет переопределять все основные операторы сравнения через методы.

Основные операторы сравнения

// Переопределяемые операторы сравнения:
== (равно)
!= (не равно) — обычно автогенерируется
< (меньше)
> (больше)
<= (меньше или равно)
>= (больше или равно)
hashCode (для использования в Set и Map)

Простой пример: переопределение ==

class User {
  final int id;
  final String name;
  final String email;

  User({required this.id, required this.name, required this.email});

  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true; // Оптимизация
    if (other is! User) return false;
    return id == other.id && email == other.email;
  }

  @override
  int get hashCode => Object.hash(id, email);
}

// Использование
var user1 = User(id: 1, name: 'John', email: 'john@example.com');
var user2 = User(id: 1, name: 'John', email: 'john@example.com');
var user3 = User(id: 2, name: 'Jane', email: 'jane@example.com');

print(user1 == user2); // true
print(user1 == user3); // false

Переопределение операторов сравнения размера

class Version {
  final int major;
  final int minor;
  final int patch;

  Version({required this.major, required this.minor, required this.patch});

  @override
  bool operator ==(Object other) {
    if (other is! Version) return false;
    return major == other.major && minor == other.minor && patch == other.patch;
  }

  @override
  int get hashCode => Object.hash(major, minor, patch);

  bool operator <(Version other) {
    if (major != other.major) return major < other.major;
    if (minor != other.minor) return minor < other.minor;
    return patch < other.patch;
  }

  bool operator >(Version other) => other < this;

  bool operator <=(Version other) => this < other || this == other;

  bool operator >=(Version other) => this > other || this == other;
}

// Использование
var v1 = Version(major: 1, minor: 2, patch: 3);
var v2 = Version(major: 1, minor: 3, patch: 0);
var v3 = Version(major: 1, minor: 2, patch: 3);

print(v1 < v2);  // true
print(v1 > v2);  // false
print(v1 == v3); // true
print(v1 <= v3); // true

Использование Equatable (рекомендуется)

Для упрощения часто используется пакет equatable:

import 'package:equatable/equatable.dart';

class Product extends Equatable {
  final int id;
  final String name;
  final double price;

  const Product({required this.id, required this.name, required this.price});

  @override
  List<Object?> get props => [id, name, price];
}

// Все операторы генерируются автоматически!
var product1 = Product(id: 1, name: 'Laptop', price: 999.99);
var product2 = Product(id: 1, name: 'Laptop', price: 999.99);

print(product1 == product2); // true
print(product1.hashCode == product2.hashCode); // true

Комплексный пример: Comparable

class Score implements Comparable<Score> {
  final String playerName;
  final int points;
  final DateTime timestamp;

  Score({
    required this.playerName,
    required this.points,
    required this.timestamp,
  });

  @override
  int compareTo(Score other) {
    // Сравниваем по очкам (убывание), потом по времени
    if (points != other.points) {
      return other.points.compareTo(points); // Убывающий порядок
    }
    return timestamp.compareTo(other.timestamp);
  }

  @override
  bool operator ==(Object other) {
    if (other is! Score) return false;
    return playerName == other.playerName &&
           points == other.points &&
           timestamp == other.timestamp;
  }

  @override
  int get hashCode => Object.hash(playerName, points, timestamp);

  bool operator <(Score other) => compareTo(other) > 0;
  bool operator >(Score other) => compareTo(other) < 0;
  bool operator <=(Score other) => compareTo(other) >= 0;
  bool operator >=(Score other) => compareTo(other) <= 0;
}

// Использование
var scores = [
  Score(playerName: 'Alice', points: 100, timestamp: DateTime.now()),
  Score(playerName: 'Bob', points: 150, timestamp: DateTime.now()),
  Score(playerName: 'Charlie', points: 120, timestamp: DateTime.now()),
];

scores.sort(); // Сортирует по compareTo
print(scores[0].playerName); // Bob (150 points)

Работа с Set и Map

class Person {
  final int id;
  final String name;

  Person({required this.id, required this.name});

  @override
  bool operator ==(Object other) {
    if (other is! Person) return false;
    return id == other.id;
  }

  @override
  int get hashCode => id.hashCode;
}

// hashCode критичен для Set и Map!
var people = {Person(id: 1, name: 'John')};
people.add(Person(id: 1, name: 'John')); // Не добавится (дубликат)
print(people.length); // 1

// Использование в Map
var personMap = <Person, String>{};
var person = Person(id: 1, name: 'John');
personMap[person] = 'Developer';
print(personMap[Person(id: 1, name: 'Jane')]); // Developer (same id)

Частые ошибки

// Ошибка 1: Несогласованность между == и hashCode
class BadExample {
  final int id;
  
  @override
  bool operator ==(Object other) => other is BadExample && id == other.id;
  // Забыли переопределить hashCode!
  // Результат: объекты с == == true могут иметь разные hashCode
}

// Ошибка 2: Игнорирование null
class BadComparison {
  final String? value;
  
  @override
  bool operator ==(Object other) {
    if (other is! BadComparison) return false;
    return value == other.value; // Работает, но берегись null!
  }
}

// Ошибка 3: Сложная логика без документации
class ComplexComparison {
  // Если логика сравнения нетривиальна — добавьте комментарий!
  @override
  bool operator ==(Object other) {
    // Сравниваем только по id, игнорируя name и email
    // потому что они могут обновиться после создания
    if (other is! ComplexComparison) return false;
    return id == other.id;
  }
}

Лучшие практики

  • Используйте Equatable для сокращения кода
  • hashCode и == должны быть согласованы — если a == b, то a.hashCode == b.hashCode
  • Переопределите оба или ни одного
  • Проверяйте тип через is перед приведением
  • Используйте Object.hash() для генерации hashCode
  • Документируйте логику сравнения если она нетривиальна
  • Тестируйте сравнение в unit тестах

Переопределение операторов — это инструмент, делающий код более интуитивным и естественным при работе с пользовательскими типами.