При переопределении equals в Java, почему не работает использование параметра, отличного от Object? - PullRequest
11 голосов
/ 21 ноября 2008

Недавно я столкнулся с интересным поведением. Кажется, что если я переопределю .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;
}

Почему не выясняется, какую версию функции вызывать, основываясь на типе параметра?

Ответы [ 7 ]

24 голосов
/ 21 ноября 2008

Вы смешиваете "переопределение" и "перегрузка".

Переопределение - добавление определения замены существующего метода для целей полиморфизма. Метод должен иметь одинаковую подпись. Подпись состоит из имени и типа аргумента. Переопределенные методы выбираются во время выполнения на основе типа времени выполнения целевого объекта.

Перегрузка - добавление метода с тем же именем, но другой подписью. Перегруженные методы выбираются во время компиляции на основе типа времени компиляции целевого объекта.

12 голосов
/ 21 ноября 2008

равно (объект) переопределяет супер метод; вы можете не переопределить супер-метод, не используя точно такую ​​же сигнатуру (ну, есть некоторые исключения, такие как ковариантные повторные типы и исключения).

6 голосов
/ 21 ноября 2008

Обратите внимание, что вызываемый вами метод определен в 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, вы должны также переопределить метод 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.
3 голосов
/ 21 ноября 2008

существуют различные типы http://en.wikipedia.org/wiki/Polymorphism_(computer_science). Java не делает http://en.wikipedia.org/wiki/Double_dispatch.

2 голосов
/ 21 ноября 2008

Реализация метода ArrayList метода contains (Object) обязательно использует метод Object.equals (Object) для внутреннего использования, поэтому он никогда не узнает о вашей перегрузке метода equals (MyClass). Будет найден только переопределяющий метод (с соответствующей подписью).

0 голосов
/ 21 ноября 2008

Вы предполагаете, что метод contains() в List знает тип объекта во время выполнения, что неверно.

Из-за стирания List<MyClass> становится просто обычным List во время выполнения, поэтому метод contains() видит свой параметр как Object, вызывая, таким образом, объект equals() вместо того, который вы определили для MyClass в его исполнении.

0 голосов
/ 21 ноября 2008

Хорошо, позвольте мне перефразировать.

(1) Поскольку компилятор исключает всю информацию, касающуюся Generics (стирание, см. здесь ) и (2), поскольку вы не можете переопределить метод без точно такой же сигнатуры (равно (Object)), (3) во время выполнения все объекты в списке обрабатываются как объекты, а не как экземпляры MyClass. Следовательно, вызываемый метод равен equals (Object), поскольку он перезаписан вашим классом.

...