你真的搞明白了 Dart 中两个对象相等的逻辑了吗?
前言
通常来说我们不会去实现类的自定义的相等判断,这个时候构建的任何对象因为值不同或 hash code不同,使用==
判断的时候都是返回 false
的。而在某些场合,如果我们想要直接使用==
来判断自定义类是否相等的话,则需要同时覆盖==
操作符和 hashCode
方法。在使用自定义的对象相等性实现时,编码需要注意哪些事情,本篇来为你总结相关内容。
对象相等判断
我们先来看 Java 语言中很经典的一道面试题,即下面的代码控制台输出结果是什么。
Integer a=127;
Integer b=127;
System.out.println(a==b);
Integer c=128;
Integer d=128;
System.out.println(c==d);
结果第一个打印的是 true,第二个打印的是 false。这是因为 Java 中的-128至127范围的整数对象使用了缓存,在这范围之外的都是新构建的对象了。那么 Dart 中,类似的情况是什么样的?
int a = 127;
int b = 127;
print(a == b);
int c = 128;
int d = 128;
print(c == d);
结果返回都是 true
,这是因为 Dart 支持操作符重载,判断两个对象相等实际是使用的是==
操作符和 hash code
判断的。int
和 double
都继承自数值类num
。对于这个类型来说,hash code
就是数值本身,而==
操作符实际使用的是 compareTo
方法进行判断的,因此只要两个数值类的值相等(特殊值除外,比如 double.nan
,double.infinity
),那么使用==
比较符操作时就是相等的。
对于字符串类型来说也是一样,字符串的只要字符序列是一致的,那么 hash code就也是一致的。但是,对于 unicode 而言,如果使用的编码不同,那么 hash code
是不相等的。因此,在 Dart 中,比较字符串相等不需要使用类似 Java 的 equals
方法,直接使用==
操作符就可以了。
这其实也就给了我们另一种灵活性,比如我们想要两个同一类型的对象相等时,可以覆写==
操作符和 hashCode
方法,来实现我们某些用途。例如 Widget 是否要刷新,再比如我们在 Redux 中讲到的,由于每次 Redux 都会返回一个新的 State 对象,如果在实际数据没变的情况下要减少刷新,那么也可以这么操作。
有了上面的认识,我们来看在自定义对象相等判断时的注意事项。
如果覆盖==操作符的话,务必同时覆盖 hashCode 方法
默认的 hashCode 方法会产生一个唯一的 hash 值——这意味着正常情况下,只有两个对象是统一对象时,他们的两个哈希值才会相等。当我们要覆盖==操作符时,意味着我们对这个类的对象相等的判断有其他的定义。对象相等的原则必须满足二者同时具有相同的哈希值。因此,如果你不覆盖 hashCode 方法,意味着在某些场合会失效,比如 Map 以及其他基于哈希值判断的集合,即便两个集合里面的元素满足相等条件,但因为相等元素的哈希值不同,导致两个集合无法满足相等判断。
==操作符应该满足数学意义上的相等规则
数学上,相等需要满足三个规则:
- 反身性:即
a == a
应该始终返回true
。 - 对称性:若
a == b
那么 b == a 也应该为true
。 - 传递性:若
a == b
且b == c
,那么a == c
也应该为true
。
这意味着我们的 hashCode
方法或==操作符方法不能有基于条件来过滤对象的某些属性相等判断。比如跳过对象属性为 null 的情况,就可能导致啥给你们的三个规则中的某一条失效。
对于可变类,应该避免自定义相等判断
什么是可变类?就是对象的属性在运行过程中可能改变的类。因为,考虑上面的数学意义的相等规则,那么自定义相等在生成哈希值时,应当将对象的所有属性都考虑在内。而如果这些属性在运行过程中会被改变的话,那就意味着这个对象的哈希值是会变的。这其实违反了反身性原则(同一个对象,前后的哈希值不相等,这就好比你的女友换了个发型后你就认不出来一样,是要被打的),通时对于基于哈希值的集合来说,没法预料这种变化,这会导致集合的相等判断出错。
不要将==操作符的参数应用于可为空的对象
在Dart 中,null
只会与其自身相等,因此只有当被比较的对象不为空时才应该调用==
操作符进行相等判断。
// 正确示例
class Person {
final String name;
// ···
bool operator ==(Object other) => other is Person && name == other.name;
}
//错误示例
class Person {
final String name;
// ···
bool operator ==(Object? other) =>
other != null && other is Person && name == other.name;
}
注意:在 Dart 推出 null safety 版本以前,对象是允许为 null 的。即便是这样,Dart也不会使用 null 调用自定义的==方法进行相等判断(可以理解为 Dart 直接处理为 false 了)。因此,在非 null safety 版本(< 2.12版本),我们无需在==方法内处理 null。
总结
本篇介绍了 Dart 中对象相等的机制,以及自定义类对象相等判断的注意事项。大部分情况下,我们不会需要自己覆盖对象相等判断,但是在某些场合需要用到的时候,请遵循这些建议,以避免出现莫名其妙的问题。