软件构造中ADT的等价性

目录

一、前言

一、Immutable类型的等价性

1.1 "=="表达引用等价性

1.2.equals()表达对象等价性

1.3重写equals()方法

二、Mutable类型的等价性

2.1观察等价性

2.2 行为等价性 

 三、总结


一、前言

在很多场景下,需要判定两个对象是否 “相等” ,例如:判断某个 Collection中是否包含特定元素。 同时,在Java中,==和equals()是存在区别的,那么如何为自定义ADT正确实现equals()?

对于等价关系,它应该满足自反、对称、传递的特性。下图给出一些解释:

在如何定义等价性上,一般有两种角度。

  • 从抽象函数AF的视角,AF映射到同样的结果,则等价;
  • 站在观察者角度,对两个对象调用任何相同的操作,都会得到相同的结果,则认为这两个对象是等价的

在Java中,由于存在两种数据类型Immutable和Mutable,其对于等价的判断也是不相同的。

一、Immutable类型的等价性

不可变类型的等价性判断通常是判断其引用等价性和对象等价性,关于引用等价性和对象等价性又具有不同的判断点。

1.1 "=="表达引用等价性

用"=="来判断等价性,实际上考量的是两个对象在内存中的地址空间是否一致,对于Immutable类型的对象来说这又被称为引用等价性。一般情况下,对于基本数据类型的判断如int, long, double等都会使用"=="来判断引用等价性。

如果两个对象是引用等价的,那么这两个对象其实就是一个对象,那么他就会满足所有的等价性。

1.2.equals()表达对象等价性

用".equals()"方法来判断等价性,要求就没有"=="那么高,也就是说,满足等价的定义即可。两个对象不一定要是同一个对象,只要这两个对象在我们所关心的维度是等价的就行了。用equals()方法来判断对象的等价关系是基于实际情况的可以个性化实现的。一般情况下,对于对象类型的数据都会使用equals()方法来判断等价性。

1.3重写equals()方法

equals()方法是继承自Object()这一父类的,而在Object()中equals()方法是实现引用等价性的,这常常不是程序员们所需要的。于是在写一个继承自Object()父类的ADT时,要对equals()方法进行重写。重写之后一定要保证重写后的equals()方法判断的等价性满足定义以及自反、传递、对称的特性。以下是一个重写的简单实例。

public final class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
@Override
public boolean equals(Object o) {
if (!(o instanceof PhoneNumber))//进行类型比较和null值判定
return false;
PhoneNumber pn = (PhoneNumber) o;
return pn.lineNumber == lineNumber
&& pn.prefix == prefix
&& pn.areaCode == areaCode;
}
//严格来说,在没有AF的情况下直接在equals()中判断每个域的等价性,是不正确的
}

只重写equals方法是不够的,我们还要重写Object()父类中的另一个方法HashCode()方法,这是因为在Object()父类中规定了如果根据 equals(Object) 方法,两个对象是相等的,那么对这两个对象中的每个对象调用 HashCode() 方法都必须生成相同的整数结果。基于这一原因,我们就必须要重写HashCode()保证equals()判断的等价的两个对象,它们的HashCode也要一致才行。

重写HashCode()的一些准则如下所示。

 下面给出一个简单的重写例子。

public final class PhoneNumber {
private final short areaCode;
private final short prefix;
private final short lineNumber;
@Override
public int hashCode() {
int result = 17; // Nonzero is good
result = 31 * result + areaCode; // Constant must be odd
result = 31 * result + prefix; // " " " "
result = 31 * result + lineNumber; // " " " "
return result;
}
}

二、Mutable类型的等价性


对于Mutable类型的对象,常常提到的是观察等价性和行为等价性。之所以不提引用等价性和对象等价性是因为在Mutable类型的对象中,这两种等价性其实是一样的。

2.1观察等价性

观察等价性是指在不改变状态的情况下,两个mutable对象是否看起来一致。对可变类型来说,往往倾向于实现严格的观察等价性。但在有些时候,观察等价性可能导致bug,甚至可能破坏RI,这是因为过段时间后可能两者就不等价了,因为值可能会发生变化。观察等价性同样通过重写equals()和HashCode() 方法来实现。

下面是一个具体的例子。

 做出的解释。

 

2.2 行为等价性 

行为等价性是指调用对象的任何方法都展示出一致的结果。对可变类型,实现行为等价性即可。也就是说,只有指向同样内存空间的objects,才是相等的。所以对可变类型来说,行为等价无需重写equals()和HashCode()这两个函数,直接继承Object()的两个方法即可。也就是说,对于行为等价的Mutable类型的对象来说,==和equals()是一样的。如果一定要判断两个可变对象看起来是否一致,最好定义一个新的方法。

下面给出一个具体的例子,感兴趣的可以自行做出解答。

Bag<String> b1 = new Bag<>().add("a").add("b");
Bag<String> b2 = new Bag<>().add("a").add("b");
Bag<String> b3 = b1.remove("b");
Bag<String> b4 = new Bag<>().add("b").add("a")

 

 三、总结

  • ADT的等价性是对于客户端角度而言的,要么,两个对象通过AF映射到相同的抽象值,要么,两个 对象能做出效果相同的行为。不一定非得让rep完全一致。
  • 所有对象都继承了Object.equals(),我们可以在类中重写它,从而定义自己的等价规则。注意 equals()与==不是一回事,后者仅仅判断两个引用是否指向同一对象。如果不重写 Object.equals(),那么默认效果和==是一样的。
  • 对于可变对象,除了上述的“观察等价性”,还会有一种“行为等价性”,如果两个对象这个时刻等价, 那么不管之后干了什么,这两个可变对象仍然是等价的。(一般和==一样,当且仅当是同一对象, 例如StringBuilder,我们常用的除它之外的类一般都是观察等价性,如String、List)
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值