如果遍历算法足够复杂,您希望避免重复自己,请将该算法隔离为一个既可以使用又可以使用的方法。equals
hashCode
我看到了两种选择,它们(就像经常发生的情况一样)在广泛适用和高效之间进行权衡。
广泛适用
第一种选择是编写一个相当通用的遍历方法,该方法接受功能接口并在遍历的每个阶段回调它,因此您可以将lambda或实例传递到其中包含要在遍历时执行的实际逻辑;访客模式。该接口希望有一种方法可以说“停止遍历”(例如,当它知道答案“不相等”时,也可以保释)。从概念上讲,这看起来像这样:equals
private boolean traverse(Visitor visitor) {
while (/*still traversing*/) {
if (!visitor.visitNode(thisNode)) {
return false;
}
/*determine next node to visit and whether done*/
}
return true;
}
然后使用它来实现相等性检查或哈希代码构建,而无需知道遍历算法。equals
hashCode
我在上面选择让方法返回一个标志,说明遍历是否提前结束,但这是一个设计细节。您可能不会返回任何内容,或者可能会返回链接,无论您的情况如何。this
但问题是,使用它意味着分配一个实例(或使用lambda,但随后您可能需要为lamba分配一些东西来更新,以跟踪它正在做什么)并执行大量方法调用。也许这在你的情况下很好;也许这是一个性能杀手,因为你的应用需要使用很多。:-)equals
具体而高效
...因此,您可能希望编写特定于此案例的内容,编写具有逻辑并内置于其中的内容。当 被 使用时,它将返回哈希代码,或者返回 (0 = 不等于,!0 = 等于)的标志值。不再通常有用,但它避免了创建访客实例以传入/ lambda开销/ 调用开销。从概念上讲,这可能看起来像这样:equals
hashCode
hashCode
equals
private int equalsHashCodeWorker(Object other, boolean forEquals) {
int code = 0;
if (forEquals && other == null) {
// not equal
} else {
while (/*still traversing*/) {
/*update `code` depending on the results for this node*/
}
}
return code;
}
同样,具体细节将是,嗯,特定于您的情况以及您的风格指南等。有些人会通过处理案例本身并使参数达到两个目的(标志和“其他”对象),并且仅在它具有非对象时才调用此工作线程。我宁愿避免在这样的论点的含义上加倍努力,但你经常看到它。other
equals
other == null
null
测试
无论你走哪条路,如果你在一个有测试文化的商店里,你自然会想要为你已经看到的失败的复杂案例以及你看到失败机会的其他案例构建测试。
附注关于hashCode
无论上述情况如何,如果您预计会被大量调用,则可以考虑将结果缓存到实例字段。如果您要处理的对象是可变的(听起来像是可变的),那么每次更改对象的状态时,您都会使该存储的哈希码失效。这样,如果对象未更改,则不必在后续调用 中重复遍历。但是,当然,如果您忘记使哈希代码失效,即使是其中一个突变体方法...hashCode
hashCode