← Назад к вопросам
Как переопределить операторы сравнения в 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 тестах
Переопределение операторов — это инструмент, делающий код более интуитивным и естественным при работе с пользовательскими типами.