软件构造--Chapter8总结

本文探讨了在抽象数据类型(ADT)和面向对象编程(OOP)中如何定义对象的等价性,包括自反性、对称性和传递性。通过Duration和LetterSet类的示例,阐述了等价性的三种方式:ADT的等价性、外部观察者的等价性和引用等价性。同时,解释了equals()与hashCode()的关系,以及在可变和不可变类型中如何实现等价性。此外,还提到了自动装箱与拆箱时原始类型和对象类型的等价性处理。
摘要由CSDN通过智能技术生成

Equality in ADT and OOP

Equivalence Relation

现实中的对象实体是独特的,无法完全相等,但又相似性。
软件中,什么情况下两个事物认为是等价的、可以相互替代的,即为等价性问题。
在集合论中,等价关系是满足自反、对称、传递的二元关系,此定义可以作用在上述的等价性问题上。
在这里插入图片描述

Three ways to regard equality

ADT是对数据的抽象,体现为一组对数据的操作,而AF抽象函数是内部表示R到抽象表示A的映射。基于AF定义ADT的等价操作为:如果AF映射到同样的结果,则等价,即AF(a)=AF(b),则有a等价于b。
在外部观察者角度中,对两个对象调用任何相同的操作,都会得到相同的结果,则认为两个对象是等价的,反之亦然。

public class Duration{
	private final int mins;
	private final int secs;
	// rep invariant:
	/*
	 * mins>=0
	 * secs>=0
	 */
	// abstraciton 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类,现在有如下4个声明,则d1、d2和d4是等价的。

Duration d1 = new Duration (1,2);
Duration d2 = new Duration (1,3);
Duration d3 = new Duration (0,62);//最后体现为mins=0,secs=62,getLength=62;
Duration d4 = new Duration (1,2);

再看看下面的LetterSet的例子:

/** Immutable type representing a subset of the letters a-z,ignoring case */
class LetterSet{
	private String s;
	//AF:
	/*
	 * AF(s)=the subset of letters {a...z} that are found in s(ignoring alphabetic case and non-letters)
	 */
	//RI:
	/*
	 * true
	 */
	/** Make a LetterSet consisting of the letters found in chars
	 *(ignoring alphabetic case and non-letters).*/
	 public LetterSet(String chars){
	 	s=chars;
	 }
	 
	 /** @return the size of this set */ 				     
	 public int size() { ... } 
	 
	 /** @param letter must be a letter 'a'...'z' or 'A'...'Z' 
	 * @return true iff this set contains letter, ignoring alphabetic case */ 
	 public boolean contains(char letter) { ... } 
	 
	 /** @return the length of the string that this set was constructed from */ 
	 public int length() { ... } 
	 
	 /** @return the union of this and that */ 
	 public LetterSet union(LetterSet that) { ... } 
	 
	 /** @return true if and only if all the letters in this set are lowercase */ 
	 public boolean isAllLowercase() { ... } 
	 
	 /** @return the first letter in s */ 
	 public char first() { ... }
}

下面有五个实例化对象,可以进行练习:

new LetterSet("abc");
new LetterSet("aBc");
new LetterSet("");
new LetterSet("bbbbbbbc");
new LetterSet("1a2b3c");

== vs. equals()

两个=为引用等价性,即内存地址是否相同(注意,Python中是is);equals为对象等价性,未重写equals则默认是内存地址是否相同,自定义ADT时根据对等价的需要,决定是否重写Object的equals()(Python中则为两个=)
对于基本数据类型,使用两个=进行判定相等,对对象类型则使用equals进行判定相等。

Implementing equals()

在Object中实现的缺省equals()是判定引用等价性,根据实际需要常常需要@Override重写

public class Duration extends Object{
	// explicit method that we declared:
	public boolean equals(Duration that){
		return this.getLength()==that.getLength();
	}
	// imlicit method inherited from object:
	public boolean equals (Object that){
		return this == that;
	}
}

注意第一个方法是重载(参数类型不同,编译器进行选择,为静态检查),第二个方法是重写

Duration d1 = new Duration (1,2);
Duration d2 = new Duration (1,2);
Object o2 = d2;
d1.equals(d2);//true
d1.equals(o2);//false

在运行时即使o2和d2是一个对象,但是在编译时已经决定了二者调用的equals方法不同。
instanceof是判断某个对象是否为特定类型(或其子类型),属于动态类型检查,在equals之外的方法要尽量避免使用instanceof和getClass()方法。

The Object contract

对于equals方法,需要始终满足如下条件:
1.满足等价关系,即自反、对称、传递;
2.除非对象被修改,否则调用多次equals应是相同的结果;
3.对于非空的x,x.equals(null)始终为false;
4.等价的对象,hashCode()结果必须相同;
上述的性质可以概括为自反性Reflexice、对称性Symmetric、传递性Transitive、一致性Consistent、非空性Non-null。
哈希表实现了key-value之间的映射,表中包含一个数字,键值对中的key被映射为hashcode,对应到数组的index,hashcode决定了数据被存储到数组的哪个位置。哈希表的RI中基本要求就是key在slot中的位置由hashcode确定。
等价的对象必须要有相同的hashCode,但是不等价的对象也可以映射为同样的hashCode,但是相对的性能会下降。因此,建议不等价的对象要有不同的hashCode。并且,重写equals方法的同时也要对应的重写hashCode方法。
另外,除非是可变类型,否则hashCode在其他方法调用中也不应该被改变。
对于hashCode的重写,可以结合计算中用到的所有信息的hashCode组合出新的hashCode。
下面是重写的hashCode样例:

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

		return result;
	}

	@Override
	public int hashCode(){//Less efficient,but otherwise equally good
		short[] hashArray = {areaCode, prefix, lineNumber};
		return Arrays.hashCode(hashArray);
	}
}

Equality of Mutable Types

观察等价性,在不改变状态的情况下,两个可变对象是否看起来一致。
行为等价性,调用对象的任何方法都展示出一致的结果。
对于不可变对象,观察等价性和行为等价性是完全一样的,因为不可变对象没有mutator方法,不可变对象用引用等价性和对象等价性判断。
对可变对象来说,倾向于实现严格的观察等价性,Java对其大部分可变数据类型(如Collections)使用观察等价性,如两个List中包含相同顺序的元素,则equals()返回true,部分可变类型用行为等价性。
在某些时候观察等价性可能导致bug,甚至会破坏RI。
例如,有List和Set如下,开始检查set中是否包含list,结果为true,对list使用Observers方法之后,再判断set中包含list,则结果为false。

List<String> list = new ArrrayList<>();
list.add("a");
Set<List<String>> set = new HashSet<List<String>>();
set.add(list);
set.contains(list);//true
list.add("b");
set.contains(list);//false,由于在上一行改变了list,但是set中的list并没有相应改变

当List的序列被改变,对象的hashcode变了,但是HashSet未更新其在bucket的位置,查找时新hashcode的位置找不到对应元素。
当equals和hashcode的结果可能被可变影响时,hash table的RI会遭到破坏。
因此,如果某个mutable对象包含在Set集合中,当其发生改变后,集合类的行为不确定。
在JDK中,不同mutable类使用不同的等价性标准。
例如,Date类的equals为观察等价性,当前仅当getTime方法返回相同的long类型的value;List类的equals为观察等价性,当且仅当具体对象也是list,两个list有相同的大小,每对符合的元素在两个list中都等价;String类的equals继承自Object类,属于行为等价性。
对于可变类型,实现行为等价性即可,即只有指向同样内存空间的objects才相等,无需重写equals和hashCode方法,若要判断两个可变对象看起来是否一致,最好定义一个新的方法。
对于不可变类型,必须重写equals和hashCode方法。

Class Bag<E>{
	/** make an empty bag */
	public Bag<E>()

	/** modify this bag by adding an occurrence of e, and return this bag */ 
	public Bag<E> add(E e) 
	/** modify this bag by removing an occurrence of e (if any), and return this bag */
	public Bag<E> remove(E e) 
	/** return number of times e occurs in this bag */ 
	public int count(E e)

对于上述可变的Bag类,有下列实例化对象

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");

可变类型,行为等价性,需要引用相等。

b1.equals(b2);//false
b1.equals(b3);//true
b1.equals(b4);//false
b2.equals(b3);//false
b2.equals(b4);//false
b3.equals(b1);//true

对应的快照图如下图所示:
在这里插入图片描述
如果实现的是观察等价性,则需要通过Observer(count())判断。
clone()创造并返回一个此对象的副本,对于任意的对象x,有如下的表达式为true:
x.clone()!=x,x.clone().getClass()==x.getClass(),x.clone().equals(x)

Autoboxing and Equality

原始类型和它们的对象类型是等价的,例如int和Integer。

Integer x = new Integer(3);
Integer y = new Integer(3);
x.equals(y);//true
x==y;//false
(int)x==(int)y;//true

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海质粒ABCC9

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值