第八章 ADT和OOP中的 “等价性”
第八章 ADT和OOP中的 “等价性”
等价关系
- 自反、对称、传递
== vs. equals()
Java的两种操作测试等价性
==
- == 运算符比较引用。 它测试引用相等性。 如果两个引用指向内存中的相同存储,则它们是 ==。 就快照图而言,如果两个引用的箭头指向同一个对象气泡,则它们是 ==。
- 基本数据类型必须使用==
- 对象数据类型如果用==,是在判断两个对象身份标识 ID是否相等(指向内存里的同一段空间)
equals()
-
比较对象内容
-
当我们定义一个新的数据类型时,我们有责任决定对象相等对于数据类型的值意味着什么,并适当地实现 equals() 操作。
-
定义在Object中,默认实现方法是return this==that,即判断引用等价性
-
重写方法
- 注意:参数一定要是Object,否则在一些泛型方法中可能会出问题,而且不能跨类型比较,即使是同一类型的强转。(避免这种错误可以用@override修饰)
- 步骤 判null、利用instanceof或getClass()判类型、最后根据AF判相等
- 除了equals方法外,避免使用instanceof和getClass(),可以用多态来代替
-
重写equals方法遵循的原则
-
等价关系:自反、传递、对称
-
除非对象被修改了,否则调用多次equals应同样的结果
-
对于非空引用 x , x.equals(null) 应该返回 false;
-
除非你能保证你的ADT不会被放入到Hash类型的集合类中,否则一定要重写hashCode()
-
“相等”的对象,其hashCode()的结果必须一致
- 如果两个相等的对象具有不同的哈希码,则它们可能被放置在不同的插槽中。因此,如果尝试使用与插入时相同的键来查找值,则查找可能会失败。Object 的默认 hashCode() 实现与其默认的 equals() 一致,即返回对象内存中的地址
-
不相等的对象,也可以映射为同样的hashCode,但性能会变差
-
Immutable类型的等价
使用AF
-
AF映射到同样的结果,则等价
- 从用户的角度看
观察等价性
- 站在外部观察者角度:对两个对象调用任何相同的操作,都会得到相同的结果,则认为这两个对象是等价的。反之亦然!
- 没有观察器等方法时是无法使用观察等价性的
AF和观察等价性判断出来的结果可能不同
- 这种AF相等,但是观察等价性判定不相等的观察器应该避免存在
Immutable类型观察等价性和行为等价性一致
- 因为没有任何mutator方法
建议
- 一定要重写equals()和hashCode()
Mutable类型等价性
观察等价性
-
当无法通过不改变对象状态的观察来区分它们时,即仅调用观察者、生产者和创建者方法。
-
某个时间点
-
当mutable类型使用观察等价性可能会出错
- HashSet
行为等价性
-
调用对象的任何方法都展示出一致的结果
-
时间段
- 此状态和未来所有状态
-
只有指向相同的内存空间的对象才是相等
建议
- 对可变类型,实现行为等价性即可
- 无需重写equals()和hashCode(),直接继承Object 的两个方法即可。
- 可以定义一个新方法similar()来判断观察等价性
示例
Java自动装箱
-
Java基本数据类型在一些情况下会自动装箱,例如,在使用基本数据类型作为泛型时List l=new ArrayList<>();l.add(1);这里1被装箱了,若两个装箱的对象前加(int)强转就会自动拆包
-
装箱后变成一个对象,应该使用equals()进行比较,使用==会判断是否是同一个引用
单例模式
-
如果对象不存在,就会创建对象,如果对象存在就会直接应用同一个对象
-
注意:new 对象 不会使用单例模式
-
字符串和-128到127之间的整数会使用单例模式
-
示例