一尘不染

覆盖Object.equals VS重载它

java

阅读:Joshua Bloch撰写的有效Java-第二版

第8项-在覆盖等于时遵守总合同:

程序员编写看起来像这样的equals方法,然后花很多时间迷惑它为什么不能正常工作的情况并不少见:

[代码示例在这里]

问题在于此方法不会覆盖Object.equals,后者的参数类型为Object,而是将其重载。

代码样例:

public boolean equals(MyClass o) {
    //...
}

我的问题:

为什么像此代码示例中那样重载的强类型equals方法不够用?该书指出重载而不是覆盖是不好的,但是没有说明为什么会这样,或者什么情况会使此equals方法失败。


阅读 248

收藏
2020-09-08

共1个答案

一尘不染

这是因为重载该方法不会更改在集合或其他equals(Object)明确使用该方法的位置中的行为。例如,使用以下代码:

public class MyClass {

    public boolean equals(MyClass m) {
        return true;
    }
}

如果您将其放在类似HashSet

public static void main(String[] args) {
    Set<MyClass> myClasses = new HashSet<>();
    myClasses.add(new MyClass());
    myClasses.add(new MyClass());
    System.out.println(myClasses.size());
}

即使您希望所有实例在过载时都相等,并且不会设置第二个实例2,这将打印出,而不是。1``MyClass

所以基本上,即使这是true

MyClass myClass = new MyClass();
new MyClass().equals(myClass);

这是false

Object o = new MyClass();
new MyClass().equals(o);

后者是集合和其他类用于确定相等性的版本。实际上,此参数将返回的 唯一 地方true是该参数显式MyClass为其子类型的实例或其子类型之一。


编辑: 根据您的问题:

覆盖与重载

让我们从覆盖和重载之间的区别开始。通过覆盖,您实际上可以 重新定义 该方法。您删除它的原始实现,然后用您自己的替换。因此,当您这样做时:

@Override
public boolean equals(Object o) { ... }

您实际上是在重新链接新的equals实现,以替换中的实现Object(或最后定义它的任何超类)。

另一方面,当您这样做时:

public boolean equals(MyClass m) { ... }

您正在定义一个全新的方法,因为您正在定义一个名称相同但参数不同的方法。当HashSet来电equals时,它调用它的类型的变量Object

Object k;
if (e.hash == hash && ((k = e.key) == key || key.equals(k))) {

(该代码来自的源代码HashMap.put,用作的基础实现HashSet.add。)

需要明确的是,它会使用不同的唯一时间equals是当一个equals方法被 覆盖
,不会过载。如果尝试添加@Override到重载的equals方法中,它将因编译器错误而失败,并抱怨它没有覆盖方法。我什至可以equals在同一个类中声明两个方法,因为它很重载:

public class MyClass {

    @Override
    public boolean equals(Object o) {
        return false;
    }

    public boolean equals(MyClass m) {
        return true;
    }
}

泛型

至于仿制药,equals 通用的。它明确地采用Object其类型,因此这一点没有意义。现在,假设您尝试执行此操作:

public class MyGenericClass<T> {

    public boolean equals(T t) {
        return false;
    }
}

这不会与以下消息一起编译:

名称冲突:MyGenericClass类型的equals(T)方法具有与Object类型的equals(Object)相同的擦除,但不会覆盖它

如果您尝试这样@Override做:

public class MyGenericClass<T> {

    @Override
    public boolean equals(T t) {
        return false;
    }
}

您将得到以下信息:

类型MyGenericClass的equals(T)方法必须重写或实现一个超类型方法

所以你赢不了。这里发生的是Java使用擦除实现泛型。Java在编译时完成对所有通用类型的检查后,实际的运行时对象都将替换为Object。在您看到的所有地方T,实际的字节码都包含Object在内。这就是为什么反射不适用于泛型类以及为什么您不能做类似的事情的原因list instanceof List<String>

这也使其无法泛型类型重载。如果您有此类:

public class Example<T> {
    public void add(Object o) { ... }
    public void add(T t) { ... }
}

您会从add(T)方法中得到编译器错误,因为当类实际完成编译时,这两个方法将具有相同的签名public void add(Object)

2020-09-08