Java 当超类不重新声明equals()和hashCode()时会出现什么问题?

Java 当超类不重新声明equals()和hashCode()时会出现什么问题?,java,Java,假设有两个这样的类: 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 */ } @Override public int hashCode() { final i

假设有两个这样的类:

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 */
}
@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;
}
然后我使用Eclipse的“源代码”在类
B
上生成
equals()
hashCode()
方法→ 生成hashCode()和equals()…”。但Eclipse警告我:

超类“com.example.test2.A”未重新声明equals()和hashCode()-生成的代码可能无法正常工作

那么,什么会使生成的代码不能与生成的方法正常工作?


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

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 */
}
@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;
}

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

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

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

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

我们不应该只是引用Bloch的书和其他网站的大量内容,而应该仔细考虑如果父类不实现equals,是否可能出现任何“标准”equals问题。我想说他们不是,我欢迎一个反例。

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

“在保留equals契约的同时,无法扩展可实例化类并添加值组件”

这是什么意思?假设在A类中有一个T类型的属性,在B子类中有另一个V类型的属性。如果两个类都覆盖了equals,那么在比较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的子类一起使用,这可能会导致不对称比较

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,无论是实现逻辑还是编程错误,都将导致对称性丢失。编译器无法强制执行此操作,因此会生成警告

为了一个令人信服的理由等了一段时间后,我的回答是

我链接的文章(同样是)解释了为什么在子类中实现equals时会出现问题(大多数情况下,
instanceof
检查导致了
equals
契约禁止的异常)

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

  • B扩展A

  • B实现
    等于
    ,比如说like

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

    与本例相反的一点是,您的超类是抽象的,但Eclipse可能会发出警告,要求您实例化它(或者只是警告检查的粒度不够细)


    当可能有不同类的实例,但却被认为比较相等时,就会出现问题的情况。处理这种情况的唯一正确方法是指定任何可能相互比较相等的实例必须派生自一个公共类,该类的契约必须指定相等的含义。公共类
    equals
    (可能是抽象的,也可能不是抽象的)通常会定义虚拟成员,派生类可以覆盖这些虚拟成员来测试相等性的不同方面。例如,如果公共类
    equals
    方法类似于
    If(other==null)返回false;否则返回此.equals2(其他)和&other.equals2(本)
    ,这将基本上保证
    equals2
    的任何实现的对称行为,不会改变被比较的对象。

    这可能会引起兴趣。也就是说,我不确定它是如何应用的,因为A是抽象的。大多数情况下,它试图阻止
    b.equals(a)
    但是
    !a、 equals(b)
    我认为你的问题受到了影响,因为有太多的文献解释了一个非常相似但又截然不同的场景。几乎下面的每个答案都提到了超级类有一个
    equals
    方法,而在您的示例中显然没有。我甚至不是问问题的人,我在这里感到沮丧@KennyTM是移除抽象修饰符后仍然生成的警告吗?@monkybonk05是的。检查此链接。基本上,子类可以确保superInstance.equals(子实例)是