Давайте кратко рассмотрим идентичность и равенство в Java, то, что они есть, и как они сравниваются друг с другом.
Вам также может понравиться:
Идентичность объекта и равенство в Java
Ключевые вынос
- Не включайте поля ID в ваши классы домена Java.
- Используйте все поля в вашем
equals()
иhashCode()
реализации.
объяснение
Я видел много Java кода , где разработчики реализовать equals()
и hashCode()
с точки зрения поля ID или подмножества полей, образующих составной ключ. Это делается так часто, что становится паттерном, когда, на мой взгляд, на самом деле это анти-паттерн . Чтобы понять, почему это анти-шаблон, мы должны дифференцировать идентичность от равенства:
тождественность
Когда мы говорим об идентичности в информатике, мы обычно думаем о чем-то, что однозначно идентифицирует человека или вещь. В Java мы используем ссылки для уникальной идентификации объектов. Это также называется ссылочным равенством (да, наименование сбивает с толку). Вы используете ==
для сравнения идентичности двух объектов.
Обратите внимание, что личность является чем-то внешним. Ссылка не является частью объекта, она просто указывает на объект. Другим важным моментом является то, что личность не меняется с течением времени: когда я становлюсь старше, я претерпеваю много изменений, но я все тот же человек.
равенство
Равенство означает, что два объекта являются одинаковыми. Два равных объекта не обязательно означают, что они являются одним и тем же объектом. В Java мы используем equals()
метод, чтобы проверить, равны ли два объекта. Это также называется структурным равенством.
Равенство всегда можно решить, взглянув только на объект. Вам не нужно никакой внешней информации, чтобы решить равенство. Равенство со временем может измениться: я не равен тому, кем был 20 лет назад.
Объекты реального мира
Когда вы моделируете объекты реального мира, у вас обычно есть какой-то идентификатор для ссылки на него. Например, у вас может быть простое определение продукта с идентификатором и ценой:
Джава
1
public class Product {
2
private final String id;
3
private BigDecimal price;
4
// rest of the code is omitted for brevity
5
}
Теперь вам нужен способ проверить, равны ли два продукта. Итак, вы идете вперед и внедряете метод равных
Джава
xxxxxxxxxx
1
public boolean equals(Object o) {
2
if (o == this) return true;
3
if (o == null || this.getClass() != o.getClass())
4
return false;
5
Product that = (Product)o;
6
return this.id.equals(that.id);
7
}
Мы использовали поле идентификатора, как и большинство разработчиков Java, но так ли это? Давайте проверим это:
Джава
xxxxxxxxxx
1
new Product("IBM", 142.05)
2
.equals(new Product("IBM", 110.03)) // true
Я думаю, что мы можем согласиться с тем, что мы могли бы потенциально потерять много денег, если бы мы полагались на эту равную реализацию для принятия наших торговых решений. Так что здесь произошло?
Что мы действительно хотели, так это проверить, имеем ли мы в виду один и тот же продукт. Но ссылочное равенство, если оно не настраивается в Java, всегда основано на адресе памяти, и мы не можем это изменить. Если бы мы могли, мы бы заставили его использовать поле идентификатора, но мы не можем, поэтому мы отступаем к выполнению этого в равных, что неправильно.
Другая вещь, которую мы, кажется, забыли, это то, что идентичность является внешней. Это не свойство самого объекта. Это внешняя ссылка, поэтому она должна быть даже частью объекта. Как мы можем достичь этого в Java? Мы можем просто использовать карту:
Джава
xxxxxxxxxx
1
Map<String, Product> products = ...
2
products.get("IBM") == products.get("IBM") // true
3
products.get("IBM") == products.get("459200101") // true
Как видите, мы получаем то, что ожидаем. Мы можем даже использовать разные идентификаторы для ссылки на один и тот же продукт, если наша Карта имеет все различные типы идентификаторов продуктов. Таким образом, ссылочное равенство работает как ожидалось; Теперь мы можем двигаться дальше, чтобы исправить наши равные:
Джава
xxxxxxxxxx
1
public class Product {
2
// id is not part of the object any more
3
private BigDecimal price;
4
// equals is implemented in terms of all the fields
5
public boolean equals(Object o) {
6
if (o == this) return true;
7
if (o == null || this.getClass() != o.getClass())
8
return false;
9
Product that = (Product)o;
10
return this.price.compareTo(that.price) == 0;
11
}
12
}
И таким образом, мы получаем, equals()
что напрямую не ведет к банкротству:
Джава
xxxxxxxxxx
1
new Product(142.05).equals(new Product(110.03)) // false
Вывод
- Не включайте поля ID в ваши классы домена Java.
- Используйте все поля в вашем
equals()
иhashCode()
реализации.