При переопределении равных значений в Java, почему он не работает, чтобы использовать параметр, отличный от Object?
Недавно я столкнулся с интересным поведением. Кажется, что если я переопределяю .equals(), чтобы принять параметр, отличный от Object, он не будет вызван. Может ли кто-нибудь объяснить мне, почему это происходит? Кажется, это нарушает мое понимание полиморфизма в ООП, но, возможно, я что-то пропустил.
Здесь гораздо более простой код, который показывает, что я вижу:
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), хотя есть и другая, которая будет работать. В отличие от этого, если я пишу равным образом, код работает так, как ожидалось:
public boolean equals(Object o) {
if(!(o instanceof MyClass))
return false;
MyClass mc = (MyClass)o;
return x == mc.x;
}
Почему не выясняется, какую версию функции вызывать на основе типа параметра?
Ответы
Ответ 1
Вы смешиваете "переопределение" и "перегрузку".
Переопределение - добавление определения замены существующего метода для целей полиморфизма. Метод должен иметь одну и ту же подпись. Подпись состоит из имен и типов аргументов. Переопределенные методы выбираются во время выполнения на основе типа среды выполнения целевого объекта.
Перегрузка - добавление метода с тем же именем, но с другой подписью. Перегруженные методы выбираются во время компиляции на основе типа времени компиляции целевого объекта.
Ответ 2
equals (Object) переопределяет супер-метод; вы не можете переопределить супер метод, не используя ту же самую подпись (ну, есть некоторые исключения, такие как ковариантные возвращаемые типы и исключение).
Ответ 3
Обратите внимание, что метод, который вы вызываете, определен в javadoc для ArrayList <E
> как
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;
}
Он использует метод equals, определенный в суперклассе Object, поскольку метод equals не переопределяется в реализации ArrayList <E
> .
Когда переопределение объекта равно в java, вы также должны переопределить метод 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.
Ответ 4
существуют различные типы http://en.wikipedia.org/wiki/Polymorphism_(computer_science). java не делает http://en.wikipedia.org/wiki/Double_dispatch.
Ответ 5
Реализация метода contains (Object) ArrayList обязана использовать метод Object.equals(Object) внутри, поэтому он никогда не узнает о вашей перегрузке метода equals (MyClass). Будет найден только переопределяющий метод (с совпадающей сигнатурой).
Ответ 6
Хорошо, позвольте мне перефразировать.
(1) Поскольку компилятор устраняет всю информацию о Generics (стирание, см. здесь) и (2), потому что вы не можете переопределить метод без той же самой подписи (равно (Object)), (3) во время выполнения все объекты внутри Списка рассматриваются как объекты, а не как экземпляры MyClass. Следовательно, метод, который вызывается, равен equals (Object), поскольку это тот, который был перезаписан вашим классом.
Ответ 7
Вы предполагаете, что метод contains()
в List
знает тип объекта во время выполнения, что неверно.
Из-за стирания List<MyClass>
становится только регулярным List
во время выполнения, поэтому метод contains()
видит свой параметр как Object
, тем самым вызывая Object equals()
вместо того, который вы определили для MyClass
в его выполнении.