前言
在一些场景下,需要我们判断两个对象是否相等,而Java本身所提供的“==”常常并不适用,那么应该如何判断两个对象的等价性?
抽象函数(aebstraction function,AF)
AF将ADT内部的数据表示映射为抽象的表示以供客户端使用。客户端并不需要知道ADT内部数据的真实表示,只需知道经AF映射后的抽象空间的数据即可。
equals()方法
基于上述观念,站在外部观察者角度,当观察到的ADT的数据相同时,认为两个对象等价即可。比如客户端观察到ADT内部只有一个数组,存储需要的信息。那么当两个对象的数组存储内容和下标相同时,我们认为两个对象 等价即可。至于外部不需要关注的数组的地址之类的信息,并不会作为判断两个对象是否相同的依据。
要实现上述效果,我们需要使用Object类的equals()方法。通过在ADT内部重写equals()方法,当判断两个对象等价时返回true,否则,返回false。这样就实现了判断两个对象的等价性。
等价分两种情况:观察等价性和行为等价性,观察等价性要看对象内部变量是否相同,形同即可认为等价;行为等价性要求调用对象的任何方法都展示出一致的结果即认为等价,通常也可以根据内存地址判断。
由于等价性具有不同类型。因此对于如何写equals()方法,要分情况看待。
首先,对于不可变数据类型,倾向于实现观察等价性:站在观察者角度,利用AF,当抽象空间观察到的结果形同即可认为对象等价。一个例子为:
在这个例子中,People是immutable的,当在外部观察到抽象空间表示结果相同,即可认为等价。
对于可变数据类型,倾向于实现严格的管擦和等价性,但有些时候,观察等价性会造成一些bug,看下边这个例子:
//观察等价性的bug
List<String> list = new ArrayList<>();
list.add("a");
Set<List<String>> set = new HashSet<>();
set.add(list);
System.out.println(set.contains(list));//true
list.add("b");
System.out.println(set.contains(list));//false
这是因为,当list发生变化时,其hashCode也会发生变化,但是set并不会随之改变,因此造成了错误的观察结果。
因此,对于可变数据类型,也可以实现行为等价性。
总结如下:
hashCode()方法
以上方法存在一个缺陷:两个等价的对象可以具有不同的hash地址,这并不是很符合我们对“等价”的理解(认为是一个对象)。为了让两个对象等价时具有相同的hashcode。我们需要一种新的hash函数来实现,这就需要在ADT内部重写hashCode()方法。
hashCode()方法的设计多种多样,只要能满足要求即可。一种常见的做法是,使用对象的成员变量值生成对象的hashCode,这样就保证了当两个对象的成员变量属性值相同时(也就是等价时),两个对象具有相同的hashCode。
需要注意的一点是,hashCode方法的书写要求等价的对象具有相等的hashCode,但不要求hashCode相等的对象等价。也就是说,可以不等价的对象也可以有相等的hashCode,称之为哈希冲突。