Лучшая практика выбора полей для реализации equals()

При написании модульных тестов я часто сталкиваюсь с ситуацией, когда equals() для какого-либо объекта в тестах - в assertEquals - должен работать иначе, чем он работает в реальной среде. Возьмем, например, некоторый интерфейс ReportConfig. Он имеет id и несколько других полей. Логично, что один config равен другому, когда их соответствие id. Но когда дело доходит до тестирования некоторой конкретной реализации, скажем, XmlReportConfig, очевидно, я хочу сопоставить поля all. Одно из решений - не использовать equals в тестах и ​​просто перебирать свойства объекта или поля и сравнивать их, но это не похоже на хорошее решение.

Итак, помимо этого конкретного типа ситуаций, я хочу разобраться, какие лучшие практики для реализации equals, семантически, а не технически.

Ответ 1

Каковы лучшие практики для реализации равных, семантически, а не технически.

В Java метод equals действительно должен рассматриваться как "тождество равно" из-за его интеграции с реализациями Collection и Map. Учтите следующее:

 public class Foo() {
    int id;
    String stuff;
 }

 Foo foo1 = new Foo(10, "stuff"); 
 fooSet.add(foo1);
 ...
 Foo foo2 = new Foo(10, "other stuff"); 
 fooSet.add(foo2);

Если идентификатор Foo - это поле id то 2-й fooSet.add(...) не должен добавлять другой элемент в Set но должен возвращать false поскольку foo1 и foo2 имеют одинаковый id. Если вы определите Foo.equals (и hashCode) для включения полей id и stuff то это может быть нарушено, поскольку Set может содержать 2 ссылки на объект с одинаковым полем id.

Если вы не храните свои объекты в Collection (или Map), вам не нужно определять метод equals таким образом, однако многие считают его плохим тоном. Если в будущем вы сохраните его в Collection все будет сломано.

Если мне нужно проверить на равенство всех полей, я склонен написать другой метод. Что-то вроде equalsAllFields(Object obj) или что-то подобное.

Тогда вы бы сделали что-то вроде:

assertTrue(obj1.equalsAllFields(obj2));

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

 Point p = new Point(1, 2);
 // ColoredPoint extends Point
 ColoredPoint c = new ColoredPoint(1, 2, Color.RED);
 // this is true because both points are at the location 1, 2
 assertTrue(p.equals(c));
 // however, this would return false because the Point p does not have a color
 assertFalse(c.equals(p));

Еще одна статья, которую я очень рекомендую, - это раздел "Подводный камень # 3: Определение равных в терминах изменяемых полей" на этой замечательной странице:

Как написать метод равенства в Java

Некоторые дополнительные ссылки:

Да, и просто для потомков, независимо от того, какие поля вы хотите сравнить для определения равенства, вам нужно использовать те же поля в расчете hashCode. equals и hashCode должны быть симметричными. Если два объекта равны, они должны иметь одинаковый хэш-код. Противоположное не обязательно верно.

Ответ 2

Скопировано из Object.equals(Object obj) javadoc:

Указывает, является ли какой-либо другой объект "равным" этому.

Метод equals реализует отношение эквивалентности для ненулевых ссылок на объекты:

  • Это рефлексивно: для любого ненулевого опорного значения х, x.equals(х) должна возвращать истинное.
  • Он симметричен: для любых непустых опорных значений x и y x.equals(y) должен возвращать true тогда и только тогда, когда y.equals(x) возвращает true.
  • Это транзитивно: для любых непустых опорных значений x, y и z, если x.equals(y) возвращает true, а y.equals(z) возвращает true, тогда x.equals(z) должен возвращать true.
  • Это согласовано: для любых непустых опорных значений x и y несколько вызовов x.equals(y) последовательно возвращают true или последовательно возвращают false, если информация, используемая при равных сравнениях с объектами, не изменяется.
  • Для любого ненулевого опорного значения х, x.equals(NULL) должен возвращать ложь.

Это довольно ясно для меня, вот как должны работать равные. Что касается того, какие поля выбрать, вы выбираете, какая комбинация полей требуется для определения , является ли какой-либо другой объект "равным" этому.

Что касается вашего конкретного случая, если вам, в вашем тесте, нужна более широкая область для равенства, тогда вы реализуете это в своем тесте. Вы не должны взламывать свой метод equals, чтобы сделать его пригодным.

Ответ 3

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

Это от эффективной Java:

Для каждого "значимого" поля в классе проверьте, соответствует ли это поле аргумента соответствующему полю этого объекта.

Если вы хотите сопоставить идентификаторы, потому что это уникальный идентификатор для этого класса, тогда просто сравните значение id, не используйте в этом случае равные.

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

Ответ 4

Я бы не подумал о unit test, когда пишут equals(), и те, и другие разные.

Вы определяете равенство каждого объекта с помощью одной или группы свойств путем реализации equals() и hashcode().

В вашем тесте, если вы хотите сравнить все свойства объекта, то, очевидно, вам нужно вызвать каждый метод.

Я думаю, что лучше относиться к ним отдельно.

Ответ 5

Я считаю, что единственной лучшей практикой при переопределении метода equals() является здравый смысл.

Нет правил, кроме определения эквивалентности API Java. После того, как вы выбрали это определение равенства, вы должны применить его к вашему методу hashCode().

Под этим я подразумеваю, что в качестве разработчика вы, ваши коллеги и сопровождающие должны знать, когда ваш экземпляр let say a Watermelon равен другому экземпляру объекта.