在Java中重写equals时,为什么使用Object以外的参数不起作用?
我最近遇到了一个有趣的行为。 似乎如果我重写.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 list = new ArrayList(); 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; }
为什么不根据参数的类型确定要调用哪个版本的函数?
你混淆了“覆盖”和“超载”。
覆盖 – 为了多态而添加现有方法的替换定义。 该方法必须具有相同的签名。 签名由名称和参数类型组成。 根据目标对象的运行时类型在运行时选择重写的方法。
重载 – 添加具有相同名称但具有不同签名的方法。 在编译时根据目标对象的编译时类型选择重载方法。
equals(Object)覆盖了一个超级方法; 你不能在不使用完全相同的签名的情况下覆盖超级方法(好吧,有一些例外,如协变返回类型和exception)。
请注意,您调用的方法是在ArrayList
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
在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.
有不同类型的http://en.wikipedia.org/wiki/Polymorphism_(computer_science) 。 java不做http://en.wikipedia.org/wiki/Double_dispatch 。
contains(Object)方法的ArrayList实现必须在内部使用Object.equals(Object)方法,因此它永远不会知道你的equals(MyClass)方法的重载。 只会找到一个重写方法(带有匹配的签名)。
好吧让我重新说一句。
(1)因为编译器消除了有关generics的所有信息(擦除,请参见此处 ),以及(2)因为你不能覆盖没有完全相同签名的方法(equals(Object)),(3)在运行时所有对象内部列表被视为对象而不是MyClass的实例。 因此,被调用的方法是equals(Object),因为这是您的类被覆盖的方法。
您假设List
中的contains()
方法在运行时知道对象的类型,这是不正确的。
由于擦除, List
在运行时变为常规List
,因此contains()
方法将其参数视为Object
,从而调用Object的equals()
而不是您在执行时为MyClass
定义的那个。