У меня есть следующие два очень простых классов:Java равно супер и подклассов
public class A {
private int a;
public A(int a)
{
this.a=a;
}
public int getA(){
return a;
}
public boolean equals(Object o)
{
if (!(o instanceof A))
return false;
A other = (A) o;
return a == other.a;
}
}
И его подкласс:
public class B extends A{
private int b;
public B(int a, int b)
{
super(a);
this.b = b;
}
public boolean equals(Object o)
{
if (!(o instanceof B))
return false;
B other = (B) o;
return super.getA() == other.getA() && b == other.b;
}
}
Это может показаться право изначально, но в следующем случае это нарушает принцип симметрии общего контракта спецификации для объекта, который гласит: «Он симметричен: для любых непустых опорных значений x и y x.equals (y) должен возвращать истинное тогда и только тогда, когда y.equals (x) возвращает true. " http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html#equals(java.lang.Object) Неисправный случаем является следующее:
public class EqualsTester {
public static void main(String[] args) {
A a = new A(1);
B b = new B(1,2);
System.out.println(a.equals(b));
System.out.println(b.equals(a));
}
}
Первые возвращает истину, в то время как второй возвращает ложь. Одним из решений, которое может показаться правильным, было бы использовать getClass()
вместо instanceof
. Однако это было бы неприемлемо в тех случаях, когда мы ищем B в коллекциях. Например:
Set<A> set = new HashSet<A>();
set.add(new A(1));
Метод set.contains(new B(1,2));
вернет false. Этот пример как есть, может быть, не идеален для логической визуализации, но представьте, что A был классом транспортного средства, а B - классом автомобилей, а поле a - числом колес, а поле b - числом дверей. Когда мы называем метод сложения, мы в основном спрашиваем: «В нашем наборе содержится 4-колесная машина?» Ответ должен быть «да», поскольку он содержит его, независимо от того, является ли это автомобилем и количеством дверей, которые он имеет. Решение Джошуа Блох предлагает в Effective Java 2 Ed стр 40 не иметь B наследует от A и иметь экземпляр в качестве поля в B вместо:
public class B {
private A a;
private int b;
public B(int a, int b)
{
this.a = new A(a);
this.b = b;
}
public A getAsA()
{
return A;
}
public boolean equals(Object o)
{
if (!(o instanceof B))
return false;
B other = (B) o;
return a.getA() == other.getA() && b == other.b;
}
}
Однако, это означает, что мы теряем власть использовать наследование в большом количестве случаев, когда это необходимо? То есть, когда у нас есть классы с дополнительными свойствами, которые должны распространять более общие, чтобы повторно использовать код и просто добавлять их дополнительные более специфические свойства.
Вы используете композицию вместо наследования. В вашем случае, как вы думаете, принцип замещения Лискова применяется для B, если вы решите пойти на наследование? – SMA
Вы можете избежать этой проблемы, если идентифицируете свои объекты на основе одного уникального свойства ID, которое существует в базовом классе. Тогда вам не нужно переопределять реализацию equals() базового класса. Когда вы сравниваете два автомобиля, вы можете сравнить количество дверей и количество колес и 1000 других свойств, или вы можете просто сравнить идентификаторы vehice. Какой вариант имеет больше смысла для вас? – Eran
@Eran Имеет смысл использовать уникальные идентификаторы для сравнения объектов, но этого недостаточно в каждом случае. Представьте, что у вас есть автомобиль, и вы делаете глубокую копию, как только пользователь вводит определенную часть вашего приложения. Затем вы разрешаете пользователю решить, хочет ли он/она изменить этот оригинальный объект автомобиля. Затем, выходя из этой части приложения, вы хотите проверить, действительно ли пользователь изменил объект или нет. Таким образом, вы сравниваете его с оригинальной копией. Очевидно, что их идентификаторы будут одинаковыми, но их другие свойства могут отличаться. – eagerToLearn