Java 从抽象类派生时如何遵守equals()的约定

Java 从抽象类派生时如何遵守equals()的约定,java,oop,inheritance,Java,Oop,Inheritance,Joshua Bloch在他的《有效Java》一书中写到了当派生类向检查中添加额外字段时,equals()契约中出现的陷阱。通常情况下,这会破坏对称性,但Bloch表示“可以在不违反equals契约的情况下向抽象类的子类添加值组件” 显然,这是正确的,因为抽象类不可能有实例,所以没有对称性可以违反。但是其他子类呢?我编写了这个示例,故意省略hashcode实现和空检查以保持代码简短: 公共抽象类车辆{ 私人最终字符串颜色; 公共车辆(字符串颜色){ 这个颜色=颜色; } 公共字符串getColo

Joshua Bloch在他的《有效Java》一书中写到了当派生类向检查中添加额外字段时,
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来实现的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);
  }
}