学习了Java中ADT的等价性的一些收获

最近在软件构造课里学习了Java中ADT的等价性,希望能够通过写博客这种方式来总结一下,加强理解。

等价关系

之前在集合论与图论中学过关系,等价关系需要满足三个性质,自反性、对称性、传递性。
对于一个集合T,这个集合的某一个关系可以定义为其笛卡尔积的子集,即关系R ⊆ \subseteq T × \times ×T。下面看看等价关系的三个性质。
自反性
∀ \forall t ∈ \in T, t R t.
对称性
∀ \forall u,v ∈ \in T, if u R v, then v R u.
传递性
∀ \forall u,v,w ∈ \in T, if u R v, and v R w, then u R w.

对象的等价性

注:这里的对象指的都是不可变(immutable)的,如果是可变的,直接用Object类里的函数即可。

老师上课举的例子我觉得对我的启示比较大,两个对象是否等价,需要比较的不应该是它们内部的属性,而是属性经过AF(abstraction function)映射后是结果,即AF(rep1)是否等于AF(rep2)。其中rep1和req2分别是待比较的两个对象的属性列表。

例子如下:

public class Duration {
	private final mins;
	private final secs;
	// rep invariant:
	//    mins >= 0, secs >= 0
	// abstraction function:
	//    represents a span of time of mins minutes and secs seconds
	/** Make a duration lasting for m minutes and s seconds. */
	public Duration(int m,int s) {
		mins=m;
		secs=s;
	}
	/** @return length of this duration in seconds */
	public long getLength() {
		return mins*60+secs;
	}
}

这是一个表示时间的类,如果要比较两个Duration对象的等价性,判断两个属性是否相等显然是不合理的,比如

Duration d1=new Duration(1,2);
Duration d2=new Duration(0,62);

这两个对象虽然内部的属性不同,但表示的意义都是62秒,所以应该等价。这里就可以用函数getLength()来判断了,如果返回值相等,则等价。

在这里需要约束class里面的方法。对于两个等价的对象,执行同一观察器(observer)函数,返回的结果也要一样。例如有一个class的结构如下:

class LetterSet {
	private String s;
	//Abstraction function:
	//	AF(s) = the subset of the letters {a...z} that are found in s
	//	(ignoring alphabetic case and non-letters)	
	
	/** @return true if and only if all the letters in this set are lowercase */
	public boolean isAllLowercase() {...}
}

这个isAllLowercase函数就是错误的。比如"abc"和"ABC",有AF,都映射到集合{a,b,c},但这个函数的返回值不同。

equals函数

首先看一下Object类中的equals函数。

public class Object() {
	...
	public boolean equals(Object that) {
    	return this == that;
	}	
}

对于这个缺省的equals函数,我的理解是判断两个对象是否指向同一个内存空间(不太确定),所以一般来说,对于自己编写的类,需要以上文的原则重写这个函数。
重写函数的代码基本如下:

@Override
public boolean equals(Object o) {
	if (!(o instanceof Duration)) return false;
	Duration that = (Duration) o;
	return this.getLength() == that.getLength();
}

注意,这里的参数必须是Object型。这是因为这是对父类函数的重写(override),参数列表必须相同,如果参数类型不同,那是重载(overload)。比如说我们把equals函数写成下面的格式:public boolean equals(Duration),则如果调用函数传入的参数类型是Duration,当然默认调用是我们写的这个函数,但如果参数是其他的类型,因为类型不匹配,所以会调用父类的equals函数,发生错误。

hashCode函数

这个函数的功能是将对象映射到一个常数。看一下Object类中的hashCode函数。

public class Object {
	...
	public boolean equals(Object that) {return this == that; }
	public int hashCode() {return /* the memory address of this */;}
}

这个函数的默认实现是返回它的内存地址。重写这个函数的规则和equals相似,都是由AF决定的。如果两个对象等价,则它们的hashCode返回值一定相等。对于上面的例子Duration这个类,重写的一种方法为:

@Override
public int hashCode() {
	return (int) getLength();
}

可变类型的等价性

这里引出了两个概念,观察等价性和行为等价性。
观察等价性
在不改变状态的情况下,两个mutable对象是否看起来一致。
行为等价性
调用对象的任何方法都展示出一致的结果。

对于不可变类型来说,这两种等价性是等价的,不可变类型没有变化器(mutator)方法。
对于可变类型,往往使用观察等价性来判断,但有时用观察等价性会出现bug。
当我们编写一个可变类型时,实现行为等价性即可,equals函数和hashCode函数,不需要重写,直接继承Object的两个方法就行了。当然如果一定要判断两个可变类型“看起来”是否一致,可以定义一个新的方法来判断。

对equals和hashCode的总结

对于不可变类型
必须重写这两个方法,保证行为等价性,行为等价性等价于观察等价性。
对于可变类型
不需要重写,保证行为等价性。

最后,本人新手,恳请斧正。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值