在Objective-C中如何正确地覆盖isEqual: ?“陷阱”似乎是,如果两个对象相等(由isEqual:方法决定),它们必须具有相同的散列值。
Cocoa Fundamentals Guide的Introspection部分确实有一个关于如何重写isEqual:的例子,复制如下,用于一个名为MyWidget的类:
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return [self isEqualToWidget:other];
}
- (BOOL)isEqualToWidget:(MyWidget *)aWidget {
if (self == aWidget)
return YES;
if (![(id)[self name] isEqual:[aWidget name]])
return NO;
if (![[self data] isEqualToData:[aWidget data]])
return NO;
return YES;
}
它检查指针是否相等,然后是类是否相等,最后使用isEqualToWidget:比较对象,后者只检查名称和数据属性。这个例子没有说明如何重写哈希。
让我们假设有其他属性不影响平等,比如年龄。难道不应该重写哈希方法,以便只有名称和数据影响哈希吗?如果是,你会怎么做?只是添加名称和数据的散列吗?例如:
- (NSUInteger)hash {
NSUInteger hash = 0;
hash += [[self name] hash];
hash += [[self data] hash];
return hash;
}
这足够了吗?有更好的技术吗?如果你有基本类型,比如int呢?将它们转换为NSNumber以获得它们的散列?或者像NSRect这样的结构?
(脑屁:最初把“位或”和|=写在一起。意味着添加。)
请注意,如果创建的对象在创建后可以更改,则如果该对象插入到集合中,则哈希值不能更改。实际上,这意味着哈希值必须从初始对象创建时开始固定。更多信息请参阅Apple的NSObject协议的-hash方法文档:
If a mutable object is added to a collection that uses hash values to determine the object’s position in the collection, the value returned by the hash method of the object must not change while the object is in the collection. Therefore, either the hash method must not rely on any of the object’s internal state information or you must make sure the object’s internal state information does not change while the object is in the collection. Thus, for example, a mutable dictionary can be put in a hash table but you must not change it while it is in there. (Note that it can be difficult to know whether or not a given object is in a collection.)
对我来说,这听起来完全是无稽之谈,因为它可能会有效地降低哈希查找的效率,但我认为最好还是谨慎行事,并遵循文档所说的。
哈希函数应该创建一个不太可能与另一个对象的哈希值冲突或匹配的半唯一值。
这里是完整的哈希函数,它可以适应你的类实例变量。它使用NSUInteger而不是int来兼容64/32位应用程序。
如果不同对象的结果为0,则会有碰撞散列的风险。当使用一些依赖于哈希函数的集合类时,碰撞哈希会导致意外的程序行为。请确保在使用之前测试您的哈希函数。
-(NSUInteger)hash {
NSUInteger result = 1;
NSUInteger prime = 31;
NSUInteger yesPrime = 1231;
NSUInteger noPrime = 1237;
// Add any object that already has a hash function (NSString)
result = prime * result + [self.myObject hash];
// Add primitive variables (int)
result = prime * result + self.primitiveVariable;
// Boolean values (BOOL)
result = prime * result + (self.isSelected ? yesPrime : noPrime);
return result;
}
Sorry if I risk sounding a complete boffin here but...
...nobody bothered mentioning that to follow 'best practices' you should definitely not specify an equals method that would NOT take into account all data owned by your target object, e.g whatever data is aggregated to your object, versus an associate of it, should be taken into account when implementing equals.
If you don't want to take, say 'age' into account in a comparison, then you should write a comparator and use that to perform your comparisons instead of isEqual:.
如果您定义了一个isEqual:方法来任意执行相等比较,那么一旦您忘记了equals解释中的“扭曲”,您就会冒这个方法被其他开发人员甚至您自己误用的风险。
因此,虽然这是一个关于哈希的很好的问答,你通常不需要重新定义哈希方法,你可能应该定义一个特别的比较器。