【问题标题】:overriding equals method when dealing with inheritance处理继承时覆盖equals方法
【发布时间】:2013-05-16 06:26:50
【问题描述】:

我一直在阅读有关在处理子类时如何最好地覆盖 equals 方法的文章,在这里我找到了很多帖子。他们推荐使用 instanceof 或 getClass() 来比较不同子类的对象来实现解决方案的不同方法。

但是关于 Effective Java,我的理解是(而且我是新手,所以我很可能是错的!)Bloch 认为最终两者都可能有问题,“没有办法扩展可实例化的类和在保留 equals 契约的同时添加一个值组件,除非您愿意放弃面向对象抽象的好处”。然后建议“优先组合而不是继承”。

所以我正在处理这个类层次结构:AbstractClass、ConcreteClass1 和 ConcreteClass2。 ConcreteClass1 扩展 AbstractClass 并且 ConcreteClass2 扩展 ConcreteClass1。目前只有 AbstractClass 覆盖了 equals 方法。

所以在 AbstractClass 中:

public abstract class AbstractClass {
        private String id;


        public boolean equals(Object other) {
            return other != null && other.getClass().equals(getClass())
                    && id.equals(((AbstractClass) other).id);
        }

    }

在 ConcreteClass1 我有:

public class ConcreteClassOne extends AbstractClass
{
  private final AbstractClass parent;

  public ConcreteClassOne( String anId, AbstractClass aParent )
  {
    super( anId );

    parent = aParent;
  }

}

我终于在 ConcreteClassTwo 中:

public class ConcreteClassTwo extends ConcreteClassOne
{
  private static int nextTrackingNo = 0;

  private final int trackingNo;

  public ConcreteClassTwo ( String anId )
  {
    super( anId, null );

    trackingNo= getNextTrackingNo();
  }
}

所以我认为我需要在 ConcreteClassOne 和 ConcreteClassTwo 中重写 equals 方法以包含重要字段 parent 和 trackingNo。我不允许更改设计,因此不能选择使用合成。有什么建议?

【问题讨论】:

  • 那么...问题/问题是什么?重写equals方法没有错。
  • 问题是关于在尊重其契约的同时覆盖equals。
  • @MarkoTopolnik 你明白我的意思!
  • @McDowell 谢谢,我会遇到那个,但坦率地说,仍然没有机会完全研究它。

标签: java inheritance overriding equals abstraction


【解决方案1】:

基类契约应该指定两种方法之一:或者它应该声明任何派生类对象都不应该将自己视为不属于完全相同的类的任何其他对象,或者它应指定每个派生类对象都应可转换为基类协定定义的规范形式,如果规范形式匹配,则两个不可变对象应被视为等效。

后一种情况的一个示例是具有方法int GetSize()float GetCell(int row, int column) 的ImmutableSquareFloatMatrix 基类。一个常见的实现会在其中包含一个 (size*size) 浮点值数组,但也可以有例如ZeroMatrixIdentityMatrix 类,其唯一字段指定大小,ConstantMatrix 类,其字段指定大小,字段指定应为每个单元格返回的值,DiagonalMatrix 类具有一维仅包含对角线项目的数组(GetCell 方法将对其他所有内容返回零),等等。

给定两个派生自ImmutableSquareFloatMatrix 的类实例,可以通过比较它们的大小然后比较其中的所有值来比较它们,但在许多情况下这样做效率低下。如果被比较的对象中的任何一个“知道”另一个对象的类型,那么就有可能极大地提高效率。如果两个对象都不知道另一个对象,则回退到默认比较方法可能会很慢,但无论如何都会产生正确的结果。

处理这种情况的可行方法可能是让基类型实现equals2 方法,如果它对另一个对象的特殊知识意味着它可以判断它相等,则返回 1,如果它可以判断它则返回 -1不相等,如果无法分辨,则为 0。如果任一类型的 equals2 方法知道它们不相等,则它们不相等。否则,如果任何一方知道他们是平等的,他们就是平等的。否则,使用逐个单元格比较来测试相等性。

【讨论】:

    【解决方案2】:

    最简单的方法是在具体类和抽象类中扩展equals()。

    public class ConcreteClassTwo extends ConcreteClassOne {
        public boolean equals(Object other) {
            boolean rv = super.equals( other );
            if ( other instanceof ConcreteClassTwo ) {
               rv = rv && (this.trackingNo == ((ConcreteClassTwo) other).trackingNo);
            }
            return rv;
        }
    }
    

    【讨论】:

    • 谢谢!关于 instanceof 和 getClass(),我是否可以说坚持使用 getClass() 更安全?即两个不同子类的对象永远不应该相同。假设如果我将 ConcreteClass1 扩展为具有 ConcreteClass3,则从 CC2 和 CC3 创建的对象即使具有相同的内容也不相等。
    • @user2397607 - 在你的情况下,是的。一般来说,Bloch 更喜欢 instanceof 而不是 getClass(),以支持不添加任何状态的琐碎子类。但是在这里,您对 AbstractClass.equals() 的实现和我对 ConcreteClassTwo.equals() 的实现感到满意。有了这些,c1.equals(c2) == c2.equals(c1)。
    • 但是你打破了传递性,所以这里没有什么收获。
    • @MarkoTopolnik 那么布洛赫的“组合胜过继承”是最后的解决方案吗?也就是说,就目前而言,这是一个设计问题,而不是实现问题。
    • 如果你有c2ac1c2b(分别是ConcreteClassTwo、One和Two的实例),你可以有c2a.equals(c1)c1.equals(c2b)而不保证@987654327 @.
    【解决方案3】:

    如果ConcreteClassOneConcreteClassTwo 中都有equals,则equals 的对称性被破坏:

    Object c1 = new ConcreteClassOne(),
           c2 = new ConcreteClassTwo();
    System.out.println("c1=c2? " + c1.equals(c2)");
    System.out.println("c2=c1? " + c2.equals(c1)");
    

    现在,如果你按照通常的方式实现equals,就会打印出来

    true
    false
    

    因为在c2.equals 中有instanceof ConcreteClassTwo,而c1 则失败,但在相反的情况下类似的检查通过。

    【讨论】:

    • 是的,我认为您需要对这一点进行额外的解释/确认。 Bloch 是对的,除了仅在条件 a.getClass()==b.getClass() 上允许相等之外,您无能为力。
    • 是的,正如我所提到的,我是这个概念的新手,因此我很感激确认我从布洛赫的观点所理解的内容。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-11-20
    • 2014-05-27
    • 2018-09-30
    • 2016-06-04
    • 2016-01-25
    • 2011-06-14
    • 1970-01-01
    相关资源
    最近更新 更多