Java 从抽象类派生时如何遵守equals()的约定
Joshua Bloch在他的《有效Java》一书中写到了当派生类向检查中添加额外字段时,Java 从抽象类派生时如何遵守equals()的约定,java,oop,inheritance,Java,Oop,Inheritance,Joshua Bloch在他的《有效Java》一书中写到了当派生类向检查中添加额外字段时,equals()契约中出现的陷阱。通常情况下,这会破坏对称性,但Bloch表示“可以在不违反equals契约的情况下向抽象类的子类添加值组件” 显然,这是正确的,因为抽象类不可能有实例,所以没有对称性可以违反。但是其他子类呢?我编写了这个示例,故意省略hashcode实现和空检查以保持代码简短: 公共抽象类车辆{ 私人最终字符串颜色; 公共车辆(字符串颜色){ 这个颜色=颜色; } 公共字符串getColo
equals()
契约中出现的陷阱。通常情况下,这会破坏对称性,但Bloch表示“可以在不违反equals契约的情况下向抽象类的子类添加值组件”
显然,这是正确的,因为抽象类不可能有实例,所以没有对称性可以违反。但是其他子类呢?我编写了这个示例,故意省略hashcode实现和空检查以保持代码简短:
公共抽象类车辆{
私人最终字符串颜色;
公共车辆(字符串颜色){
这个颜色=颜色;
}
公共字符串getColor(){
返回颜色;
}
@凌驾
公共布尔等于(对象o){
如果(this==o)返回true;
如果(!(车辆实例))返回false;
车辆=(车辆)o;
返回color.equals(that.color);
}
}
公营单车{
公共自行车(字符串颜色){
超级(彩色);
}
}
公车{
私有最终字符串模型;
公共汽车(字符串颜色、字符串型号){
超级(彩色);
this.model=模型;
}
@凌驾
公共布尔等于(对象o){
如果(this==o)返回true;
如果(!(汽车实例))返回false;
车那=(车)o;
返回getColor().equals(that.getColor())和&model.equals(that.model);
}
}
当我使用相同的颜色字符串创建每个类的一个实例时,equals()
的对称性被破坏:
Bicycle=新自行车(“蓝色”);
轿车=新车(“蓝色”、“梅赛德斯”);
bicycle.equals(car)通过比较类对象而不是执行instanceof
检查来解决问题
if (getClass() != obj.getClass()) {
return false;
}
以下是完整的实现(由Eclipse生成):
然后,示例中的两个检查都将产生false
equals()
基本上比较对象的状态。创建抽象类时,应该考虑如何处理对象的状态
在您的情况下,您的车辆具有特定颜色的状态。问题是:是否希望所有颜色相同的车辆都被视为同等车辆?然后将此问题的答案作为抽象类契约的一部分
如果您回答“是”:
很简单,只需将equals设置为final
如果您回答“否”:
你(完全可以理解)希望你的等式是对称的。让我们看看下面的代码:
Bicycle bike = new Bicycle("blue");
Car car = new Car("blue", "gtr");
assert car.equals(bike) == bike.equals(car);
这将因AssertionError而失败,因为在调用bike.equals(car)
时,您正在按bike的标准进行比较。要解决这个问题,可以为Bicycle实现equals。但是,您不希望查看所有类以确保有人没有忘记在某个地方实现equals
在这种情况下,在抽象父类中确保不同的子类返回false就足够了。这可以通过替换if(!(o instanceof P))返回false来实现如果(!getClass().equals(o.getClass())返回false,则使用的code>代码>。你的对称性将被保留。的对称性被破坏主要是因为Bicycle
类是一个子类,它依赖于超级类(Vehicle)
,因为它本身是平等的。如果为每个子类定义equals()
方法,则不会遇到此问题
下面是每个类的equals()
实现。(仅添加了Bicycle
equals()
,其他实现相同但简化。)
或您应该在超级类实现中使用对象.getClass()
而不是@FranzBecker建议的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);
}
}
Java的equals契约在这种情况下变得特别不稳定,最终这一切都成为程序员偏好和需求的问题。我记得我遇到过同样的问题,在考虑使用Java的equals契约时,我讨论了几个可能性和问题。它基本上是说,如果不打破Java equals契约,就无法正确地完成它
在处理抽象类时,我个人的偏好是根本不给抽象类一个equals方法。这没有道理。不能有两个抽象类型的对象;你应该如何比较它们?相反,我给每个子类赋予它自己的等号,并且每当调用equals()
时,运行时都会处理其余的。总的来说,我最常遵循的文章中提出的解决方案是“只能比较完全相同类别的对象”,这对我来说似乎是最明智的。如果o
是Bicycle
在o instanceof Car
中的一个实例,会发生什么?这是错误的。问题是另一个方向,当在抽象超类的equals()
方法中检查Car
实例时,是什么意思当我用相同的颜色字符串创建每个类的一个实例时,equals()的对称性被破坏。
@StephanWindmüller你是对的,但是抽象类可能没有实例。可以使用this.getClass().isInstance()
来执行此操作,但每个类都应该实现自己的equals
和hashcode
方法。@wero我在原始问题中添加了一个示例。equals方法应该首先检查两个对象是否属于同一类型,而不是调用super.equals(obj)
在那之前。它将调用超级类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
}
}
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);
}
}