Почему при переопределении равенства в Java не получается использовать параметр, отличный от Object?

StackOverflow https://stackoverflow.com/questions/309892

  •  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, вызывая таким образом объект <=> вместо того, который вы определили для <= > в его исполнении.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top