你真的搞明白了 Dart 中两个对象相等的逻辑了吗?

戳这里了解《Flutter入门与实战》专栏,持续更新、系统学习!

前言

通常来说我们不会去实现类的自定义的相等判断,这个时候构建的任何对象因为值不同或 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 判断的。intdouble 都继承自数值类num。对于这个类型来说,hash code 就是数值本身,而==操作符实际使用的是 compareTo 方法进行判断的,因此只要两个数值类的值相等(特殊值除外,比如 double.nandouble.infinity),那么使用==比较符操作时就是相等的。

对于字符串类型来说也是一样,字符串的只要字符序列是一致的,那么 hash code就也是一致的。但是,对于 unicode 而言,如果使用的编码不同,那么 hash code 是不相等的。因此,在 Dart 中,比较字符串相等不需要使用类似 Java 的 equals 方法,直接使用==操作符就可以了。

这其实也就给了我们另一种灵活性,比如我们想要两个同一类型的对象相等时,可以覆写==操作符和 hashCode 方法,来实现我们某些用途。例如 Widget 是否要刷新,再比如我们在 Redux 中讲到的,由于每次 Redux 都会返回一个新的 State 对象,如果在实际数据没变的情况下要减少刷新,那么也可以这么操作。具体可以参考:Redux之利用 distinct 属性进行性能优化

有了上面的认识,我们来看在自定义对象相等判断时的注意事项。

如果覆盖==操作符的话,务必同时覆盖 hashCode 方法

默认的 hashCode 方法会产生一个唯一的 hash 值——这意味着正常情况下,只有两个对象是统一对象时,他们的两个哈希值才会相等。当我们要覆盖==操作符时,意味着我们对这个类的对象相等的判断有其他的定义。对象相等的原则必须满足二者同时具有相同的哈希值。因此,如果你不覆盖 hashCode 方法,意味着在某些场合会失效,比如 Map 以及其他基于哈希值判断的集合,即便两个集合里面的元素满足相等条件,但因为相等元素的哈希值不同,导致两个集合无法满足相等判断。

==操作符应该满足数学意义上的相等规则

数学上,相等需要满足三个规则:

  • 反身性:即a == a 应该始终返回 true
  • 对称性:若a == b 那么 b == a 也应该为 true
  • 传递性:若a == bb == 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 中对象相等的机制,以及自定义类对象相等判断的注意事项。大部分情况下,我们不会需要自己覆盖对象相等判断,但是在某些场合需要用到的时候,请遵循这些建议,以避免出现莫名其妙的问题。

猜你喜欢

转载自blog.csdn.net/shuijian00/article/details/125192117