第八章 ADT和OOP中的相等
作者:互联网
第八章 ADT和OOP中的相等
Reading Sources
Objectives
理解等价关系的性质
站在观察者的角度通过AF理解不可变类型的等价关系
引用等价性和对象等价性的差异
区分可变类型的严格观察等价性和行为等价性
理解Object的契约,正确实现可变和不可变类型的等价关系判定
等价关系
自反 对称 传递
不可变类型的相等
使用AF描述相等
AF映射到同样的结果则等价
f(a) = f(b) --> a和b等价
等价关系引出了抽象函数,抽象函数将每个元素映射到不同的区分类
即由抽象函数引起的关系就是等价关系
使用观察来描述相等
从客户的角度:
使用观察。当两个对象不能通过观察区分时,我们可以说它们是相等的——>即我们可以应用的每个操作都会为两个对象产生相同的结果。
在 ADT 中,“观察”是指对对象调用操作。所以两个对象相等当且仅当它们不能通过调用抽象数据类型的任何操作来区分。
实例
== Vs equals()
==:引用相等
equals(): 对象内容相等
当我们定义一个新的数据类型时,我们有责任决定对象相等对数据类型的值意味着什么,并适当地实现 equals() 操作
比较
对基本数据类型,使用==判定相等
对对象类型,使用equals()
如果用==,是在判断两个对象 身份标识 ID是否相等(指向内存里的同一段空间)
实现equals()
可变类型的相等
在Object中实现的缺省equals()是在判断引用等价性
-->需要重写
注:不是重载
注意下面的问题
有上一章的内容:
要调用的方法的哪个重写版本是在运行时根据对象类型决定的,但是要调用的方法的哪个重载版本是基于编译时传递的参数的引用类型。所以这里实际调用的是Object实现的缺省equals()
-->在重写时为了避免方法签名,应使用 Java 的注解 @Override。使用此注解,Java 编译器将检查超类中是否确实存在具有相同签名的方法,如果您在签名中犯了错误,则会给您一个编译器错误
重写equals()的例子
instanceof
instanceof 运算符测试对象是否是特定类型的实例。
使用 instanceof 是动态类型检查,而不是静态类型检查。
通常,在面向对象编程中使用 instanceof 是a bad smell。除了实现 equals 之外,任何地方都应该禁止它。
此禁令还包括检查对象运行时类型的其他方式。
– 例如,getClass() 也是不允许的。
-->从不在超类中使用 instanceof 来检查子类的类型
如下所示:
使用多态来避免上面的instanceof:
Object契约
重写equals()应该遵循的契约:
- equals()应该定义一种满足自反,传递,对称的等价关系
- 除非对象被修改了,否则调用多次equals应同样的结果
- for a non-null reference x ,x.equals(null) should return false;
- “相等”的对象,其hashCode()的结果必须 一致
Hash Tables
§ 哈希表是如何工作的: – 它包含一个数组,该数组被初始化为与我们期望插入的元素数量相对应的大小。
– 当一个键和一个值出现插入时,我们计算键的哈希码,并将其转换为数组范围内的索引(例如,通过模除法)。然后将该值插入该索引处
哈希表的 rep 不变量包括键值位于由其哈希码确定的槽中的基本约束。
等价的对象必须有相同的hashcode
why:
如果两个相等的对象具有不同的哈希码,它们可能被放置在不同的槽中。
– 因此,如果您尝试使用与插入值相同的键来查找值,则查找可能会失败。
Object 的默认 hashCode() 实现与其默认的 equals() 一致
-->
Always override hashCode() when you override equals() 除非你能保证你的ADT不会被放入到Hash类型的集合类中
可变类型的相等
两种方式:
观察等价性(通过仅调用观察者、生产者和创建者方法。):在不改变状态的情况下,两个mutable对象是否看起来一致
行为等价值性:调用对象的任何方法都展示出一致的结果
注意:
1.对于不可变对象,观察和行为平等是相同的,因为没有任何 mutator 方法。
2.如果某个mutable的对象包含在Set集 合类中,当其发生改变后,集合类的行为不确定,务必小心
如下实例:
原因分析:
- List是一个可变对象。在 List 等集合类的标准 Java 实现中,突变会影响 equals() 和 hashCode() 的结果。
- 列表第一次放入HashSet时,存储在哈希桶哈希/散列桶中,对应于其当时的hashCode()结果。
- 当列表随后发生变异时,它的 hashCode() 会发生变化,但 HashSet 没有意识到它应该被移动到不同的存储桶中。所以再也找不到了。
教训:
当equals() 和hashCode() 可能受突变影响时,我们可以打破使用该对象作为键的哈希表的rep 不变量。
- 在JDK中,不同的 mutable类使用不同的等价性标准…
总结:
clone()
clone() 创建并返回此对象的副本。
“复制”的确切含义可能取决于对象的类别。
自动封装和相等
装箱就是自动将基本数据类型转换为包装器类型;
当把一个基本数据类型的数据赋值给对应的包装器类的时候。
当把一个基本数据类型的数据作为参数传给一个方法,而这个方法想要接收的参数是该基本数据类型对应的包装器类的对象。
拆箱就是 自动将包装器类型转换为基本数据类型。
当把一个包装器类的对象赋值给一个基本数据类型的时候。
当把一个包装器类作为参数传给一个方法,而这个方法想要接收的参数是基本数据类型的时候。
实例
对于上图中的:Numbers between -128 and 127 are true.
原因:
总结
相等是实现抽象数据类型 (ADT) 的一部分。
-
相等应该是一种等价关系(自反的、对称的、传递的)。
-
相等和哈希码必须相互一致,这样使用哈希表的数据结构(如 HashSet 和 HashMap )才能正常工作。
-
抽象函数是不可变数据类型相等的基础。
-
引用相等是可变数据类型相等的基础;这是确保随着时间的推移保持一致性并避免破坏哈希表的代表不变量的唯一方法。
避免错误
- 正确实现相等和哈希码对于使用集合数据类型(如集合和映射)是必要的。它也非常适合编写测试。由于 Java 中的每个对象都继承了 Object 实现,因此不可变类型必须覆盖它们。
易于理解
- 阅读我们规范的客户和其他程序员会期望我们的类型实现适当的相等操作,如果我们不这样做,他们会感到惊讶和困惑。
随时准备改变
- 正确实现不可变类型的相等性将引用的相等性与抽象值的相等性分开,对客户隐藏我们关于值是否共享的决定。为可变类型选择行为相等而不是观察相等有助于避免意外的别名
标签:ADT,相等,对象,数据类型,equals,第八章,OOP,哈希,类型 来源: https://www.cnblogs.com/ro2phy/p/16342202.html