Проблема псевдонимов Java полиморфизм
Если есть 3 класса. A, B и C. класс B расширяет A, а класс C расширяет B.
класс A имеет метод equals:
public boolean equals(A other)
{...}
класс B имеет метод equals:
public boolean equals(B other)
{...}
и класс C имеет метод euals:
public boolean equals(Object other)
{...}
И основной имеет эти строки кода:
A a = new A();
C c = new C();
a=c;
System.out.println(a.equals(c));
Я не могу понять, почему метод equals класса A выполняется.
Я знаю, что перегруженные методы связаны статическим связыванием. Но после псевдонима указывается на "C-часть объекта", и есть метод, равный классу C. Почему не будет выполняться метод equals класса C?
3 ответа
Метод в подклассе переопределяет метод в суперклассе, только если параметры имеют одинаковые типы.
Object
класс определяет equals()
метод:
class Object {
public boolean equals(Object obj) {...}
}
Когда вы определяете класс A
наследует equals
рутина из Object
, Вы определяете новый equals
, но тип параметра отличается, поэтому он не переопределяет Object
; вместо этого это становится перегрузкой. Результатом является то, что A
имеет два перегруженных equals
методы:
class A {
public boolean equals(Object obj) {...} // inherited
public boolean equals(A other) {...} // the one you wrote
}
Точно так же equals
в B
не будет переопределять либо equals
итак три перегруженных результата equals
методы:
class B {
public boolean equals(Object obj) {...} // inherited from Object
public boolean equals(A other) {...} // inherited from A
public boolean equals(B other) {...} // doesn't override anything
}
В классе C
, новый equals
метод переопределяет один в Object
так что осталось еще три equals
методы:
class C {
public boolean equals(Object other) {...} // overrides the one in Object
public boolean equals(A other) {...} // inherited from A
public boolean equals(B other) {...} // inherited from B
}
Теперь вот ваш код:
A a = new A();
C c = new C();
a=c;
System.out.println(a.equals(c));
Когда ты сказал a.equals(c)
компилятор видит что a
имеет тип A
, Поэтому он смотрит на методы в A
чтобы увидеть, какой из них выполнить. (Компилятор не знает, что a
будет иметь тип C
во время выполнения; поэтому он не будет смотреть на методы в C
.)
Есть два метода на выбор, как показано выше:
public boolean equals(Object obj) {...} // inherited
public boolean equals(A other) {...} // the one you wrote
Оба они могут быть использованы по параметру c
, поскольку c
является Object
и это A
, В том случае, когда один параметр является подклассом другого, компилятор по сути выбирает "ближайший". C
только два подкласса от A
и это три подкласса от Object
поэтому выбирает перегрузку с параметром A
, который вы определили в A
, И обратите внимание, что это equals
метод никогда не был отменен. Таким образом, он выполняет код, который вы написали в классе A
,
Но предположим, что вы написали:
System.out.println(a.equals((Object)c));
Кастинг c
для Object
вы заставляете компилятор рассматривать его как Object
, Теперь, когда он выбирает между перегрузками, он должен выбрать тот, с Object
параметр, потому что Object
не может быть автоматически преобразован в A
(потому что не каждый Object
является A
). Таким образом, он выбрал бы унаследованный метод. И так как во время выполнения объект на самом деле имеет класс C
и с класса C
имеет equals
метод, который переопределяет один в Object
в этом случае он будет выполнять код, написанный в классе C
,
Ваш код является хорошим примером для демонстрации того, как работают перегрузки и переопределения. В реальной жизни, однако, плохая идея написать equals()
метод, чей параметр является чем-то иным, чем Object
потому что это не переопределит, и это может привести к путанице. Кроме того, это хорошая практика, чтобы положить @Override
любой метод, который, по вашему мнению, переопределит метод в суперклассе. Таким образом, если вы пошлете и используете неправильные параметры, компилятор поймает их, прежде чем вы получите ошибку во время выполнения, которую может быть очень трудно отследить.
Короткий ответ
Ваш метод в дочернем классе переопределяется только тогда, когда он имеет ту же сигнатуру метода, что и родительский класс. В противном случае это не так.
Рассмотрим эти два примера
Пример 1
class A {
public boolean equals(A Other) {
System.out.println("A's method");
return true;
}
}
class C extends A {
public boolean equals(Object Other) {
System.out.println("C's method");
return true;
}
}
public class Driver {
public static void main(String[] args) {
A a = new C();
a.equals(null);
}
}
Выход 1
A's method
Пример 2
class A {
public boolean equals(A Other) {
System.out.println("A's method");
return true;
}
}
class C extends A {
public boolean equals(A Other) {
System.out.println("C's method");
return true;
}
}
public class Driver {
public static void main(String[] args) {
A a = new C();
a.equals(null);
}
}
Выход 2
C's method
В примере № 1 вы изменили сигнатуру метода, когда equals()
Метод определен в дочернем классе и поэтому не подходит для переопределения метода. И поэтому при вызове этого метода из класса Driver вызывается родительский метод.
тогда как в примере № 2 сигнатура метода в родительском и дочернем элементах абсолютно одинакова, поэтому здесь происходит переопределение метода, и, таким образом, вы получаете результат, который вы ожидали в своем вопросе.
Проще говоря, Java обрабатывает псевдонимы во время выполнения. поскольку перегруженный метод использует статическое связывание (время компиляции), он вызывает метод equals А. если вы переопределите метод equals в классе A.
@Override
public boolean equals(Object o){ ...}
это будет переопределенный метод, который использует динамическое связывание (время выполнения) и будет вызван метод equals C.