等价关系(Equivalence Relation)
现实中每个对象都是独特的,并不存在完全相等,但存在对“等价性”的需要。
数学中完全相等存在
等价关系问题即为在软件领域中界定判断为相等:等价、可相互替代的
数学中认为等价关系为“自反、对称、传递”的关系。
判断等价的三种方法
ADT中等价:抽象函数AF
在ADT中,抽象函数AF映射到同样的结果(AF(a)==AF(b))=两对象等价.
外部观察者角度:
对两个对象执行任何相同操作,得出的结果都相同,则认为两对象等价。(为充要关系)
==和.equals()
Java下有==和.equals()两种操作来测试相等性,其有不同原理和用法。
==是引用等价性
其为真当且仅当两个引用指向内存中相同位置(快照图中箭头指向同一个气泡)
.equals()是对象等价性
按照这一方法的内部程序比较对象的内容。自定义ADT时需要重写Object的.equals()方法(其初始使用==判断),即在创建一个新数据类型时也需要给出两个该对象相等的判断条件。重写最好写出@Override,并确保签名匹配,复制Spec
instanceof
该操作符测试对象是否为特定类型的实例,属于动态类型检查,最好不要用在除equals之外。建议用多态取代instanceof
对象的契约
equals()的契约
当重写equals()方法时,必须遵守其契约:
满足自反、传递、对称的等价关系
除非对象被修改了,否则调用多次equals()应同样的结果
“相等”的对象,其hashCode()的结果必须一致
对于任何非空引用值x,x.equals(null)必须返回false。
equals是一个在所有对象上的全局等价关系,用“是否为等价关系”检验你的equals()是否正确。
hashcode()的契约
每当在应用程序执行过程中对同一对象进行多次调用时,hashCode方法必须一致地返回相同的整数,只要不修改在该对象的平等比较中使用的信息。
如果两个对象根据 equals(Object) 方法相等,那么对这两个对象分别调用hashCode()方法必须产生相同的整数结果。
等价的对象必须有相同的hashCode。
不相等的对象,也可以映射为同样的hashCode,但性能会变差。
相等的对象必须有相等的哈希代码
如果重写equals(),必须也重写hashCode()
不相等的对象应该有不同的哈希代码
在构造它时考虑到所有的值字段
除非对象突变,哈希代码必须不改变
可变类型的等价性
分为观察等价性和行为等价性
- 观察等价性:在不改变状态的情况下,两个可变对象是否看起来一致
- 行为等价性:调用对象的任何方法都展示出一致的结果
注意:对于不可变对象,观察性和行为性相等是相同的,因为没有任何mutator方法。
对可变类型来说,往往倾向于实现严格的观察等价性。
Java对其大多数可变数据类型(如集合)使用观察性相等,但其他可变数据类(如StringBuilder)使用行为相等。
如果两个不同的List对象包含相同的元素序列,则equals() 表示它们相等。
但在有些时候,观察等价性可能导致bug,甚至可能破坏RI
在JDK中,不同的mutable类使用不同的等价性标准:
观察等价性
- Date类的equals()
- List类的equals()
行为等价性
StringBuilder类的equals()
对可变类型,实现行为等价性即可。也就是说,只有指向同样内存空间的objects,才是相等的。所以对可变类型来说,无需重写这两个函数,直接继承Object的两个方法即可。
如果一定要判断两个可变对象看起来是否一致,最好定义一个新的方法。
总结:
- 不可变类型:重写equals()和 hashCode()
- 可变类型:不该重写equals()和 hashCode()
自动包装和等价性
原始类型及其对象类型的等价物,例如,int和Integer。
如果创建两个具有相同值的Integer对象,它们将满足equals() ,但是不满足==。
但是强制转换为int后,就可以满足==。