Dart 中的 Equals 和 HashCode
最后修改日期:2025 年 6 月 5 日
本教程探讨了 Dart 中的 equals/hashCode 合约,这是对象比较和基于哈希的集合的关键概念。它包括 Dart 2.14 中引入的现代哈希函数,如 Object.hash、Object.hashAll 和 Object.hashAllUnordered。
equals/hashCode 合约 确保对象被正确比较,并在 HashSet 和 HashMap 等基于哈希的集合中可靠地存储。正确实现此合约可防止集合操作中的错误。
合约的关键规则
- 如果两个对象通过
==运算符相等,则它们的hashCode值必须相同。 - 具有相同
hashCode的对象可能不相等,因为可能存在哈希冲突。 - 违反此合约会导致基于哈希的集合出现不可预测的行为,例如查找失败或元素丢失。
为什么合约很重要
-
高效查找: 基于哈希的集合使用
hashCode进行快速索引。不一致的哈希码会中断检索。 -
冲突处理: 集合使用
==来解决哈希冲突。不一致会导致数据完整性问题。 -
稳定哈希: 在集合中插入后更改
hashCode会导致对象无法检索。
该合约确保了集合中可靠的行为。Dart 的现代哈希函数(Object.hash、Object.hashAll、Object.hashAllUnordered)在保持合约合规性的同时简化了哈希码生成。
| 概念 | 描述 | 重要性 |
|---|---|---|
| == 运算符 | 比较对象是否相等 | 必须是自反的、对称的和传递的 |
| hashCode | 生成整数哈希值 | 必须与 == 一致 |
| Object.hash | 组合多个哈希码 | 简化哈希码生成 |
| 身份 | Dart 中的默认比较 | 使用 identical |
默认情况下,Dart 的 == 运算符使用 identical 进行身份比较。要实现基于值的相等性,请重写 == 和 hashCode。Object 类提供默认的基于身份的实现。
使用 Object.hash 的基本 Equals/HashCode
此示例使用 Object.hash 为 Person 类实现 equals/hashCode 合约,比较姓名和年龄。
class Person {
final String name;
final int age;
Person(this.name, this.age);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Person && name == other.name && age == other.age;
@override
int get hashCode => Object.hash(name, age);
@override
String toString() => 'Person(name: $name, age: $age)';
}
void main() {
var p1 = Person('Alice', 30);
var p2 = Person('Alice', 30);
var p3 = Person('Bob', 25);
print('p1 == p2: ${p1 == p2}');
print('p1 == p3: ${p1 == p3}');
var people = {p1, p2, p3};
print('People in set: $people');
}
== 运算符检查身份、类型和字段相等性。Object.hash 组合了 name 和 age 的哈希码,用它取代了旧的 XOR 方法以获得更好的分布。
HashSet 将 p1 和 p2 识别为相等,只存储一个。Object.hash 确保相等对象的哈希码一致。
$ dart run basic_equals.dart
p1 == p2: true
p1 == p3: false
People in set: {Person(name: Alice, age: 30), Person(name: Bob, age: 25)}
集合的相等性
此示例展示了合约如何使用 Point 类影响 Set 和 List 的行为。
class Point {
final int x;
final int y;
Point(this.x, this.y);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Point && x == other.x && y == other.y;
@override
int get hashCode => Object.hash(x, y);
@override
String toString() => 'Point($x, $y)';
}
void main() {
var p1 = Point(1, 2);
var p2 = Point(1, 2);
var p3 = Point(3, 4);
var pointsList = [p1, p2, p3];
print('List length: ${pointsList.length}');
var pointsSet = {p1, p2, p3};
print('Set size: ${pointsSet.length}');
print('Contains p2: ${pointsSet.contains(p2)}');
}
Point 类使用值相等性,并使用 Object.hash 进行哈希码生成。List 允许重复项,而 Set 根据相等性消除它们。
contains 方法确认 Set 中识别出 p2,这表明了合约的正确遵守。
$ dart run collection_equality.dart List length: 3 Set size: 2 Contains p2: true
继承和相等性
此示例在类层次结构中实现 equals/hashCode,以确保跨子类的合约合规性。
class Shape {
final String color;
Shape(this.color);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Shape && runtimeType == other.runtimeType && color == other.color;
@override
int get hashCode => Object.hash(color, runtimeType);
}
class Circle extends Shape {
final double radius;
Circle(String color, this.radius) : super(color);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Circle && super == other && radius == other.radius;
@override
int get hashCode => Object.hash(super.hashCode, radius);
@override
String toString() => 'Circle(color: $color, radius: $radius)';
}
void main() {
var shape = Shape('red');
var circle1 = Circle('blue', 5.0);
var circle2 = Circle('blue', 5.0);
var circle3 = Circle('red', 10.0);
print('circle1 == circle2: ${circle1 == circle2}');
print('circle1 == circle3: ${circle1 == circle3}');
print('shape == circle1: ${shape == circle1}');
var shapes = {shape, circle1, circle2, circle3};
print('Unique shapes: $shapes');
}
Shape 类包含 runtimeType 以区分子类。Circle 首先检查超类相等性,然后使用 Object.hash 组合哈希码。
这确保了跨层次结构的对称性和一致性,其中 circle1 和 circle2 相等。
$ dart run inheritance_equality.dart
circle1 == circle2: true
circle1 == circle3: false
shape == circle1: false
Unique shapes: {Shape(color: red), Circle(color: blue, radius: 5.0), Circle(color: red, radius: 10.0)}
可空字段的相等性
此示例使用 Object.hash 在 equals/hashCode 实现中处理可空字段。
class Product {
final String id;
final String? name;
final double? price;
Product(this.id, this.name, this.price);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Product &&
id == other.id &&
name == other.name &&
price == other.price;
@override
int get hashCode => Object.hash(id, name, price);
@override
String toString() => 'Product(id: $id, name: $name, price: $price)';
}
void main() {
var p1 = Product('123', 'Widget', 9.99);
var p2 = Product('123', 'Widget', 9.99);
var p3 = Product('123', null, null);
var p4 = Product('123', null, null);
print('p1 == p2: ${p1 == p2}');
print('p3 == p4: ${p3 == p4}');
print('p1 == p3: ${p1 == p3}');
var products = {p1, p2, p3, p4};
print('Unique products: $products');
}
== 运算符安全地比较可空字段。Object.hash 会自动处理 null 值,确保生成健壮的哈希码。
该示例确认了具有 null 字段的产品得到正确处理,从而维护了合约。
$ dart run nullable_equality.dart
p1 == p2: true
p3 == p4: true
p1 == p3: false
Unique products: {Product(id: 123, name: Widget, price: 9.99), Product(id: 123, name: null, price: null)}
使用 Object.hashAll() 的集合相等性
此示例在 Team 类的字段中使用 Object.hashAll 来处理集合。
class Team {
final String name;
final List<String> members;
Team(this.name, this.members);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Team &&
name == other.name &&
other.members.length == members.length &&
other.members.asMap().entries.every(
(e) => members[e.key] == e.value);
@override
int get hashCode => Object.hash(name, Object.hashAll(members));
@override
String toString() => 'Team(name: $name, members: $members)';
}
void main() {
var team1 = Team('A', ['Alice', 'Bob']);
var team2 = Team('A', ['Alice', 'Bob']);
var team3 = Team('A', ['Alice', 'Charlie']);
var team4 = Team('B', ['Alice', 'Bob']);
print('team1 == team2: ${team1 == team2}');
print('team1 == team3: ${team1 == team3}');
print('team1 == team4: ${team1 == team4}');
var teams = {team1, team2, team3, team4};
print('Unique teams: $teams');
}
== 运算符为简化起见手动检查列表相等性。Object.hashAll 为 members 列表生成哈希码,并与 name 的哈希码组合。
这确保了具有相同成员的团队是相等的,并且 Set 能正确识别唯一的团队。
$ dart run collection_field_equality.dart
team1 == team2: true
team1 == team3: false
team1 == team4: false
Unique teams: {Team(name: A, members: [Alice, Bob]), Team(name: A, members: [Alice, Charlie]), Team(name: B, members: [Alice, Bob])}
使用 Object.hashAllUnordered 的无序集合相等性
此示例在不考虑顺序的集合中使用 Object.hashAllUnordered,例如 Post 类中的标签集。
class Post {
final String title;
final Set<String> tags;
Post(this.title, this.tags);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Post &&
title == other.title &&
tags.length == other.tags.length &&
tags.every((tag) => other.tags.contains(tag));
@override
int get hashCode => Object.hash(title, Object.hashAllUnordered(tags));
@override
String toString() => 'Post(title: $title, tags: $tags)';
}
void main() {
var post1 = Post('News', {'urgent', 'breaking'});
var post2 = Post('News', {'breaking', 'urgent'});
var post3 = Post('News', {'local', 'urgent'});
var post4 = Post('Update', {'urgent', 'breaking'});
print('post1 == post2: ${post1 == post2}');
print('post1 == post3: ${post1 == post3}');
print('post1 == post4: ${post1 == post4}');
var posts = {post1, post2, post3, post4};
print('Unique posts: $posts');
}
== 运算符检查 tags 集合是否包含相同的元素,而不考虑顺序。Object.hashAllUnordered 生成一个对元素顺序不敏感的哈希码。
这确保了具有相同标签(无论顺序如何)的帖子是相等的,并且 Set 正确地处理它们。
来源
掌握 equals/hashCode 合约,并结合 Object.hash、Object.hashAll 和 Object.hashAllUnordered,可确保 Dart 中健壮的对象比较和集合行为。
作者
浏览 所有 Dart 教程。