==操作符比較的是兩個(gè)指針本身,而不是其所指的對(duì)象
isEqual: 方法來(lái)判斷兩個(gè)對(duì)象的等同性
NSObject協(xié)議中有兩個(gè)用于判斷等同性的關(guān)鍵方法:
-(BOOL)isEqual:(id)object;
-(NSUInteger)hash;
NSObject類對(duì)這兩個(gè)方法的默認(rèn)實(shí)現(xiàn)是: 當(dāng)且僅當(dāng)其"指針值"(pointer value)完全相待時(shí), 這兩個(gè)對(duì)象才相等.
如果"isEqual:"方法判定兩個(gè)對(duì)象相等, 那么其hash方法也必須返回同一個(gè)值. 但是, 如果兩個(gè)對(duì)象的hash方法返回同一個(gè)值, 那么"isEqual:" 方法未必會(huì)認(rèn)為兩者相等
自定義isEqual:
例如有下面這個(gè)類:
@interface EOCPerson : NSObject
@property (nonatomic, copy) NSString *firstName;
@property (nonatomic, copy) NSString *lastName;
@property (nonatomic, assign) NSUInteger age;
@end
我們認(rèn)為,如果兩個(gè)EOCPerson的所有字段均相等, 那么這兩個(gè)對(duì)象就相等.
-(BOOL)isEqual:(id)object{
if (self == object) return YES;
if ([self class] != [object class]) return NO;EOCPerson *otherPerson = (EOCPerson *)object;
if(![_firstName isEqualToString:otherPerson.firstName])
return NO;
if(![_lastName isEqualToString:otherPerson.lastName])
return NO;
if(_age != otherPerson.age)
return NO;
return YES;
}
接下來(lái)該實(shí)現(xiàn)hash方法.
根據(jù)等同時(shí)約定: 若兩對(duì)象相等, 則其哈希碼(hast) 也相等,但是兩個(gè)哈希碼相同的對(duì)象卻未必相等.
- (NSUInteger)hash{
return 1337;
}
上面的寫(xiě)法,在collection中使用這種對(duì)象將產(chǎn)生性能問(wèn)題, 因?yàn)閏ollection在檢索哈希表(hash table) 時(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ì)象都返回相同的哈希碼, 那么在set中已有1000000個(gè)對(duì)象的情況下, 若是繼續(xù)向其中添加對(duì)象, 則需將這 1000000個(gè)對(duì)象全部掃描一遍.
修改如下:
- (NSUInteger)hash {
NSString *stringToHash = [NSString stringWithFormat:@"%@:%@:%i",_firstName, _lastName, _age];
return [stringToHash hash];
}
這次所用的辦法是將NSString對(duì)象中的屬性都塞入另一個(gè)字符串中, 然后令hash方法返回該字符串的哈希碼. 這么做符合約定, 因?yàn)閮蓚€(gè)相等的EOCPerson對(duì)象總會(huì)返回相同的哈希碼. 但是這樣做還需負(fù)擔(dān)創(chuàng)建字符串的開(kāi)銷, 所以比返回單一值要慢. 把這種對(duì)象添加到collection中時(shí), 也會(huì)產(chǎn)生性能問(wèn)題, 因?yàn)橄胍砑? 必須先計(jì)算其哈希碼.
進(jìn)化如下:
- (NSUInteger)hash {
NSUInteger firstNameHash = [_firstName hash];
NSUInteger lastNameHash = [_lastName hash];
NSUInteger ageHash = _age;
return firstNameHash ^ lastNameHash ^ ageHash;
}
這種做法既能保持較高效率, 又能使生成的哈希碼至少位于一定范圍之內(nèi), 而不會(huì)過(guò)于頻繁地重復(fù). 當(dāng)然, 此算法生成的哈希碼還是會(huì)碰撞(collision), 不過(guò)至少可以保證哈希碼有多種可能的取值. 編寫(xiě)hash方法時(shí), 應(yīng)該用當(dāng)前的對(duì)象做實(shí)驗(yàn), 以便在減少碰撞頻度與降低運(yùn)算復(fù)雜程度之間取舍.
特定類所具有的等同性判斷方法
如果經(jīng)常需要判斷等同性, 那么可能會(huì)自己來(lái)創(chuàng)建等同性判定方法, 因?yàn)闊o(wú)須檢測(cè)參數(shù)類型, 所以能大大提升檢測(cè)速度.
在編寫(xiě)判定方法時(shí), 也應(yīng)一并覆寫(xiě)"isEqual:"方法. 后者的常見(jiàn)實(shí)現(xiàn)方式為: 如果受測(cè)的參數(shù)與接收該消息的對(duì)象都屬于同一個(gè)類, 那么就調(diào)用自己編寫(xiě)的判定方法, 否則就交由超類來(lái)判斷.
例:
- (BOOL)isEqualToPerson:(EOCPerson *)otherPerson{
if (self == object) return YES;
if (![_firstName isEqualToString:otherPerson.firstName])
return NO;
if (![_lastName isEqualToString:otherPerson.lastName])
return NO;
if (_age != otherPerson.age)
return NO;
return YES;
}
- (BOOL)isEqual:(id)object{
if ([self class] == [object class]){
return [self isEqualToPerson:(EOCPerson *)object];
} else {
return [super isEqual:object];
}
}
容器中可變類的等同性
容器中放入可變類對(duì)象的時(shí)候. 把某個(gè)對(duì)象放入collection之后, 就不應(yīng)再改變其哈希碼了. 前面解釋過(guò), collection會(huì)把各個(gè)對(duì)象按照其哈希碼分裝到不同的"箱子數(shù)組"中. 如果某對(duì)象在放入"箱子"之后哈希碼又變了, 那么其現(xiàn)在所處的這個(gè)箱子對(duì)它來(lái)說(shuō)就是"錯(cuò)誤"的. 所以容器里的對(duì)象應(yīng)該是不可改變的