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