ZetCode

Dart 中的 Equals 和 HashCode

最后修改日期:2025 年 6 月 5 日

本教程探讨了 Dart 中的 equals/hashCode 合约,这是对象比较和基于哈希的集合的关键概念。它包括 Dart 2.14 中引入的现代哈希函数,如 Object.hashObject.hashAllObject.hashAllUnordered

equals/hashCode 合约 确保对象被正确比较,并在 HashSetHashMap 等基于哈希的集合中可靠地存储。正确实现此合约可防止集合操作中的错误。

合约的关键规则

为什么合约很重要

该合约确保了集合中可靠的行为。Dart 的现代哈希函数(Object.hashObject.hashAllObject.hashAllUnordered)在保持合约合规性的同时简化了哈希码生成。

概念 描述 重要性
== 运算符 比较对象是否相等 必须是自反的、对称的和传递的
hashCode 生成整数哈希值 必须与 == 一致
Object.hash 组合多个哈希码 简化哈希码生成
身份 Dart 中的默认比较 使用 identical

默认情况下,Dart 的 == 运算符使用 identical 进行身份比较。要实现基于值的相等性,请重写 ==hashCodeObject 类提供默认的基于身份的实现。

使用 Object.hash 的基本 Equals/HashCode

此示例使用 Object.hashPerson 类实现 equals/hashCode 合约,比较姓名和年龄。

basic_equals.dart
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 组合了 nameage 的哈希码,用它取代了旧的 XOR 方法以获得更好的分布。

HashSetp1p2 识别为相等,只存储一个。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 类影响 SetList 的行为。

collection_equality.dart
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,以确保跨子类的合约合规性。

inheritance_equality.dart
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 组合哈希码。

这确保了跨层次结构的对称性和一致性,其中 circle1circle2 相等。

$ 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 实现中处理可空字段。

nullable_equality.dart
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 来处理集合。

collection_field_equality.dart
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.hashAllmembers 列表生成哈希码,并与 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 类中的标签集。

unordered_equality.dart
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 正确地处理它们。

来源

Dart 相等性指南
Dart == 运算符

掌握 equals/hashCode 合约,并结合 Object.hashObject.hashAllObject.hashAllUnordered,可确保 Dart 中健壮的对象比较和集合行为。

作者

Jan Bodnar 是一位充满热情的程序员,拥有十多年的教学和写作经验。自 2007 年以来,他已在各种编程语言中撰写了 1,400 多篇文章和 8 本电子书。

浏览 所有 Dart 教程