3

根据 Java 中的 Josh Bloch 所说:

除非您愿意放弃面向对象抽象的好处

所以这是我的情况,我有一个类Foo已被 Intellij Idea 覆盖equals()hashcode()实现。现在我有另一个类FooChild,它扩展Foo并添加了几个字段到“Foo”。现在 FindBugs 正在抱怨 FooChild:

类不覆盖超类中的equals 此类扩展了一个定义equals 方法并添加字段的类,但本身没有定义equals 方法。因此,此类实例上的相等性将忽略子类的身份和添加的字段。确保这是预期的,并且您不需要覆盖 equals 方法。即使您不需要重写 equals 方法,也请考虑重写它以记录子类的 equals 方法仅返回调用 super.equals(o) 的结果这一事实。

我的问题是“这个类的实例上的相等意味着什么将忽略子类的身份?我理解关于忽略添加字段的部分,因为equals()尚未为它们编写任何方法。

4

3 回答 3

5

“此类实例上的平等将忽略子类的身份”是什么意思?

如果类Foo有一个equals()方法,则FooChild继承它,这意味着如果您比较FooChildusing的两个实例,则将调用equals()该方法。Foo.equals()

如果FooChild有任何数据成员,那么两个实例可能FooChild在它们的部分中具有相同的成员值Foo,但在类中直接定义的成员的值不同。但是该Foo.equals()方法只会查看 中定义的成员Foo,因此会将两个这样的对象发音为equals(),即使它们的FooChild部分不同。

这就是为什么您需要equals()FooChild.

现在,如果你比较 aFoo和 a FooChildusing equals(),当两个类都有自己的版本时会发生equals()什么?这取决于您调用哪个对象equals(),并且取决于您如何实现这两种equals()方法。坦率地说,这是一团糟!这就是第一句话的含义,来自 Josh Bloch。定义这两种equals()方法是不可能的,所以它们总是做正确的事情。因此,最好避免一个值类(即身份与其成员变量的值绑定的类)扩展另一个值类的情况。

于 2012-07-27T11:50:09.403 回答
3

equals首先,在计算orhashCode方法时,不应包含来自超类的任何状态。该状态超出了您正在处理的类的范围,您的类应该依赖于超类而不是重写它的功能。

使用 IntelliJ生成equalshashCode使用时,如果您想包含来自父类的任何计算,只需对添加调用super.equals(o)or的方法进行小的修改super.hashCode()

实现这些方法的另一种方法是使用 Apache Commons Lang 库中的and EqualsBuilder这些构建器提供语义来添加超级调用。HashCodeBuilder

编辑:

回答“此类实例上的相等性意味着什么将忽略子类的身份?” :正如您已经指出的那样,这意味着如果您有两个类,一个扩展另一个类,并且子类既不覆盖equals或也不覆盖hashCode,那么,在检查两个实例是否相等(或添加到散列结构)时,两者都会表现得好像他们来自同一个班级。这是一个例子:

class A {
   private int intField = 2;

   public A(int value) {
       intField = value;
   }

   public boolean equals(Object o) {
       if (null == o) return false;
       if (this == o) return true;
       if (!(o instanceof A)) return false;

       return intField == ((A) o).intField;
   }

   public int hashCode() {
       return 11 * intField;
   }
}

class B extends A {
   private boolean boolField = true;

   public B(int intValue, boolean boolValue) {
       super(intValue);
       boolField = boolValue;
   }

   // no equals or hashCode
}

因此,使用这些课程,您将面临以下问题:

A a = new A(12);
B b = new B(12, false);
b.equals(a);    // returns true

为避免这种情况,只需在 IntelliJ 中生成它们后在内部equals或方法中添加一个超级调用。hashCode

class B extends A {
   private boolean boolField = true;

   public B(int intValue, boolean boolValue) {
       super(intValue);
       boolField = boolValue;
   }

   public boolean equals(Object o) {
       if (null == o) return false;
       if (this == o) return true;
       if (!(o instanceof B)) return false;
       if (!super.equals(o)) return false;

       return boolField == ((B) o).boolField;
   }

   public int hashCode() {
       int hash = super.hashCode();
       hash += 11 * Boolean.valueOf(boolField).hashCode();
       return hash;
   }

}

正如我已经说过的,其他方式是使用 Apache Commons Lang 库中的构建器。

于 2012-07-27T10:32:10.367 回答
1

处理此类问题的通常方法是诉诸组合而不是继承。这是必要的,因为正如 Josh Block 在他的优秀著作“Effective Java”中所详述的那样,你不能真正重写一个考虑属性的 equals(Object) 实现而不破坏 equals(Object) 的一般合同。

例子:

class Foo {
    boolean property;

    public boolean equals(Object that) {
        return this == that
            || (that instanceof Foo)
                && this.property == ((Foo) that).property;
    }

    public int hashCode() { ... } // needs to be consistent with equals(Object).
}

class Bar extends Foo {
    boolean anotherProperty;

    // This is broken - DO NOT USE IT!
    public boolean equals(Object that) {
        return super.equals(that)
            && (that instanceof Bar)
            && this.anotherProperty == ((Bar) that).anotherProperty
    }

    public int hashCode() { ... } // needs to be consistent with equals(Object).
}

现在为什么 Bar.equals(Object) 的实现被破坏了?仅仅因为它打破了对称性!这是证明:

Foo foo = new Foo();
Bar bar = new Bar();
assert foo.equals(bar); // passes because bar is a Foo and property is zero
assert bar.equals(foo); // throws up because foo isn't an instance of Bar!

使用继承无法解决此类问题。你需要使用组合。

因此,在覆盖它们时将 equals(Object) 和 hashCode() 方法声明为 final 通常是一个好主意,这样它们就不会因实现损坏而再次被覆盖。

于 2012-07-27T12:04:46.277 回答