当前位置: 首页 > 知识库问答 >
问题:

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

齐昆
2023-03-14

约书亚·布洛赫(Joshua Bloch)在他的书《有效的Java》中写道,当派生类向检查中添加额外字段时,equals()的契约会出现陷阱。通常情况下,这会破坏对称性,但布洛赫表示,“可以在不违反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()也可以达到同样的效果。

共有3个答案

萧献
2023-03-14

equals()的对称性主要被破坏了,因为自行车类是一个子类,它依赖于超级类(车辆)来实现它自己的相等性。如果您为每个子类定义equals()方法,那么您就不会遇到这个问题。

下面是每个类的equals()实现。(只添加了自行车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
  }
}

或者你应该使用对象。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);
  }
}
陆文博
2023-03-14

比较类对象而不是执行instanceof检查可以解决问题。

        if (getClass() != obj.getClass()) {
            return false;
        }

以下是完整的实现(由Eclipse生成):

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;
    }


}

然后,示例中的两个检查都将产生false

殷功
2023-03-14

Java的equals契约在这种情况下变得特别不稳定,最终这一切都变成了程序员偏好和需求的问题。我记得我也遇到过同样的问题,我看到了这篇文章,在考虑Java的equals契约时,它讨论了几种可能性和问题。它最终基本上会说,如果不打破Java equals契约,就无法正确地完成它。

在处理抽象类时,我个人的偏好是根本不给抽象类一个equals方法。这没有道理。不能有两个抽象类型的对象;你应该如何比较它们?取而代之的是,我给每个子类赋予它自己的等号,并且每当调用equals()时,运行时都会处理其余的。总的来说,我在这篇文章中最常遵循的解决方案是“只有同一类的对象才能被比较”,这对我来说似乎是最明智的。

 类似资料:
  • 我仍然试图掌握抽象基类的概念,以及从派生类中可以做什么和不能做什么。 我有以下代码: 基类中的示例- 在派生类中

  • 我学会了通过存储基类指针将派生类指针存储在基类向量中: 但是如果我有一个抽象基类: 从中派生出另外两个抽象类。 以及来自二级抽象类的其他几个派生类: 是否有可能将它们全部存储在多态性载体中?和往常一样,我做了以下工作: 但是如何将两个多态向量存储在基类向量中呢?

  • 是否有任何JAXB绑定可以告诉JAXB代码生成器将Java类生成为,而不必在XSD中将相应的XML类型标记为? 情况如下: > 我在xsd中定义架构: 我使用内联JAXB绑定(“inline”==“直接在模式中”)来指示应该生成JAXB类的包(): 我使用内联JAXB绑定为我的每个复杂类型(在本例中、和)指示实现类的名称: 我从模式生成JAXB类。这导致: 我自己编写类: 使用这两个类层次结构这样

  • 问题内容: 有这样的事情: 抽象类: 和扩展器: 我想要的是扩展,因为如果不处理它,然后尝试在中处理它。例如add- 但同时让类处理默认值,例如。 这是完全错误的方法吗? 到目前为止,我所做的就是添加一个接口。通常: 在课堂上: 在课堂上: 这看起来像是可用的设计吗? 并且,主要问题: 有没有什么好办法扩展该类,以便可以使用与in 中相同的切换方法?我想知道的是,有没有一个更好的设计,第二个是是否

  • 我们不能创建一个抽象类的对象,对吧?那么如何调用在抽象基类和派生类中都有定义的虚函数呢?我想在抽象基类中执行代码,但目前,我正在使用派生类的对象。 有什么方法可以使用对象t调用基类函数吗?如果不可能,我需要做什么来调用基类函数?

  • 问题内容: 结果如下: 2011-09-24 14:10:51 -0400 2011年9月24日星期六20:10:51 为什么当我解析来自format()的日期时,它不遵守时区? 问题答案: 您正在打印调用的结果,该调用 始终 使用默认时区。基本上,除了调试之外,您不应该使用其他任何东西。 不要忘记,一个不 具有 时区-它代表着一个时刻,因为自Unix纪元(午夜1970年1月1日UTC)毫秒。 如