Проблема псевдонимов 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.

Другие вопросы по тегам