Есть два шага при выборе метода для вызова.Один шаг (какая перегрузка?) Происходит во время компиляции, другой шаг (какая реализация?) Происходит во время выполнения.
C.f(B b)
закомментированный случай
Компилятор выполнитпервый шаг, решив, какую перегрузку вызвать.Существует только одна перегрузка c.f
:
void f(A a) { System.out.println("C.f(A)"); }
, поэтому компилятор выбирает это.this
имеет тип времени компиляции B
, поэтому его можно неявно преобразовать в A
.
Почему компилятор не видит метод f(B b)
в D
?Поскольку компилятор не знает, что c
является на самом деле экземпляром D
, он будет выглядеть только в классе C
, поскольку c
имеет тип времени компиляции C
.
Во время выполнения обнаружено, что существует две реализации f(A a)
- одна в классе C
и одна в классе D
.Поскольку среда выполнения знает, что c
является экземпляром D
, будет вызван D.f(A a)
.
C.f(B b)
без комментариев
На этот раз компилятор обнаружит две перегрузки c.f
для вызова:
void f (A a) {System.out.println ("Cf (A)");} void f (B b) {System.out.println ("Cf (B)");}
Компилятор выберет метод с наиболее специфическими параметрами.«Наиболее конкретный» означает, что тип является самым дальним из дерева наследования.Это означает, что будет выбран f(B b)
.
Во время выполнения реализация в D
будет вызвана по той же причине, что и в первом случае.