При переопределении равных значений в 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.

Ответ 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 в его выполнении.