Метод Java - equals в базовом классе и в подклассах
У меня есть простой базовый класс, который позже расширяется многими отдельными классами, которые потенциально могут вводить новые поля, но не обязательно. Я определил метод equals в базовом классе, но также переопределить это для нескольких подклассов. Можно ли смешивать определения в базе/подклассах? В моем случае это было во избежание дублирования кода, проверяющего те же поля.
Ответы
Ответ 1
Взгляните на "Внедрение equals(), чтобы разрешить сравнение смешанного типа" от Angelika Langer.
Вот краткое описание некоторых проблем и возможное решение:
В уравнении равенства говорится (среди прочих):
Он симметричен: для любых непустых опорных значений x и y x.equals(y) должен возвращать true тогда и только тогда, когда y.equals(x) возвращает true.
Это означает, что у вас могут возникнуть проблемы, если ваш подкласс вводит новые поля, и вы сравниваете объект базового класса (или другого подкласса, который не переопределяет равные) объекту этого подкласса.
НЕ делать следующее:
class BaseClass {
private int field1 = 0;
@Override
public boolean equals(Object obj) {
if (obj instanceof BaseClass) {
return field1 == ((BaseClass) obj).field1;
}
return false;
}
}
class BadSubClass extends BaseClass {
private int field2 = 0;
@Override
public boolean equals(Object obj) {
if (obj instanceof BadSubClass) {
return super.equals(obj)
&& field2 == ((BadSubClass) obj).field2;
}
return false;
}
}
потому что вы получаете
BaseClass baseClass = new BaseClass();
BadSubClass subClass = new BadSubClass();
System.out.println(baseClass.equals(subClass)); // prints 'true'
System.out.println(subClass.equals(baseClass)); // prints 'false'
Возможное решение:
Замените instanceof
-check сравнением классов:
obj != null && obj.getClass() == getClass()
При таком решении объект BaseClass
никогда не будет равен объекту любого подкласса.
Если вы создаете еще один SubClass
без @Override
метода equals
, два SubClass
-объекта могут быть равны друг другу (если проверка BaseClass.equals
принимает это решение) из коробки, но a SubClass
-объект никогда не будет равен объекту BaseClass
.
Хорошая реализация может быть следующей:
class BaseClass {
private int field1 = 0;
@Override
public boolean equals(Object obj) {
if (obj != null && obj.getClass() == getClass()) {
return field1 == ((BaseClass) obj).field1;
}
return false;
}
}
class GoodSubClass extends BaseClass {
private int field2 = 0;
@Override
public boolean equals(Object obj) {
if (obj instanceof GoodSubClass) {
return super.equals(obj) && field2 == ((GoodSubClass) obj).field2;
}
return false;
}
}
Пожалуйста, обратитесь к статье, упомянутой выше, для более сложных проблем и их решений.
Ответ 2
Нет, невозможно согласовать равный контракт при введении новых полей, имеющих отношение к методу equals. См. "Эффективная Java" Джошуа Блоха для получения дополнительной информации.
Edit:
У меня сейчас нет книги, но я думаю, что это нормально, если базовый класс является абстрактным/не может быть создан.
Ответ 3
Я думаю, что это прекрасно, если вы следуете eqauls() и hashcode().
Ответ 4
Вы можете использовать метод super()
для вызова метода класса, который вы распространяете, чтобы предотвратить необходимость дублирования кода
public class BaseClass {
public boolean equals(BaseClass other) {
return (other.getBlahblah() == this.Blahblah && .....);
}
}
public class DerivedClass extends BaseClass {
public boolean equals(DerivedClass other) {
return (super(other) && other.getNewAttribute() == this.NewAttribute.....);
}
}
Ответ 5
Достаточно правильный подход. Проблема заключается в одном из ваших подклассов: должен сохранять определение equals как связанное с его родителем. Кроме того, у вас есть сломанная функция равенства, которая может вызвать некоторые очень уникальные сценарии во время выполнения.
Ответ 6
Я предполагаю, что это идеально, чтобы обеспечить реализацию метода equals(Object obj)
и hashCode()
в super
class
как Java
. Мы все знаем, что Java предоставляет реализацию метода hashCode() and equals(Object obj)
в базовом классе java.lang.Object, и когда когда это требуется, мы override
их в нашем class
.
Ответ 7
В то время как следующее не обрабатывает каждый случай, я нашел его вполне практичным. Я использовал это много раз, когда у меня есть SuperClass и SubClass в игре. Я не хочу сравнивать их, но я также не хочу повторно реализовывать все значения SuperClass equals() для SubClass. Он обрабатывает:
- a.equals(b) == b.equals(a)
- Не дублирует код сравнения полей
- Легко обобщается для любой глубины подкласса
- Subclass.equals(SuperClass) == false
- Superclass.equals(SubClass) == false
Пример кода
// implement both strict and asymmetric equality
class SuperClass {
public int f1;
public boolean looseEquals(Object o) {
if (!(o instanceof SuperClass)) return false;
SuperClass other = (SuperClass)o;
return f1 == other.f1;
}
@Override public boolean equals(Object o) {
return looseEquals(o) && this.getClass() == o.getClass();
}
}
class SubClass extends SuperClass {
public int f2;
@Override public boolean looseEquals(Object o) {
if (!super.looseEquals(o)) return false;
if (!(o instanceof SubClass)) return false;
SubClass other = (SubClass)o;
return f2 == other.f2;
}
// no need to override equals()
}
Ответ 8
Если вы не напишите свой код правильно, это создаст серьезную проблему, называемую
асимметрия (нарушает контракт на равенство), поэтому давайте посмотрим наши варианты.
Лучшая практика - стратегия одинаковых классов. Если B является подклассом A
и каждый класс имеет свой собственный метод equals, реализованный
используя ту же стратегию классов, то класс B должен быть
объявлен окончательным, чтобы предотвратить введение асимметричной
определение равных в любом будущем подклассе B.
Вопрос. Что, если мы не хотим сделать финал B?
Используйте Композицию вместо Наследования. Всякий раз, когда классы B и
A, где B является подклассом A, требуют различных методов равенства,
использование композиции вместо наследования является хорошей стратегией, и
если сделать финал класса B не вариант, это единственный безопасный способ
обращаться с равными.
Как?
public class A{
public boolean equals(Object ob){
//write your code here
}
}
class B{
A a= new A();
public B(A a){
this.a= a;
}
public boolean equals(Object ob){
//...write your code here
if(!((B)ob).a).equals(a)) return false;
//...write your code here
}
}