Software Construction学习——ADT和OOP的相等性

在很多场景下,需要判定两个对象是否“相等”,例如,判断某个Collection中是否包含特定元素。

“==”和“equals()”有何区别?如何定义ADT正确实现equals()

一.    什么是等价性(Equality)

ADT是对数据的抽象,体现为一组对数据的操作。而抽象函数AF是将内部表示R转换为抽象表示A。而等价性就是基于AF来定义的。

现实中每个对象都是独特的,所以无法有完全相等,但有“相似性”,但是在数学中,“绝对相等”是存在的


二.    三个判断等价性的方法

1.    使用AF来判定是否等价——如果AF映射到同样的结果,那么二者等价

2.    等价关系(满足自反、传递、对称)

    -这二者实际上是相同的

        ·    等价关系引出了一个抽象函数

        ·    抽象函数所引起的关系是等价关系

3.    客户端所观察到的结果是相同的,则二者等价。

    e.g.    考虑两个集合{1, 2}和 {2, 1}。这二者在客户端来看显然是等价的。但是其内部的表示可能不完全相同

e.g.



如果根据AF来判断,只有d1和d4是等价的,但是如果站在客户端角度来看,调用getLength这个观察器,d1,d3,d4的结果都是相同的,所以三者也是相同的。


三.    ‘==’    vs.    equals()

==:在Java之中这是用来判断引用等价性(referential equality)

equals():在Java之中这是用来判断对象等价性(object equality)

而对于不同的ADT时,判断相等的条件也不尽相同,因此需要重写equals()



对于基本数据类型,一般使用==来判断相等;

但是对于对象数据类型,使用equals()来判断相等。

    ·    如果使用==,是在判断两个对象的身份标识ID是否相同(指向内存之中同一段空间)

    ·    最好使用equals()来判断

在对象引用之中使用==是一种糟糕的选择

e.g.    


四.    不可变类型的等价性

在Object之中默认的equals()是在判断引用等价性。但是这通常不是程序员所期望的,因此需要重写。

e.g.



我们要判断d1和d2、o2的等价关系,要针对Duration类之中的equals来判断。很显然,Duration之中有两个equals(),一个的参数是Duration类,另一个是Object类,这是因为Duration是Object的子类,它继承了Object类之中的equals(),而Duration中的equals()是对Object类中的equals()的重载。


因此在d1调用equals来判断时,会在运行时发生动态分派。d1调用的equals()传递的参数类型是Object则会调用下面的equals(),如果是Duration类的则会调用上面的equals()。因此结果是



instanceOf()——这是用来判断一个对象是不是某一个类型的操作。对于instanceOf的操作是一个动态类型检查,而不是静态类型检查。通常情况下,在OOP之中使用instanceOf是一个糟糕的选择,除了实现equals之外,它在任何地方都应该被禁止。这种对于探测对象运行时的类型的操作的禁止也包括getClass()


五.    Object的“合同”

equals():当重写equals()时,必须遵守它的一般规则

·    equals必须是一个等价关系——它满足自反的,对称的,传递的

·    equals应当是一致的(consistent)——对于equals的多次调用的结果应当是一样的

·    对于空指针(null)的引用所调用的equals必须返回false

·    如果两个对象相同,那么它们的hashCode也必须相同

     -    hashCode确定了不同对象在哈希表之中的位置,而为了提高哈希表的效率,哈希表之中的元素最好均匀分布,即不同对象hashCode最好不相同,但是相同的对象,它们的hashCode必须相同,不然在哈希表之中的位置就不是同一个位置。

e.g.    

    -    因此在重写equals的时候一定要重写hashCode


六.    可变类型的等价性

观察等价性(observational equality):在不改变对象状态的情况下,两个mutable对象看起来是否一致

行为等价性(behaviorial equality):调用对象的任何方法都展现出一致的结果

注意:不可变类型之中观察等价性和行为等价性是相同的,因为不可变类型没有变值器(Mutator)

对于可变类型来说,往往倾向于实现严格的观察等价性

但在有些时候,观察等价性可能导致bug,甚至可能破坏RI

e.g.


然后我们判断set之中是否含有list

接下来我们在list之中添加一个新的元素

然后在判断set之中是否含有list

原因是:在list调用了add这个Mutator之后,它的hashCode发生了变化,但是存放它的哈希表并没有意识到要将这个list放到一个新的地方,因此就无法再次根据哈希表来查找这个list。

用一个更形象的例子来说明:你在派出所申领了身份证,留了当时的照片;几个月以后,你“整容”了(mutated),你用乘机的时候就无法匹配到你的身份证照片了。

在Java之中Collections是使用观察等价性,但是其它可变类(像是StringBuilder)是使用行为等价性。

·    对可变类型,实现行为等价性即可;也就是说只有指向相同的内存空间才是相等的。

·    所以对可变类型来说,无需重写这两个函数,直接继承Object的方法即可

·    如果一定要判断两个可变类型是否相等,最好定义一个新的方法


七.    自动封装(Autoboxing)和等价性

基本数据类型和他们的对象数据类型是等价的    e.g.    int 和 Integer


但是x==y -> false;因为==是引用等价性

但是对于(int)x == (int) y -> true

e.g.


显然最后的判断是false,因为a.get()所返回的类型是Integer,而==是比较引用等价性,所以是false。


资料来源    MIT6.031    哈工大软件构造课程

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值