【软件构造】课件精译(九)ADT与OOP中的等价性

一、什么是以及为什么要等价性

ADT中的操作等价性
AF决定了ADT中各操作的实现 ,可通过AF判定ADT中操作的等价。
数据类型中值的等价性

二、三种看待等价性的方式

通过AF或关系
AF(a)=AF(b)、离散数学中等价关系的定义
通过观察
从观察的角度,对两个对象的任何同一操作都会得到相同的结果 。

三、==与equals()

在这里插入图片描述
定义新数据类型时,需要考虑等价的含义,然后实现equals()方法。注意,重写equal时要确保签名一致,否则是重载。
在这里插入图片描述
返回值是false,事实上并没有成功实现重写。

四、不可变类型的等价性

在这里插入图片描述
这个例子可以看到,并没有成功重写equal,而是实现了重载,结果是根据参数的类型。o2是object类型会调用父类object的方法,d2则是Duration类型,会调用子类新的方法。所以,尽量使用override来进行重写。

除了用于实现equals()方法,尽可能避免使用 instanceof和getClass()在运行时检测对象的类型,存在安全隐患,可以使用更安全的替代方法!

五、对象约定

重写Object类中equals()时,需要满足约定:
满足等价性(自反,对称,传递)
一致性,在比较中用到的信息没有被修改的情况下,多次比较结果应始终相同
使用equals方法判定相等的两个对象,其hashCode必须产生相同的结果
equals约定
自反性、对称性、传递性、一致性、空值处理
equals是所有对象的全局等价关系(对所有对象都生效)
破坏等价关系
在这里插入图片描述
破坏哈希表
哈希表实现了键-值之间的映射。哈希表的查询时间为常数(线性),性能优于trees或lists,且不需要排好序或其他特殊要求,除了需要提供equals和hashCode方法。
键值对中的key被映射为hashcode,对应到数组的index, hashcode决定了数据被存储到数组的那个位置。
哈希表的表示不变量包括基本约束,即键位于由其哈希代码确定的槽中。
hashcode设计时会尽量确保均匀分布到数组,当多个key散列到同一个index时(冲突),哈希表维护一个列表来记录这些键值对(bucket) 。
匹配过程中,首先利用hashCode()产生的hashcode确定slot(index) ,再用equals()方法在bucket中找到匹配的key。
哈希码约定
只要比较操作用到的信息没有被修改,那么对这同一个对象调用多次,hashCode()方法必须始终返回相同整数。
不要求程序的多次执行时相同。
如果equals比较相等,则要求hashcode相等;如果equals不等,则hashcode是否相等均可,但最好不等(提升性能,避免了调用不必要的equals操作)
构造hashcode时考虑对象的所有字段,以避免不相等对象产生相同hashcode
除非对象可变,否则hashcode不能修改。
重写哈希码
最简单方法:让所有对象的hashCode为同一常量,符合contract,但降低了hashTable效率。
或者,复杂一点通过equals计算中用到的所有字段的hashCode组合出新的hashCode。
大部分语言中都是采用对象的内存地址作为默认hashcode。
如果没有重写hashCode(),就很可能打破对象约定。所以,当重写equals()时也要重写hashCode()。例如:
在这里插入图片描述
总之,在重写equals时要遵守约定,并且要同时重写hashCode方法。

六、可变类型的等价性

可变类型的等价性分为以下两种:
观察等价:在不改变对象状态的情况下(不使用mutator),无法区分对象
行为等价:改变一个对象而不改变另外一个时,仍然无法区分对象
对于不可变对象,两者是一致的,因为其不存mutator方法。
对于可变对象,使用观察等价性貌似可行,但是会带来潜在的问题,例如下面的例子:
在这里插入图片描述
对于这段代码,set.contains(list)的结果是true,而当list.add(“goodbye”)以后,结果则是false。但当我们迭代set中的成员时,会发现list仍在里面,但是contains判断为不在。
之所以有上面的现象,是因为List是可变对象,在Java标准库中,像List这样的collection,mutation影响equals和hashCode的结果。当list第一次放入HashSet时,仍存储在当时哈希码对应的位置。当加入元素后,hashCode改变了,但是HashSet并没有将元素移动到其他位置,所以就无法找到对应的元素,contains也就返回false。当这两个方法被mutator影响后,我们将破坏将对象作为键值的表示不变性。
格外关注:可变对象作为集合元素的时候,对象值的变化会对相等比较产生影响。
通过这个例子,可以得出以下结论:
Equals()应该实现行为等价
两个引用相等意味着它们指向了同一个对象
可变类型应该继承Object类的equals()和hashCode()
客户端如果需要判断两个对象的观察等价性,可以重新定义一个方法

七、equals()和hashCode()最后的规则

不可变类型应该重写equals()和hashCode()
可变类型不应该重写equals()和hashCode() ,采用Object默认实现的即可

八、自动封装和等价性

Integer x= Integer.valueOf(300); 
Integer y= Integer.valueOf(300); 

x.equals(y)//true
x == y //falseint)x == (int)y //true

再看下面这个例子:

Map<String, Integer> a = new HashMap<>(), b = new HashMap<>();
a.put("c", 130);
b.put("c", 130);

a.get("c") == b.get("c")//false
Integer x = Integer.valueOf(30);
Integer y = Integer.valueOf(30);

x.equals(y)//true
x == y//true

这是因为,为了提高性能,Java缓存了从-127到127的对象,如果改成3000,那么第二个的结果就是false了。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值