Почему кажется, что он вызывает неправильный метод? - PullRequest
2 голосов
/ 23 февраля 2020

Допустим, у меня есть два класса A и B. B наследуется от A, а B имеет следующие методы:

public boolean equals(Object other) {
   System.out.print("Object");
   return true;
}
public boolean equals(A other){
   System.out.print("A object");
   return true;
} 
public boolean equals(B other) {
   System.out.print("B object");
   return true;
}
A a1 = new A();
A ab = new B();
B b1 = new B();

Мне неясно, почему

 ab.equals(a1)
 ab.equals(b1)

Возвращает Object

ab является экземпляром B с указателем A. a1 является явно экземпляром и указателем A. Так почему же он использует Object other вместо A other метода? То же самое касается b1, который является экземпляром B с pointe B, но компилятор решил применить метод equals, как я вставил обычный объект ?? Я так глуп, или этот язык бессвязен?

Кстати A вообще не имеет equals методов.

Ответы [ 2 ]

3 голосов
/ 23 февраля 2020

Пояснение

Кстати A вообще не имеет методов equals.

Но вы делаете

ab.equals(a1)
ab.equals(b1)

И ab - это:

A ab = new B();

Таким образом, хотя это на самом деле B, переменная имеет тип A.

Запустятся правила Javas для выбора методов в таких ситуациях. ищем метод equals в классе A. И на самом деле он есть, реализация по умолчанию унаследована от Object. Он имеет подпись

public boolean equals(Object other)

, поэтому он решает эту подпись. Теперь он запрашивает фактический экземпляр за ab, который имеет тип B, для метода с этой сигнатурой. Он выбирает переопределенную реализацию в B:

public boolean equals(Object other) {
   System.out.print("Object");
}

и последовательно печатает Object в обоих случаях.


Подробно

Подробно об этом определено в JLS§15.12. Выражения вызова метода . В нем говорится о поиске наиболее конкретного c соответствия . Правила могут стать довольно сложными, если вы углубитесь в них.

Некоторые выдержки:

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

На втором шаге выполняется поиск типа, определенного на предыдущем шаге, для методов-членов. На этом шаге используется имя метода и выражения аргумента для определения доступных и применимых методов, то есть объявлений, которые могут быть правильно вызваны для данных аргументов.

Может быть несколько таких методов , в этом случае выбирается наиболее конкретный c. Дескриптор (сигнатура плюс тип возврата) наиболее определенного c метода - это тот, который используется во время выполнения для выполнения отправки метода.

В классе или интерфейсе, определенных на этапе 1 времени компиляции (§15.12.1), ищутся все методы-члены, которые потенциально применимы к этому вызову метода; члены, унаследованные от суперклассов и суперинтерфейсов, включаются в этот поиск.

Если более одного метода-члена доступны и применимы для вызова метода, необходимо выбрать один, чтобы предоставить дескриптор для метода времени выполнения отправка. Язык программирования Java использует правило, согласно которому выбирается наиболее конкретный метод c.

2 голосов
/ 23 февраля 2020

Настройка

При наличии класса A и класса B extends a, переопределения и перегрузки equals(...) с различными типами параметров следующим образом:

class A {}

class B extends A {
    public boolean equals(Object other) {
        System.out.println("Object");
        return true;
    }

    public boolean equals(A other) {
        System.out.println("A object");
        return true;
    } 

    public boolean equals(B other) {
        System.out.println("B object");
        return true;
    }
}

Вопрос

Учитывая следующий код

A a1 = new A();
A ab = new B();
B b1 = new B();

ab.equals(a1);
ab.equals(b1);

Почему "Object" распечатывается для обоих вызовов?

Объяснение

Сигнатура вызываемого метода определяется через тип приемника stati c и тип параметров stati c (как описано в JLS, §15.12.3 ). Кроме того, с помощью dynamici c dispatch можно выполнить переопределение метода дочернего класса.

Если мы проанализируем тип ab (получатель вызова), мы находим это A. Существует только один метод с именем equals(...) wihtin A, который equals(Object) ( наследует от Object). параметры (a1 и b1 соответственно) присваиваются Object, поэтому этот метод вызывается.

Посредством динамической отправки c, equals(Object) в B фактически выполняется и "Object" распечатывается для обоих вызовов.

Полный пример доступен по Ideone

...