is equals() supposed to be recursive/deep?

Refresh

December 2018

Views

1.1k time

3

None really talks about this aspect of equals() and hasCode(), but there is potentially massive impact on equals() and hashCode() behavior. Massive when dealing with a bit more complex objects referencing other objects.

Joshua Bloch in his Effective Java does not even mention it in his "overriding equals() method" chapter. All his examples are trivialities like Point and ColorPoint, all with just primitive or nearly primitive types.

Can recursivity be avoided? Sometimes hardly. Assume:

Person {
    String name;
    Address address;
}

Both fields has to go to business key (as Hibernate guys call it), they are both value components (as Joshua Bloch has it). And Address is a complex object itself. Recursion.

Be aware, IDEs like Eclipse and IntelliJ does generates recursive equals() and hashCode(). They by default use all fields. If you apply generator tools an mass, you asking for troubles.

One trouble is you can get a StackOverflowError. Here my simple test proving it.
All is needed is class having as a "value component" another object, forming a object graph and recommended equals() implementation. Yes, you need a graph in that cycle, but that is nothing unrealistic (imagine molecules, paths on map, interlinked transactions..).

Another trouble is performance. What is recommended for equals() is in fact comparing of two object graphs, potentially huge graphs, one can end up comparing thousands of nodes without knowing it. And not all of them are necessary in the memory! Consider that some objects may be lazy loadable. One can end up loading half of the database on one equals() or hashCode() call.

Paradox is, the more rigorously you override equals() and hashCode() as you are encouraged to do, the more likely you get into troubles.

2 answers

0

Есть по крайней мере два логических вопросов, которые были бы значимы для любых двух ссылок любого типа, которое было бы в разное время быть полезным для equalsтесту:

  1. Может ли тип обещание, что две ссылки будут идентифицировать эквивалентные веки вечные объекты?

  2. Может ли тип обещание, что две ссылки будут определены эквивалентные объекты до тех пор, как код, который не держит ссылки ни модифицирует объекты, и не выставляет их в код, который будет?

Если ссылка идентифицирует объект , который может изменить в любое время без предварительного уведомления, только ссылки , которые следует учитывать эквивалентные тем , которые определяют один и тот же объект. Если ссылка идентифицирует объект глубоко-неизменного типа, и никогда не используется таким образом , чтобы проверить его личность (например , блокировка, IdentityHashSetи т.д.) , то все ссылки на объекты , имеющих одинаковое содержание следует считать эквивалентными. В обоих вышеуказанных случаях, надлежащее поведение equalsявляется ясным и однозначным, так как в первом случае , правильный ответ на оба вопроса будут получены ссылочной тестирования идентичности, и в последнем случае правильный ответ будет получен путем тестирования глубокого равенства ,

К сожалению, есть очень распространенный сценарий , при котором ответы на два вопрос расходятся: когда единственные сохранившиеся ссылки на объекты изменяемом типа проводится с помощью кода , который знает , что никаких ссылок на эти объекты никогда не будут проводиться кодом , который может мутировать их ни тест их для справки идентичности. В этом случае, если два таких объекта в настоящее инкапсулировать то же самое состояние, они всегда будут более сделать это, и , таким образом , эквивалентность должна быть основана на эквивалентности составляющих , а не на опорной идентичности. Другими словами, equalsдолжно быть основано на том , как вложенные объекты ответа на второй вопрос.

Поскольку смысл равенства зависит от информации , которая только известный владельцем ссылки, а не объект , идентифицированного ссылкой, это на самом деле не возможно для equalsметода , чтобы узнать , какой стиль равенства подходит. Типы , которые знают , что вещи , к которым они имеют ссылки могут спонтанно изменять следует проверить равенство ссылок этих составных частей, в то время как типы , которые знают , что они не будут меняться как правило , должны испытать глубокое равенство. Такие вещи , как коллекции должны позволить владельцу указать , будет ли вещи , хранящиеся в коллекции могут спонтанно меняться, и проверить равенство на этой основе; К сожалению, относительно немногие из встроенных в коллекции включают в себя любой такой объект (код можно выбрать , например , между HashTableиIdentityHashTableразличать , какой тест подходит для ключей, но большинство видов коллекций не имеют никакого эквивалентного выбора). Лучшее , что можно сделать , это , вероятно, каждый новый тип коллекции предложение в своем конструкторе на выбор режима инкапсуляции: Сосчитайте коллекция сама по себе как то , что может быть изменена без предварительного уведомления (отчета равенство ссылок на коллекции), предположим , что коллекция будет держать неизменное набор ссылок на вещи , которые могут измениться без предварительного уведомления (тест равенство ссылок на содержание), предположим , что ни коллекция , ни объекты , составляющие будут меняться (тест equalsкаждого составляющего объекта), или - для коллекций массивов или вложенных коллекций, не поддерживает тестирование глубокого равенства - выполнять сверхглубокое тестирование равенства на заданную глубину.

0

В идеале, метод равно () должен проверить логическое равенство. В некоторых случаях это может опуститься еще глубже, чем физический объект, а в других, она не может.

Если тестирование логического равенства не представляется возможным, в связи с выполнением или других проблем, то вы можете оставить реализацию по умолчанию, предоставляемое Object, а не полагаться на равных (). Например, вы не должны использовать свой график объект в качестве ключа в коллекции.

Блох делает сказать следующее:

Самый простой способ, чтобы избежать проблем, не переопределить метод равных, в этом случае каждый экземпляр класса равен только самому себе.