Перед тем, как вставить параметры, B2.m1()
отменяют A2.m1()
, поэтому метод называется зависит только от типа исполнения приемника.
После того, как вы вставили параметры, два метода m1
имеют разные типы подписей, а B2.m1(B2 b)
больше не переопределяет A2.m1(A2 a)
. При этом вызванный метод зависит от типов времени компиляции как приемника, так и отправляемого параметра.
Дано:
A2 x = new A2();
A2 y = new B2();
B2 z = new B2();
y.m1(x); // calls A2.m1(A2 a)
y.m1(z); // calls A2.m1(A2 a)
z.m1(x); // calls A2.m1(A2 a)
z.m1(z); // calls B2.m1(B2 b)
Перед тем, как добавлен параметр:
y.m1(); // would have called B2.m1()
является то, что несоответствие вы видите?
EDIT: Детали слишком подробно добавлены ниже.
Прежде всего вы можете думать о подписи как в основном имя метода в сочетании с номером и типами формальных параметров. Вы можете увидеть их в виде «m1: (LA2;) V» или «m1: (LB2;) V» (метод с именем «m1», который принимает 1 параметр типа класса A2
и возвращает Void). Но здесь я просто напишу m1(A2)
или m1(B2)
, чтобы это стало проще.
В основном два шага для определения того, какой метод окончательно вызван. Шаг времени компиляции и шаг выполнения. Шаг времени компиляции определяет подпись метода, который должен быть вызван. Шаг выполнения выбирает фактический метод для вызова путем поиска того, что с этой сигнатурой является классом объекта по мере его создания. Если он не найдет его там, он перемещается вверх по иерархии классов до тех пор, пока не будет найден метод с этой сигнатурой (там должно быть где-то или оно не было бы скомпилировано).
Чтобы найти подпись, используются типы времени компиляции (также называемые «статическими типами») (для объявления переменной используются те, которые были указаны ).Это касается как приемника, так и параметров.
Итак, давайте посмотрим на подписи, выбранной в каждом случае:
y.m1(x)
у объявляются быть A2
поэтому единственно возможная подпись m1(A2)
.
y.m1(z)
Опять y
является A2
поэтому единственная возможность m1(A2)
. Тот факт, что z
является B2
, прекрасен с , вы всегда можете пройти B2
, где ожидается A2
. (На данный момент в этом процессе, тот факт, что время выполнения y
будет создан как B2
не имеет никакого значения!)
z.m1(x)
z
является B2
. В B2
есть две возможности. m1(B2)
и унаследованный m1(A2)
. Поскольку x
- A2
, первое невозможно (вы не можете пройти A2
, где ожидается B2
), поэтому один раз снова мы остаемся с m1(A2)
.
z.m1(z)
z
является B2
. В B2
есть две возможности. m1(B2)
и унаследованный m1(A2)
. С z
- B2
, возможны оба варианта. Здесь у Java есть правило для выбора, которое должно взять одно с самым конкретным (наиболее производным) аргументом типа , поэтому выбирается m1(B2)
.
Во всех вышеперечисленных случаях больше нечего решать во время выполнения, так как в каждом случае есть только один метод с данной сигнатурой. Возьмите случай y.m1(z)
. Во время выполнения Java будет искать метод с сигнатурой m1(A2)
, как определено на этапе компиляции.
Сначала он будет выглядеть в классе B2
с y
был создан как объект B2
. Но здесь нет метода с этой подписью (есть только m1(B2)
). Не имеет значения, что z
является объектом B2
, как только подпись определяется на этапе компиляции, только метод с этой сигнатурой будет называться во время выполнения . Итак, Java поднимается по иерархии и находит метод с правильной подписью в классе A2
, и, следовательно, результаты - это то, что вы видите. (Обратите внимание, что это концептуально, а фактический код, созданный для его правильного метода, является более умным.)
Теперь, прежде чем вы добавили параметр, оба метода m1 были названы «m1» и не имели параметры, следовательно, у них была та же подпись m1()
.Так, глядя на
y.m1()
Compile времени шаг: y
является A2
поэтому единственная возможность выбирать является m1().
Затем во время выполнения, так как y
был создан как B2
, Java начинается в классе B2
ищет метод с этой подписью. Он находит один! Метод m1()
в B2
имеет ту же подпись, что и метод m1()
в A2
. Другими словами, в отличие от вышеприведенных случаев, B2
.m1 действительно отменяет A2.m1
. Так что java счастлив назвать версию m1()
в B2
, как вы видели перед добавлением параметров.
Так что это история. Я знаю, что это сбивает с толку, но это действительно ключ к пониманию Java (и других объектов ориентированных языков) и замечательно, что вы копаете в этом.
Прочитайте все, что вы можете о переопределении и перегрузке, полиморфизме, динамической диспетчеризации и виртуальных таблицах методов .
Я думаю, что наиболее важные моменты:
- Только если вы переопределить метод вы получаете динамическую диспетчеризацию на основе типа во время выполнения приемника
- метод в производный класс только переопределяет метод в базовом классе, если подпись совпадает с
Я предполагаю, что это не последняя версия 'D2', или она не будет компилироваться. Какой аргумент вы перешли на 'm1'? – jas