第八條 :理解“對(duì)象等同性”這一概念
根據(jù)“等同性“(equality)來(lái)比較對(duì)象是一個(gè)非常有用的功能。
不過(guò)按照”==“操作符比較出來(lái)的結(jié)果未必是我們想要的,因?yàn)樵摬僮鞅容^的是兩個(gè)指針本身,而不是其所指的對(duì)象。應(yīng)該使用NSObject協(xié)議中聲明的”isEqual“:方法來(lái)判斷兩個(gè)對(duì)象的等同性。
一般來(lái)說(shuō),兩個(gè)類型不同的對(duì)象總是不相等(unequal)。某些對(duì)象提供了特殊的”等同性判斷方法“,如果已經(jīng)知道兩個(gè)受測(cè)對(duì)象都屬于同一類,那么就可以使用這種方法。
NSString類實(shí)現(xiàn)了一個(gè)自己獨(dú)有的等同性判斷方法,名叫“isEqualToString:”。傳遞給該方法的對(duì)象必須是NSString。調(diào)用該方法比調(diào)用“isEqual”方法快。因?yàn)閕sEqual方法不知道受測(cè)對(duì)象的類型。
NSObject協(xié)議中有兩個(gè)用于判斷等同行的方法:
-(BOOL)isEqual:(id)object;
-(NSUInteger)hash;
NSObject類對(duì)這兩個(gè)方法的默認(rèn)實(shí)現(xiàn)是:當(dāng)且僅當(dāng)“指針值”(pointer value)完全相等時(shí),這兩個(gè)對(duì)象才相等。若想在自定義的對(duì)象中,正確復(fù)寫這些方法,就必須先理解其約定(contract)。
如果isEqual:方法判定兩個(gè)對(duì)象相等,那么其hash也必須返回同一個(gè)值。
但是,如果兩個(gè)對(duì)象的hash方法返回同一個(gè)值,那么“isEqual:”方法未必會(huì)認(rèn)為兩者相等。
collection:array、dictionary、set等數(shù)據(jù)結(jié)構(gòu)的總稱
collection在檢索hash表時(shí),會(huì)用對(duì)象的哈希碼做索引。假如某個(gè)collection是用set實(shí)現(xiàn)的,那么set可能會(huì)根據(jù)哈希碼把對(duì)象分裝到不同的數(shù)組中。在向set中添加對(duì)象時(shí),要根據(jù)其哈希碼找到與之相關(guān)的的那個(gè)數(shù)組,依次檢查各個(gè)元素,看數(shù)組中已有的對(duì)象是否和將要添加的對(duì)象相等。如果相等,那就說(shuō)明將要添加的對(duì)象已經(jīng)在set中了。如果令每個(gè)對(duì)象都返回hash碼,那么必然會(huì)產(chǎn)生性能問(wèn)題。
hash方法也可以這樣來(lái)實(shí)現(xiàn):
將NSString對(duì)象中的所有屬性都放在一個(gè)字符串中,然后領(lǐng)hash方法返回該字符串的hash碼。這樣做也符合約定,兩個(gè)相同對(duì)象會(huì)返回相同的hash碼。
但是這樣做還需負(fù)擔(dān)創(chuàng)建字符串的開銷,所以比返回單一值要慢。
把這種對(duì)象放入collection中也會(huì)產(chǎn)生性能問(wèn)題,因?yàn)橄胍砑訉?duì)象,必須先計(jì)算其hash碼。
另一種計(jì)算hash碼的辦法:
-(NSUInteger)hash{
NSUInteger firstNameHash = [_firstName hash];
NSUInteger lastNameHash = [_lastName hash];
NSUInteger ageHash = ?_age;
return firstNameHash ^ lastNameHash ^ ageHash;
}
這種做法既能保持高效,又能使生成的hash碼至少位于一定范圍內(nèi),而不會(huì)過(guò)于頻繁的重復(fù)。
當(dāng)然,這種算法生成的hash碼還是會(huì)碰撞(collision),不過(guò)至少可以保證hash碼有多種可能的取值。
特定類所具有的等同性判定方法
如果某些特定類需要經(jīng)常判斷等同性,那么可能需要自己來(lái)創(chuàng)建等同性判定方法,因?yàn)闊o(wú)須檢測(cè)參數(shù)類型,所以能大大提升檢測(cè)速度。
在編寫判定方法時(shí),也應(yīng)一并復(fù)寫“isEqual”方法。后者的常見實(shí)現(xiàn)方法為:如果受測(cè)的參數(shù)與接收該消息的對(duì)象都屬于同一個(gè)類,那么就調(diào)用自己寫的判定方法,否則就交由超類來(lái)判斷。
等同性判定的執(zhí)行深度
創(chuàng)建等同性判定方法時(shí),需要決定是根據(jù)整個(gè)對(duì)象來(lái)判斷等同性,還是僅根據(jù)其中幾個(gè)字段來(lái)判斷。
例如:NSArray的檢測(cè)方式為先看兩個(gè)數(shù)組所含對(duì)象的個(gè)數(shù)是否相同,若相同,則在每個(gè)對(duì)應(yīng)位置的兩個(gè)對(duì)象身上調(diào)用其“isEqual:”方法。如果對(duì)應(yīng)位置上的對(duì)象均相等,那么這兩個(gè)數(shù)組就相等,這叫做“深度等同性判定”(deep equality)。
不過(guò),有時(shí)候無(wú)須將所有的數(shù)據(jù)逐個(gè)比較,只根據(jù)其中部分?jǐn)?shù)據(jù)即可判明二者是否等同。
容器中可變類的等同性
如果在容器中放入可變類對(duì)象的時(shí)候,把某個(gè)對(duì)象放入collection中后,就不應(yīng)再改變其hash碼了。collection會(huì)把各個(gè)對(duì)象按照其hash碼分裝到不同的“箱子數(shù)組”中。如果對(duì)象的hash碼在放入”箱子“之后又變了,那么其所在的這個(gè)箱子對(duì)他來(lái)說(shuō)就是”錯(cuò)誤“的。
要想解決這個(gè)問(wèn)題,要保證,對(duì)象的hash碼,不是根據(jù)對(duì)象的可變部分計(jì)算來(lái)的。
或者保證放入collection之后就不再改變對(duì)象內(nèi)容了。
【要點(diǎn)】
1.若想要檢測(cè)對(duì)象的”對(duì)象的等同性“,請(qǐng)?zhí)峁眎sEqual:“與hash方法
2.相同的對(duì)象必須具有相同的hash碼,但是兩個(gè)hash碼相同的對(duì)象卻未必相同
3.不要盲目地逐個(gè)檢測(cè)每條屬性,而是應(yīng)該依照具體需求來(lái)制定檢測(cè)方案
4.編寫hash方法時(shí),應(yīng)該使用計(jì)算速度快而且hash碰撞幾率低的算法