ADT和OOP中的“等价性”
等价关系
现实中的每个对象实体都是独特的,所以无法完全相等,但有“相似性”,在人类语言和数学中,“绝对相等”是存在的
等价关系:自反、对称、传递
重视平等的三种方式
1.ADT上的相等运算
ADT是对数据的抽象, 体现为一组对数据的操作
对于抽象数据类型,抽象函数(AF)解释了如何将具体表示值解释为抽象类型的值,我们看到了抽象函数的选择如何决定如何编写实现ADT的每个操作,抽象函数AF:内部表示→抽象表示
基于抽象函数AF定义ADT的等价操作
2.使用AF定义相等
抽象函数AF:R→A将数据类型的具体实例映射到它们对应的抽象值。
使用AF作为等式的定义,我们说a等于b当且仅当AF(a)=AF(b)映射到同样的结果,则等价
使用AF和使用关系来定义相等是等效的。
等价关系引发了一个抽象函数(该关系划分T,因此AF将每个元素映射到其划分类)。
由抽象函数引发的关系是等价关系。
3.用观察来定义等式
站在外部观察者角度:对两个 对象调用任何相同的操作,都 会得到相同的结果,则认为这 两个对象是等价的。 反之亦然!
相等的不同表示:==与equals()
==运算符比较引用。它测试引用相等性。如果两个引用指向内存中的相同存储,则它们为==。就快照图而言,如果两个引用的箭头指向同一个对象气泡,则它们为==。引用等价性
equals()操作比较对象内容。对象等价性
在自定义 Object 的 ADT 时,需要根据对“等价”的要求,决定是否重写 equals()
== 对基本数据类型,使用==判定相等;对对象类型,使用equals()
实现equals()
在Object 中实现的缺省 equals() 是在判断引用等价性,不是程序员所希望的,需要重写
Java编译器使用参数的编译时类型。静态类型检查
instanceof
判断某个对象是不是特定类型(或其子类型)
动态类型检查,不是静态类型检查
除了用于实现 equals() 方法,尽可能避免使用 instanceof 和 getClass () !
对象之间的合约
对象中equals()中的合约
等价关系:自反、传递、对称
除非对象被修改了,否则调用多次equals 应是同样的结果
“相等”的对象,其hashCode() 的结果必须一致
打破等价合约
我们必须确保equals()实现的等式定义实际上是前面定义的等价关系:自反、对称和传递。
如果不是,那么依赖于相等的操作(如集合、搜索)将表现得不稳定和不可预测。
破坏哈希表
哈希表是映射的表示:一种将键映射到值的抽象数据类型。
哈希表提供了恒定的时间查找,因此它们往往比树或列表执行得更好。除了提供equals和hashCode之外,密钥不必排序,也不必具有任何特定属性。
哈希表的工作方式:
-它包含一个数组,该数组被初始化为与我们期望插入的元素数量相对应的大小
-当一个键和一个值被提供用于插入时,我们计算该键的哈希码,并将其转换为数组范围内的索引(例如,通过模除法)。然后将该值插入该索引决定了数据被存 储到数组的哪个位置
哈希表的rep不变量包括密钥位于由其哈希代码确定的槽中的基本约束。
哈希表中基本要求就是密钥槽散列码
hashCode合约
在应用程序的执行过程中,每当对同一对象多次调用hashCode方法时,只要不修改对象上的equals比较中使用的信息,hashCode方法就必须始终返回相同的整数。程序中多次调用同一对象的 hashCode方法,都要返回相同值
从一个应用程序的一次执行到同一应用程序的另一次执行,该整数不需要保持一致。不要求程序的多次执行时相同
如果根据equals(Object)方法,两个对象相等,那么对这两个对象中的每一个调用hashCode方法必须产生相同的整数结果。等价的对象必须有相同的hashCode
然而,程序员应该意识到,为不相等的对象生成不同的整数结果可能会提高哈希表的性能。不相等的对象,也可以映射为同样的hashCode
1.相等的对象必须具有相等的哈希代码:如果覆盖equals,则必须覆盖hashCode
2.不相等的对象应具有不同的哈希代码:构造时考虑所有值字段
3.除非对象发生突变,否则哈希代码不得更改
破坏哈希表
为什么Object合约要求相等的对象具有相同的哈希代码?
-如果两个相等的对象具有不同的哈希代码,则它们可能被放置在不同的槽中。
-因此,如果您试图使用与插入值的键相同的键来查找值,则查找可能会失败。
重写hashCode
最简单方法:让所有对象的hashCode为同一常量,符合contract,但降低了hashTable效率
通过 equals计算中用到的所有信息的hashCode组合出新的hashCode
两个equal的objects,一定要 有同样的hashcode.
重写equals()时始终重写hashCode()
除非你能保证你的ADT哈希类型的集合类中
可变类型的相等性
观察等价性: 在不改变状态的情况下, 两个 mutable 对象是否看起来一致
行为等价性:调用对象的任何方法都展示出一致的结果
对可变类型来说,往往倾向于实现严格的 观察等价性
Collections Java 对其大部分可变数据类型(如 )使用观察等价性,如两个 则 equals() 返回 true List 中包含相同顺序的元素, 。但是部分可变类型用行为等价性。
但在有些时候,观察等价性可能导致 bug ,甚至可能破坏 RI
equals()和hashCode()的最终规则
对于不可变类型:
equals()应该比较抽象值。这与equals()应该提供行为平等是一样的。
hashCode()应该将抽象值映射到一个整数。
因此,不可变类型必须覆盖equals()和hashCode()。
对于可变类型:
equals()应该比较引用,就像=一样。同样,这与equals()应该提供行为平等是一样的。hashCode()应该将引用映射为一个整数。
因此,可变类型根本不应该覆盖equals()和hashCode(),而应该只使用Object提供的默认实现。不幸的是,Java在其集合中没有遵循这一规则,这导致了我们在上面看到的陷阱。
自动包装与等价
基元类型及其对象类型等价物,例如int和整数
如果创建两个具有相同值的Integer对象,它们将是彼此相等()