Непонятно, на чем основан ваш вопрос.
Если у вас есть код формы
Dog d = new Dog();
d.eat();
статический тип d
является Dog
и, следовательно, компилятор будет кодировать вызов Dog.eat()
в файл класса, после проверки, что вызов является правильным.
Для вызова, существует несколько сценариев возможных
Dog
может объявить метод eat()
, который переопределяет метод с той же сигнатурой в суперкласса Animal
, как в вашем примере
Dog
может объявить метод eat()
, который не переопределяет другой метод
Dog
может не объявлять метод сопоставления, но наследовать метод сопоставления из его суперкласса или имплементации nted интерфейсы
Обратите внимание, что это совершенно не имеет значения, какой сценарий применяется. Если вызов действителен, он будет скомпилирован с вызовом Dog.eat()
, независимо от того, какой случай применяется, потому что формальный статический тип d
, на который вызывается eat()
, составляет Dog
.
Будучи агностиком к реальному сценарию, также подразумевается, что во время выполнения у вас может быть другая версия класса Dog
, к которой применяется другой сценарий, без нарушения совместимости.
Было бы другая картина, если бы вы написали
Animal a = new Dog();
a.eat();
Теперь формальный тип a
является Animal
и компилятор проверит Animal
содержит ли заявление для eat()
, будь то переопределении в Dog
или нет. Этот вызов будет затем закодирован как таргетинг Animal.eat()
в байтовом коде, хотя компилятор мог бы вывести, что a
фактически является ссылкой на экземпляр Dog
. Компилятор просто следует формальным правилам. Это означает, что этот код не будет работать, если для версии времени Animal
не было метода eat()
, даже если у Dog
есть один.
Это означает, что это было бы опасно изменение, чтобы удалить метод в базовом классе, но вы всегда можете реорганизовать свой код, добавив более абстрактный базовый класс и переместить методы вверх по иерархии классов, не влияя на совместимость с существующим кодом. Это была одна из целей разработчиков Java.
Так что, возможно, вы собрали один из два выше примера, а затем, вы запускаете свой код с более новой версией библиотеки, в которой иерархия типов Animal
>Carnivore
>Dog
и Dog
не имеет реализацию eat()
, потому что естественным местом для наиболее конкретной реализации является Carnivore.eat()
. В этой среде ваш старый код все равно будет работать и делать все правильно, без проблем.
Обратите внимание, что даже если вы перекомпилируете свой старый код без изменений, но используя новую библиотеку, он останется совместимым со старой версией библиотеки, как в вашем коде, вы никогда не будете ссылаться на новый тип Carnivore
, и компилятор будет используйте формальные типы, которые вы используете в своем коде, Animal
или Dog
, не записывая тот факт, что Dog
наследует метод eat()
от Carnivore
в скомпилированный код в соответствии с формальными правилами, как описано выше. Здесь нет сюрпризов.
Он не знает, потому что ему все равно. Точка полиморфизма заключается в том, что разработчику не нужно знать, что такое реализация, на самом деле, 'javac' почти не оптимизирует, он просто проверяет ваш код. –