Dart中你真的知道怎么去判断两个对象是否相等?

913 阅读3分钟

今天在开发的过程中,有一个需求:将一些对象数据存储在变量中,相同数据进行去重。

我首先想到的是dart中的内置数据类型SetSet是一个无序且元素唯一的集合。 原本我是想用List的,但是去重我还需要遍历它来处理,比较麻烦。而使用Set就方便得多,最终还可以调用toList方法转成List

于是,我通过字面量的方式创建Set集合,然后将t1t2t3对象adddata里。

class TestObject {
  final String? a;
  final String? b;
  
  const TestObject({this.a, this.b});
}
void main() {
  var t1 = TestObject(a: 'a', b: 'b');
  var t2 = TestObject(a: 'a', b: 'b');
  var t3 = TestObject(a: 'a', b: 'c');
  
  Set<TestObject> data = {};
  data.add(t1);
  data.add(t2);
  data.add(t3);
}

我原以为t1应该和t2是相等的,会被过滤掉,data中只剩下t2t3,但结果却出乎意料,三个对象都在data中,t1t2也并不相等。

WX20221216-154903@2x.png

接着,我将t1t2t3改为常量对象,因为创建它们的类是一个常量构造函数,我可以使用const修饰符设置常量对象。

class TestObject {
  final String? a;
  final String? b;
  
  const TestObject({this.a, this.b});
}
void main() {
  var t1 = const TestObject(a: 'a', b: 'b');
  var t2 = const TestObject(a: 'a', b: 'b');
  var t3 = const TestObject(a: 'a', b: 'c');
  
  Set<TestObject> data = {};
  data.add(t1);
  data.add(t2);
  data.add(t3);
}

这一次,data中只剩下t1t3t2被认为跟t1重复,并没有添加进来。

image.png

我这才意识到,Set并不能简单的对对象进行去重处理,而是要认为对象相等的情况下才能进行数据去重。那对象之间是怎么判定是否相等的呢?以下是我通过研究,总结出的一些判定要点:

1、同一个类创建的实例对象才有比较的价值

通常情况下,每一个实例对象都自带一个随机的hashCode属性,当比较两个对象是否相等时,实际上就是对比hashCode的值是否相等。而由不同类创建出来的实例对象,hashCode的值永远都是不相同的,所以它们无法进行比较。这里所说的比较就是单纯的说 a == bab同时引用了一个内存地址。

2、同一个类创建出的常量实例对象,只要它们的属性和值一致,它们就是相等的

不知道你是否明白什么是常量构造函数和常量实例对象?首先,常量构造函数需要以const关键字修饰。其次,它的成员属性都必须由final关键字修饰。最后,实例化时不加const修饰符,即使调用的是常量构造函数,实例化的对象也不是常量实例。

通过同一个类创建出来的常量实例对象,属性和值一样,hashCode的值是一致的,也就是说它们所指向的内存地址一致。所以通过 == 比较,它们是相等的。

void main() {
  var t1 = const TestObject(a: 'a', b: 'b');
  var t2 = const TestObject(a: 'a', b: 'b');
  var t3 = const TestObject(a: 'a', b: 'c');
  
  print('${t1 == t2}'); // true
  print('${identical(t1, t2)}'); // true
  print('${t1 == t3}'); // false
  print('${identical(t1, t3)}'); // false
}
tips: 
1、在Flutter中使用const修饰的组件可以减少内存开销,每次重新构造时,都不会重新构建const组件。 
2、identical方法用于检查两个引用是否指向同一个对象(指向同一个内存地址)
    

3、同一个类创建出的非常量实例对象,可能通过重载操作符“==”和hashCode方法让它们相等。

dart中可能通过operator关键字对==进行重载,同时也要将hashCode一同重载。这样我们就可以自己编写对象相等逻辑,让原本不相等的两个对象在某些条件下认为是相等的。

class TestObject {
  String? a;
  String? b;
  
  TestObject({this.a, this.b});
  
  /// 通过operator关键字重写==方法
  @override
  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is TestObject && a == other.a && b == other.b;
  }

  /// 重写hashCode方法
  @override
  int get hashCode => a.hashCode + b.hashCode;
}
void main() {
  var t1 = TestObject(a: 'a', b: 'b');
  var t2 = TestObject(a: 'a', b: 'b');
  var t3 = TestObject(a: 'a', b: 'c');
  
  print('${t1 == t2}'); // true
  print('${identical(t1, t2)}'); // false 
  print('${t1 == t3}'); // false
  print('${identical(t1, t3)}'); // false
}