Возьмите следующий код:
public class Parent
{
public String doIt(Object o) {
return "parent";
}
}
public class Child extends Parent
{
public String doIt(Object s) {
return super.doIt(s) + ": " + "child";
}
}
public class Poly
{
public String makeItHappen() {
Parent p = new Child();
return p.doIt("test");
}
}
Из-за того, что Child.doIt () переопределяет Parent.doIt (), вызов Poly.makeItHappen () приводит к тому, что это выводится на консоль:
parent: child
Однако, если я сделаю изменение в Child.doIt (), чтобы оно выглядело так:
public String doIt(String s) {
return super.doIt(s) + ": " + "child";
}
Теперь Child.doIt () не переопределяет Parent.doIt (). Когда вы вызываете Poly.makeItHappen (), вы получаете такой результат:
parent
Я немного озадачен этим. Тип p во время компиляции - Parent, поэтому я определенно понимаю, что Parent.doIt () является потенциально применимым методом, но, учитывая, что тип p во время выполнения - Child, я не уверен, почему Child.doIt ( ) не является. Предполагая, что они оба определены как потенциально применимые методы, я ожидаю, что Child.doIt (String) будет вызываться через Parent.doIt (Object), поскольку он более конкретен.
Я попытался обратиться к JLS и нашел этот бит:
15.12.1 Время компиляции Шаг 1: Определить класс или интерфейс для поиска
...
Во всех других случаях квалифицированное имя имеет форму FieldName. Идентификатор; тогда именем метода является Идентификатор, а класс или интерфейс для поиска - это объявленный тип T поля, названного FieldName, если T является классом или типом интерфейса, или верхняя граница T, если T является переменной типа ,
Для меня это говорит о том, что во время компиляции будет использоваться тип p. Это имеет некоторый смысл, когда я вижу, что Parent.doIt (Object) вызывается, а Child.doIt (String) - нет. Но это не имеет смысла с точки зрения полиморфного поведения, отмеченного, когда Child.doIt () корректно переопределяет Parent.doIt () - в этом случае оба метода Parent и Child анализируются, чтобы найти потенциально применимые методы, так почему бы и нет во втором же случае?
Я знаю, что я что-то здесь упускаю, но я просто не могу понять, почему я вижу поведение, которым я являюсь. Если бы кто-нибудь мог пролить свет на это, я был бы признателен.
РЕДАКТИРОВАТЬ: НАЙТИ ОТВЕТ:
Благодаря ответу Джека я смог найти ответ в JLS. Раздел JLS, на который я ссылался ранее, был фактически сосредоточен на поиске правильного метода во время компиляции и не охватывал процесс вызова правильного метода во время выполнения. Эта часть процесса может быть найдена в разделе JLS под названием 15.12.4. Оценка вызова метода во время выполнения .
.
Там я нашел этот бит текста:
В противном случае режим вызова является интерфейсным, виртуальным или супер, и может произойти переопределение. Динамический метод поиска используется. Процесс динамического поиска начинается с класса S, определенного следующим образом:
Мой режим вызова виртуальный, поэтому применимо приведенное выше утверждение ...
Если режим вызова является интерфейсным или виртуальным, то изначально S является фактическим классом времени выполнения R целевого объекта.
...
Динамический поиск метода использует следующую процедуру для поиска класса S, а затем, при необходимости, суперклассы класса S для метода m.
Хорошо, мне это показалось очень странным. В соответствии с этим JVM начнет искать подходящий метод для вызова в Child, что заставит меня поверить, что Child.doIt (String) будет вызван. Но, читая на ...
Пусть X будет типом времени компиляции целевой ссылки вызова метода.
...
Если класс S содержит объявление для неабстрактного метода с именем m с тем же дескриптором (тем же числом параметров, теми же типами параметров и тем же типом возврата), который требуется для вызова метода, определенного во время компиляции (§ 15.12.3), затем:
Класс «S», который был бы Child, действительно содержит метод с тем же дескриптором, что и вызов метода, определенный во время компиляции (в конце концов, String - это «Объект», поэтому дескрипторы идентичны).Кажется, все еще похоже на то, что Child.doIt (String) должен вызываться, но читается на ...
Если режим вызова виртуальный, и объявление в S переопределяет (§8.4.8.1) Xm , тогда метод, объявленный в S, является вызываемым методом, и процедура завершается.
...
В противном случае, если S имеет суперкласс, эта же процедура поиска выполняется рекурсивно с использованием прямого суперкласса S вместо S;метод, который должен быть вызван, является результатом рекурсивного вызова этой процедуры поиска.
Жирный шрифт является действительно важной частью этого.Как я уже говорил, когда я изменил метод Child.doIt (), он больше не переопределял метод doIt () из Parent.Таким образом, даже несмотря на то, что JVM оценивает метод Child.doIt () в качестве потенциального кандидата для вызова, его не удается вызвать, поскольку он не переопределяет метод, определенный в X, который является Parent.Я действительно зависал, потому что думал, что JVM даже не проверяет Child.doIt как потенциально применимый метод, и это не кажется правильным.Теперь я считаю, что JVM проверяет этот метод как потенциально применимый метод, но затем игнорирует его, потому что он не переопределяет родительский метод должным образом.Это ситуация, в которой я думал, что метод из подкласса будет вызван не потому, что он переопределяет метод родительского класса, а потому, что он наиболее специфичен.Однако в этом случае это не так.
Следующая строка в JLS просто объясняет, что эта процедура выполняется рекурсивно над суперклассами, приводя к вызову Parent.doIt (Object).
Интуитивно, это имело для меня полный смысл, но я просто не мог понять, как на самом деле JVM выполняет этот процесс.Конечно, поиск правильной части JLS очень помог бы.