28

假设有两个这样的类:

abstract class A { /* some irrelevant methods */ }

class B extends A {
    public final int x;

    public B(final int x) {
        this.x = x;
    }

    /* some more irrelevant methods */
}

然后我使用 Eclipse 的“Source → Generate hashCode() and equals()...”在类上生成equals()and方法。但 Eclipse 警告我:hashCode()B

超类 'com.example.test2.A' 不会重新声明 equals() 和 hashCode() - 结果代码可能无法正常工作。

那么,什么会使生成的代码无法与生成的方法一起正常工作呢?


(顺便说一句,生成的方法如下所示:

@Override
public int hashCode() {
    final int prime = 31;
    int result = 1;
    result = prime * result + x;
    return result;
}

@Override
public boolean equals(Object obj) {
    if (this == obj)
        return true;
    if (obj == null)
        return false;
    if (getClass() != obj.getClass())
        return false;
    B other = (B) obj;
    if (x != other.x)
        return false;
    return true;
}

)

4

4 回答 4

10

重写 equals 时必须小心以遵守一组特定的规则。有关详细信息,请参阅javadoc。简而言之,两个棘手的部分是保持对称性和传递性。根据 Joshua Block 的Effective Java

“没有办法扩展一个可实例化的类并添加一个值组件,同时保留平等合同”

这是什么意思?好吧,假设您在 A 类中有一个类型为 T 的属性,而在子类 B 中有另一个类型为 V 的属性。如果两个类都覆盖等于,那么将 A 与 B 进行比较而不是 B 与 A 进行比较时,您将得到不同的结果。

A a = new A(T obj1);
B b = new B(T obj1, V obj2);
a.equals(b) //will return true, because objects a and b have the same T reference.
b.equals(a) //will return false because a is not an instanceof B

这是违反对称性的。如果您尝试通过进行混合比较来纠正此问题,您将失去传递性。

B b2 = new B(T obj1, V obj3);
b.equals(a) // will return true now, because we altered equals to do mixed comparisions
b2.equals(a) // will return true for the same reason
b.equals(b2) // will return false, because obj2 != obj3

在这种情况下 b == a, b2 ==a, b != b2,这是一个问题。

编辑

为了更准确地回答这个问题:“什么会导致生成的代码无法与生成的方法一起正常工作”,让我们考虑一下这个具体情况。父类是抽象的,不会覆盖equals。我相信我们可以得出结论,代码是安全的,没有发生违反 equals 合约的情况。这是父类是抽象的结果。它不能被实例化,因此上面的例子不适用于它。

现在考虑父类是具体的并且不覆盖equals的情况。正如 Duncan Jones 所指出的,警告消息仍然会生成,在这种情况下这样做似乎是正确的。默认情况下,所有类都从Object继承equals,并将根据对象标识(即内存地址)进行比较。如果与覆盖 equals 的子类一起使用,这可能会导致不对称比较。

A a = new A();
B b = new B(T obj1);
a.equals(b) //will return false, because the references do not point at the same object
b.equals(a) //should return false, but could return true based on implementation logic. 

如果 b.equals(a) 出于任何原因返回 true,无论是实现逻辑还是编程错误,都会导致失去对称性。编译器无法强制执行此操作,因此会生成警告。

于 2013-04-26T13:51:15.290 回答
8

我认为这个警告是错误的。我见过的每篇关于equals构造的文献(包括 Bloch 的第 8 条)都警告了父类确实实现了 equals 的情况。

鉴于您的A课程仅使用引用相等,我无法理解您的B equals方法违反任何必需原则(对称性、传递性和自反性)的情况。

请记住,任何半明智的equals方法都会进行类型检查。原始问题中的自动生成代码也是如此:

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

与其仅仅引用 Bloch 的书和其他网站的部分内容,不如认真思考如果父类没有实现 equals,是否有任何“标准”equals 问题是可能的。我会说他们不是,我欢迎一个反例。

于 2013-04-26T13:27:06.783 回答
1

如果父类和子类都equals以这样一种方式实现,除非两个引用都引用同一个确切类的实例,否则两个引用不可能被认为是相等的,那么equals.

当可能有不同类的实例但仍然应该比较相等时,就会出现有问题的情况。处理这种情况的唯一正确方法是指定任何可以相互比较相等的实例必须派生自一个公共类,并且该类的合同必须指定相等的含义。公共类equals(可能是抽象的,也可能不是抽象的)通常会定义虚拟成员,派生类可以覆盖这些虚拟成员以测试相等性的不同方面。例如,如果公共类equals方法类似于if (other==null) return false; else return this.equals2(other) && other.equals2(this);,则本质上将保证任何equals2不会改变被比较对象的实现的对称行为。

于 2013-12-13T23:26:07.960 回答
-3

出于令人信服的理由等了一段时间,我的看法。

我链接的文章(再次,它是http://www.artima.com/lejava/articles/equality.html)解释了为什么在子类中实现 equals 时会出现问题(主要是,instanceof检查会导致禁止的不对称根据equals合同)。

如果你的超类没有实现 equals,你可能会发现这种情况。

  • B 扩展 A

  • B 实现equals,让我们说像

    public boolean equals(Object obj) {
      A a = (A) obj;  <-- this cast is the most problematic issue from this example
      return this.id.equals(a.getId());
    }
    

所以你以

A a = new A("Hello");
B b = new B("Hello");
a.equals(b) != b.equals(a);

与此示例相反的一点是您的超类是抽象的,但可能 Eclipse 发出警告以防止您实例化它(或者只是警告检查不是那么细粒度)。

于 2013-04-26T14:16:13.797 回答