11

我最近遇到了一个有趣的行为。似乎如果我重写 .equals() 以获取 Object 以外的参数,它就不会被调用。谁能向我解释为什么会这样?这似乎违反了我对 OOP 中多态性的理解,但也许我遗漏了一些东西。

这是更简单的代码,显示了我所看到的:

public class MyClass {
  private int x;
  public MyClass(int n) { x = n; }
  public boolean equals(Object o) { return false; }
  public boolean equals(MyClass mc) { return x == mc.x; }
  public static void main(String[] args) {
    List<MyClass> list = new ArrayList<MyClass>();
    list.add(new MyClass(3));
    System.out.println("Contains 3? " + list.contains(new MyClass(3)));
  }
}

当它运行时,它会打印“ Contains 3? false”。看起来好像调用了 equals(Object) 函数,即使还有另一个可以工作。相比之下,如果我这样写 equals ,则代码按预期工作:

public boolean equals(Object o) {
  if(!(o instanceof MyClass))
    return false;
  MyClass mc = (MyClass)o;
  return x == mc.x;
}

为什么不根据参数的类型确定要调用哪个版本的函数?

4

7 回答 7

24

您正在混淆“覆盖”和“重载”。

覆盖——为多态性添加现有方法的替换定义。该方法必须具有相同的签名。签名由名称和参数类型组成。在运行时根据目标对象的运行时类型选择覆盖的方法。

重载——添加一个名称相同但签名不同的方法。在编译时根据目标对象的编译时类型选择重载方法。

于 2008-11-21T19:41:23.883 回答
12

equals(Object) 覆盖了一个超级方法;如果不使用完全相同的签名,您就无法覆盖超级方法(嗯,有一些例外,如协变返回类型和异常)。

于 2008-11-21T19:35:57.013 回答
6

请注意,您正在调用的方法在 ArrayList <E> 的 javadoc 中定义为

boolean contains(Object o)
    Returns true if this list contains the specified element. 

代替

boolean contains(E o)
    Returns true if this list contains the specified element. 

ArrayList.java的实现:

private transient Object elementData[];

public boolean contains(Object elem) {
    return indexOf(elem) >= 0;
}

public int indexOf(Object elem) {
    if (elem == null) {
        for (int i = 0; i < size; i++)
            if (elementData[i]==null)
                return i;
    } else {
        for (int i = 0; i < size; i++)
            if (elem.equals(elementData[i]))
                return i;
    }
    return -1;
}

它使用 Object 超类中定义的 equals 方法,因为在 ArrayList <E> 的实现中未覆盖 equals 方法。

在 java 中重写 Object equals 时,您也应该重写 Object hashCode 方法。

无论如何,您可能想尝试以下代码:

class A{    
    public int content;    
    A(){
        this(0);
    }    
    A(int value){
        content = value;
    }   
    public boolean equals(Object obj){
        System.out.println("overriding equals method");
        return this.content == ((A) obj).content;
    }    
    public boolean equals(A a){
        System.out.println("overloading equals method");
        return this.content == a.content;
    }    
    public static void main(String[] args){
        A x = new A(1);
        A y = new A(2);
        Object z  = new A(1);
        System.out.println(x.equals(y));
        System.out.println(x.equals(x));
        System.out.println(x.equals(z));
        //override as z is declared as Object at compile time
        //so it will use methods in class Object instead of class A
        System.out.println(x.equals((Object) y));
        System.out.println(x.equals((Object) x));        
    }   
}
//rant: they didn't teach me these in javaschool and I had to learn it the hard way.
于 2008-11-21T20:55:25.043 回答
3

有不同类型的http://en.wikipedia.org/wiki/Polymorphism_(computer_science)。java 不做http://en.wikipedia.org/wiki/Double_dispatch

于 2008-11-21T19:44:06.123 回答
2

contains(Object) 方法的 ArrayList 实现必然会在内部使用 Object.equals(Object) 方法,因此它永远不会知道您重载了 equals(MyClass) 方法。只会找到一个覆盖方法(具有匹配的签名)。

于 2008-11-21T19:47:32.203 回答
-1

好的,让我重新措辞。

(1)因为编译器消除了与泛型有关的所有信息(擦除,请参见此处),以及(2)因为您无法覆盖没有完全相同签名(equals(Object))的方法,(3)在运行时所有对象在List 被视为对象,而不是 MyClass 的实例。因此,被调用的方法是 equals(Object),因为这是被您的类覆盖的方法。

于 2008-11-21T19:39:26.883 回答
-1

您假设 in 中的contains()方法在List运行时知道对象的类型,这是不正确的。

由于擦除,在运行时List<MyClass>只是一个常规List,因此该contains()方法将其参数视为一个Object,因此调用对象而不是您在其执行中equals()定义的对象。MyClass

于 2008-11-21T19:53:26.417 回答