从抽象类派生时如何服从equals()的契约

时间:2015-10-04 07:21:19

标签: java oop inheritance

在他的书 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 (),可以实现同样的效果。

4 个答案:

答案 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;即可完成此操作。你的对称性将被保留。