Ответ 1
Если алгоритм обхода достаточно сложный, чтобы вы не повторялись, выделите алгоритм на метод, который могут использовать как equals
, так и hashCode
.
Я вижу два варианта, которые (как это часто бывает) компромисс между широко применимыми и эффективными.
В широком смысле, применимая
Первый вариант - написать довольно общий метод обхода, который принимает функциональный интерфейс и обращается к нему на каждом этапе обхода, поэтому вы можете передать в него лямбду или экземпляр, содержащий фактическую логику, которую вы хотите выполнить, в то время как обходе; шаблон посетителя. Этот интерфейс хотел бы иметь способ сказать "остановить перемещение" (например, поэтому 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
для цепочки, независимо от того, что подходит в вашей ситуации.
Проблема заключается в том, что использование этого средства означает выделение экземпляра (или использование лямбда, но тогда вам, вероятно, нужно будет что-то выделять для того, чтобы lamba обновлялось, чтобы отслеживать, что он делает) и совершает множество вызовов методов, Возможно, это хорошо в вашем случае; возможно, это убийца производительности, потому что вашему приложению нужно много использовать equals
.: -)
Конкретные и эффективные
... и поэтому вы можете написать что-то конкретное в этом случае, написав то, что имеет логику для equals
и hashCode
, встроенных в нее. Он будет возвращать хэш-код при использовании hashCode
или значение флага для equals
(0 = не равно,! 0 = равно). Больше не полезно вообще, но он позволяет создать экземпляр посетителя для передачи служебных служебных/служебных данных/лямбда. Концептуально, это может выглядеть примерно так:
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
. Но, конечно, если вы забудете сделать недействительным хэш-код даже в одном из ваших методов мутаторов...