2016-11-04 11 views
0

Я пытаюсь понять динамическое/статическое привязку на более глубоком рычаге, и я могу сказать, что после многого чтения и поиска я действительно запутался в чем-то.Динамическое связывание Java: почему компилятор не может отличить методы переопределения

Ну, java использует динамическое связывание для переопределенных методов, и причина этого в том, что компилятор не знает, к какому классу принадлежит метод, не так ли? Например:

public class Animal{ 
     void eat(){ 
} 

class Dog extends Animal{ 
     @Override 
     void eat(){} 
} 

public static void main(String[] args[]){ 
    Dog d = new Dog(); 
    d.eat(); 
} 

Мой вопрос, почему компилятор не знает, что код относится к классу Dog Eat() методу, несмотря на то, d ссылка объявляется класс Dog и конструктор Собачьего используется создать экземпляр во время выполнения? Объект будет создан во время выполнения, но почему компилятор не понимает, что код относится к методу Dog? Это вопрос дизайна компилятора или я что-то упускаю?

+1

Он не знает, потому что ему все равно. Точка полиморфизма заключается в том, что разработчику не нужно знать, что такое реализация, на самом деле, 'javac' почти не оптимизирует, он просто проверяет ваш код. –

ответ

3

и причина этого в том, что компилятор не знает, к какому классу принадлежит метод, не так ли?

Собственно, нет. Компилятор не хочет знать конкретный тип целевого объекта. Это позволяет теперь скомпилировать код для работы в будущем с классами, которые еще не существуют.

В качестве наиболее очевидного примера рассмотрим метод JDK, например Collections.sort(List). Вы можете передать ему реализацию List, которую вы только что создали. Вы не хотите уведомлять Oracle о том, что вы это сделали, и надеемся, что они включили его в список «статически поддерживаемых» типов списков.

+0

Спасибо за ваше время. Что вы на самом деле подразумеваете под «статически поддерживаемой»? – Nikos

+0

Предполагая динамическую отправку, компилятор должен знать о вашем типе. Это означает, что Oracle должен будет знать об этом во время компиляции. Это то, что я имел в виду. –

+0

Но, если мы предположим, что передаю свой собственный объект реализации List, я должен импортировать исходный код, поэтому снова компилятор будет знать даже с новыми классами, не так ли? – Nikos

2

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

Animal a; 
String kind = askTheUser(); 
if (kind.equals("Dog") { 
    a = new Dog(); 
} 
else { 
    a = new Cat(); 
} 
a.eat(); 

Очевидно здесь, компилятор не может знать, во время компиляции, что a является собака. Это может быть кошка. Поэтому он должен использовать динамическое связывание.

Теперь вы можете сказать, что в вашем примере компилятор мог знать и оптимизировать. Однако это не так, как была разработана Java. Большинство оптимизаций происходит во время выполнения, благодаря компилятору JIT. Компилятор JIT (возможно) способен сделать эту оптимизацию во время выполнения и многое другое, что статический компилятор не сможет сделать. Таким образом, Java решила упростить статический компилятор и байтовый код и сконцентрировать усилия по оптимизации в компиляторе JIT.

Поэтому, когда компилятор компилирует это, он просто заботится о линии d.eat(). d имеет тип Dog, eat() - это переопределяемый метод, который существует в иерархии классов Dog, а байт-код, используемый для динамического вызова этого метода, является сгенерированным.

1

Непонятно, на чем основан ваш вопрос.

Если у вас есть код формы

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 в скомпилированный код в соответствии с формальными правилами, как описано выше. Здесь нет сюрпризов.

+0

Итак, динамическая привязка существует, чтобы обеспечить гибкость с возможными изменениями в дизайне ООП, правильно? Если java может быть только привязана к стати, единственная проблема, возникающая в результате этого, - это несовместимость с будущими изменениями в дизайне классов? – Nikos

+0

@Nikos Примечание: методы 'static' определяются во время компиляции. –

+1

Нет, это совсем не о будущих изменениях. Это основная способность писать полиморфный код, который позволяет, например, абстрагировать алгоритм от деталей структуры памяти. Вы пишете 'sort' только один раз, и он работает с массивом-списком, связанным списком, массивом-деком и т. Д. –