在他的书 Effective Java 中,Joshua Bloch写了关于派生类在检查中添加其他字段时equals()
合同中发生的陷阱。通常情况下,这会打破对称性,但Bloch声明“你可以将值组件添加到 abstract 类的子类中,而不会违反equals合约”。
显然这是真的,因为没有抽象类的实例,所以没有违反的对称性。但是其他子类呢?我写了这个例子,故意省略哈希码实现和空检查以保持代码简短:
public abstract class Vehicle {
private final String color;
public Vehicle(String color) {
this.color = color;
}
public String getColor() {
return color;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Vehicle)) return false;
Vehicle that = (Vehicle) o;
return color.equals(that.color);
}
}
public class Bicycle extends Vehicle {
public Bicycle(String color) {
super(color);
}
}
public class Car extends Vehicle {
private final String model;
public Car(String color, String model) {
super(color);
this.model = model;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Car)) return false;
Car that = (Car) o;
return getColor().equals(that.getColor()) && model.equals(that.model);
}
}
当我使用相同的颜色字符串创建每个类的一个实例时,equals()
的对称性被打破:
Bicycle bicycle = new Bicycle("blue");
Car car = new Car("blue", "Mercedes");
bicycle.equals(car) <- true
car.equals(bicycle) <- false
我不确定如何处理这个最好的方法。在抽象类中将equals()
声明为抽象,以在子类中强制实现?但是,通过在抽象类中完全不声明equals ()
,可以实现同样的效果。
答案 0 :(得分:3)
Java等于合同在这样的情况下变得特别不稳定,最终这一切都成为程序员的偏好和需求的问题。我记得遇到过这个同样的问题,我遇到了this article,在考虑与Java的平等合同时,它考虑了几种可能性和问题。它基本上最终说在没有违反Java等于合同的情况下没有办法正确地做到这一点。
在处理抽象类时,我个人的偏好是根本不给抽象类一个equals方法。这没有意义。你不能有两个抽象类型的对象;你应该如何比较它们?相反,我给每个子类赋予自己的等号,运行时在调用equals()
时处理其余的子类。总的来说,我最常关注的文章中提到的解决方案是“可以比较完全相同类别的唯一对象”,这对我来说似乎是最明智的。
答案 1 :(得分:1)
比较类对象而不是进行<ion-view view-title="Details">
<ion-content class="padding">
<ion-item class="item " ng-repeat="exercise in historyExercises" >
<div class="row">
<div>
<p><h2>{{exercise.exerciseName}}</h2></p>
<p ng-repeat="(parameter, value) in exercise.parameters">
{{parameter}}: {{value}}
</p>
</div>
</div>
</ion-item>
</ion-content>
</ion-view>
检查可以解决问题。
instanceof
这是完整的实现(由Eclipse生成):
if (getClass() != obj.getClass()) {
return false;
}
您的示例中的两个检查都会产生public class Vehicle {
// ...
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Vehicle other = (Vehicle) obj;
if (color == null) {
if (other.color != null) {
return false;
}
} else if (!color.equals(other.color)) {
return false;
}
return true;
}
}
public class Car extends Vehicle {
// ...
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!super.equals(obj)) {
return false;
}
if (getClass() != obj.getClass()) {
return false;
}
Car other = (Car) obj;
if (model == null) {
if (other.model != null) {
return false;
}
} else if (!model.equals(other.model)) {
return false;
}
return true;
}
}
。
答案 2 :(得分:1)
equals()
的对称性主要被打破,因为Bicycle
类是一个子类,它依赖于超类(Vehicle)
,因为它自己的等式。如果为每个子类定义equals()
方法,则不会遇到此问题。
以下是每个类的equals()
实现。 (仅添加Bicycle
equals()
,其他实现相同但简化。)
public abstract class Vehicle {
....
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Vehicle)) return false;
Vehicle that = (Vehicle) o;
return color.equals(that.color);
}
}
public class Bicycle extends Vehicle {
...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Bicycle)) return false;
Bicycle that = (Bicycle) o;
return super.getColor().equals(that.getColor());
}
}
public class Car extends Vehicle {
...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof Car)) return false;
if (!super.equals(o)) return false;
Car car = (Car) o;
return model.equals(car.model);
}
}
// This is main class for testing the above functionality.
class MainClass {
public static void main(String[] args) {
Bicycle bicycle = new Bicycle("blue");
Car car = new Car("blue", "Mercedes");
System.out.println(bicycle.equals(car)); -> false
System.out.println(car.equals(bicycle)); -> false
}
}
OR 您应该在@FranzBecker建议的超类实现中使用object.getClass()
而不是instanceof
运算符。子类仍然可以使用instanceOf
运算符而没有任何问题。
public abstract class Vehicle {
...
@Override
public boolean equals(Object o) {
if (this == o) return true;
if ((this.getClass() != o.getClass())) return false;
Vehicle that = (Vehicle) o;
return color.equals(that.color);
}
}
答案 3 :(得分:0)
equals()
基本上比较了对象的状态。在创建抽象类时,您应该考虑如何处理对象的状态。
在您的情况下,您的车辆处于具有特定颜色的状态。问题是:您是否希望所有具有相同颜色的车辆都被视为平等?然后将此问题的答案作为抽象类合同的一部分。
如果您回答是:
很简单,只需将等于最终。
如果您回答否:
你(完全可以理解)希望你的平等是对称的。我们来看看以下代码:
Bicycle bike = new Bicycle("blue");
Car car = new Car("blue", "gtr");
assert car.equals(bike) == bike.equals(car);
这将因AssertionError而失败,因为在调用bike.equals(car)
时,您正在按照自行车的标准进行比较。要解决此问题,您可以为Bicycle实现equals。但是,您不希望查看所有类,以确保有人不会忘记在某处实现equals。
在这种情况下,在您的抽象父级中确保不同的子类返回false就足够了。只需将if (! (o instanceof P)) return false;
替换为if (!getClass().equals(o.getClass())) return false;
即可完成此操作。你的对称性将被保留。