有 Java 编程相关的问题?

你可以在下面搜索框中键入要查询的问题!

java重写对象。等于VS重载它

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

第8项-当覆盖等于状态时,遵守总合同:

It is not uncommon for a programmer to write an equals method that looks like this, and then spend hours puzzling over why it doesn't work properly:

[Code sample here]

The problem is that this method does not override Object.equals, whose argument is of type Object, but overloads it instead.

代码示例:

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

我的问题:

为什么像这个代码示例中那样重载的强类型equals方法是不够的?这本书指出重载而不是重写是不好的,但它没有说明为什么会出现这种情况,或者什么情况会使equals方法失败


共 (3) 个答案

  1. # 1 楼答案

    因为使用equals的集合将使用Object.equals(Object)方法(可能在MyClass中被重写,因此以多态方式调用),这与MyClass.equals(MyClass)不同

    重载一个方法定义了一个新的、不同的方法,该方法恰好与另一个方法同名

  2. # 2 楼答案

    这是因为重载该方法不会改变集合或其他显式使用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;
        }
    }
    

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

    Name clash: The method equals(T) of type MyGenericClass has the same erasure as equals(Object) of type Object but does not override it

    如果你试图@Override它:

    public class MyGenericClass<T> {
    
        @Override
        public boolean equals(T t) {
            return false;
        }
    }
    

    你会得到这个:

    The method equals(T) of type MyGenericClass must override or implement a supertype method

    所以你赢不了。这里发生的事情是,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)

  3. # 3 楼答案

    Why is a strongly typed equals method that overloads like the one in this code sample not sufficient?

    因为它不会覆盖Object.equals。任何只知道Object(例如HashMap,测试密钥相等性)中声明的方法的通用代码都不会调用重载,它们只会调用提供引用相等性的原始实现

    记住,重载是在编译时确定的,而重写是在执行时确定的

    如果要重写^{,通常最好提供一个强类型版本以及,并从^{中声明的方法委托给它

    下面是一个完整的例子,说明它是如何出错的:

    import java.util.*;
    
    final class BadKey {    
        private final String name;
    
        public BadKey(String name) {
            // TODO: Non-nullity validation
            this.name = name;
        }
    
        @Override
        public int hashCode() {
            return name.hashCode();
        }
    
        public boolean equals(BadKey other) {
            return other != null && other.name.equals(name);
        }
    }
    
    public class Test {
    
        public static void main(String[] args) throws Exception {
            BadKey key1 = new BadKey("foo");
            BadKey key2 = new BadKey("foo");
            System.out.println(key1.equals(key2)); // true
    
            Map<BadKey, String> map = new HashMap<BadKey, String>();
            map.put(key1, "bar");
            System.out.println(map.get(key2)); // null
        }
    }
    

    修复方法只是添加一个覆盖,如下所示:

    @Override
    public boolean equals(Object other) {
        // Delegate to the more strongly-typed implementation
        // where appropriate.
        return other instanceof BadKey && equals((BadKey) other);
    }