Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > Java: Общие вопросы > Позднее связывание


Автор: Sherst 21.3.2006, 20:14
Привет всем!

Прочитал в одной книжке:
"Для решения проблемы объектно-ориентированные языки используют концепцию позднего связывания. Когда вы посылаете объекту сообщение, код, который будет вызван, не определяется, пока не начнется время выполнения. Компилятор не убеждается, что функция существует, а выполняет проверку типа аргумента и возвращаемого значения, но он не знает точный код для выполнения."

Это что получается я могу послать объекту сообщение(метод), который нигде не определен?

Код

class A 
{
}

class B extends A 
{
 public static void main(String[] args) {
   A a = new A();
   a.myMethod();//myMethod()-нигде не определен
 }
}


Автор: powerOn 21.3.2006, 20:46
Цитата

Это что получается я могу послать объекту сообщение(метод), который нигде не определен?


Нет. Тут немного другое имеется ввиду.
Скажу сразу: позднее связывание это механизм вызова виртуального метода, заключается он в том, что адрес (а не имя smile ) метода, который необходимо выполнить будет изветсен только во время выполнения программы. Каждый класс обладающий виртуальными методами имеет так назаваемую таблицу виртуальных функций, в которой храняться их адреса. Во время содания/выполнения класса в эту таблицу вносятся реальные адреса функций и именно по этим адресам происходит их вызов.

Это имеет отношение только к виртуальным функциям, которыми в Java все и являются по умолчанию в отличии от С++. Не виртуальная функция в С++ вызывается по смещению относительно начала класса, а виртуальные как и в Java по указателю из таблицы виртуальных функций.

Это все хорошо проявляется при наследовании классов и при переопределении методов.

Вот пример
Код

class A {
 public void meth() {
 System.out.println("a meth call!");
}
}

class B extends A {
 public void meth(){
 System.out.println("b meth call!");
}
}


class C {
 public static void main(String [] s) {
     A  a1 = new A();
     B  b1 = new B(); 
     a1.meth(); // a meth call!
     b1.meth(); // b meth call!
     
     A a2 = new B();
     a2.meth(); // b meth call!           Не будь этот метод виртуальным на экране бы появилось - a meth call!

 }

}







Автор: Sherst 21.3.2006, 21:11
Т.е. выходит что на этапе выполнения программы (не компиляции) для класса создается
таблица виртуальных ф-ий ?
А могу ли я как-то программно просмотреть содержимое таблицы виртуальных функций?
И еще вопрос smile получается что в Java не существует не виртуальных ф-ий?

Заранее спасибо.

Автор: powerOn 21.3.2006, 21:16
Цитата

И еще вопрос smile получается что в Java не существует не виртуальных ф-ий?


Существуют - static функции. Поменяй добавь этот идентификатор к функциям в примере и почувствуй разницу.

Цитата

Т.е. выходит что на этапе выполнения программы (не компиляции) для класса создается
таблица виртуальных ф-ий ?


Нет на этапе компиляции. Все функции по умолчанию виртуальны, кроме статических.

Цитата

А могу ли я как-то программно просмотреть содержимое таблицы виртуальных функций?


Точно не знаю, посмотри в сторону Java Reflections, там есть возможность получить исчерпывающую информацию об объектах.



Автор: LSD 21.3.2006, 21:24
Цитата(MoonCat @ 21.3.2006, 21:16 Найти цитируемый пост)
Нет на этапе компиляции.

Нет, на этапе выполнения smile
Пока класс не загружен в таблице его не будет, так что таблицы динамические.

Цитата(Sherst @ 21.3.2006, 21:11 Найти цитируемый пост)
А могу ли я как-то программно просмотреть содержимое таблицы виртуальных функций?

Средствами Java никак, возможно в JNI есть методы, но сильно сомеваюсь. Т.к. эта штука очень сильно влияет на производительность и наверняка подвержена частым изменениям.

Цитата(Sherst @ 21.3.2006, 21:11 Найти цитируемый пост)
И еще вопрос  получается что в Java не существует не виртуальных ф-ий?

Наоборот, все не статические функции по умолчанию виртуальны, чтобы сделать их не виртуальными надо объявить их final.

Автор: powerOn 21.3.2006, 21:40
Цитата

Нет, на этапе выполнения smile
Пока класс не загружен в таблице его не будет, так что таблицы динамические.



Етественно пока класса в памяти нет, ни о какой таблице вирт.ф. говорить не стоит. Эта таблица появится только вместе с загруженным классом.
Но размер таблицы виртуальных функций неизменен для определенного и зависит от количества виртуальных функции. Точнее размер вт = кол-ву виртуальных. функции. (Естественно с учетом наследования т.е. вирт. ф. предков тоже считаютя). И изменить его можно только добавив/удалив вирт.ф-цию. - Что во время выполнения невозможно.


Я скорее воспринял этот вопрос как о размере таблицы, и скорее всего не много ни так.... smile
Добавлено @ 21:42
Цитата

Наоборот, все не статические функции по умолчанию виртуальны, чтобы сделать их не виртуальными надо объявить их final.


функции final естественно не виртуальны, поскольку их нельзя переопределить. А виртуальность функции безсмысленна без переопределения.

Автор: LSD 21.3.2006, 21:47
Цитата(MoonCat @ 21.3.2006, 21:40 Найти цитируемый пост)
Етественно пока класса в памяти нет, ни о какой таблице вирт.ф. говорить не стоит. Эта таблица появится только вместе с загруженным классом.
Но размер таблицы виртуальных функций неизменен для определенного и зависит от количества виртуальных функции. Точнее размер вт = кол-ву виртуальных. функции. (Естественно с учетом наследования т.е. вирт. ф. предков тоже считаютя). И изменить его можно только добавив/удалив вирт.ф-цию. - Что во время выполнения невозможно.

Тогда такой вопрос, каков размер таблицы виртуальных функций для java.lang.Object.toString()? smile

Во время выполнения добавляется класс (загрузка то динамическая), а вот он уже может содержать новые виртуальные функции и их приходится добавлять в таблицу. На этапе компиляции известно только какие функции виртуальны а какие нет.
В С++ все классы известны на этапе компиляции и поэтому можно сразу посчитать размер данной таблицы, но не в Java.
Добавлено @ 21:50
Цитата(MoonCat @ 21.3.2006, 21:40 Найти цитируемый пост)
А виртуальность функции безсмысленна без переопределения.

Кстати не совсем, в Delphi можно объявить функцию с такой же сигнатурой что и в базовом классе, даже если та не является виртуальной. Это приведет к тому, что та функция будет скрыта в потомках, но виртуальной не будет (как именно она будет работать не скажу). Но возможность такая есть.

Автор: powerOn 21.3.2006, 21:56
Цитата

Тогда такой вопрос, каков размер таблицы виртуальных функций для java.lang.Object.toString()? smile



LSD, извиняюсь, я немного запутался.
Мы же загружаем скомпилированный байт код, и загрузчик прекрастно знает что за класс он грузит и от кого он наследован, и следовательно знает (или может просчитать) количество виртуальных функций для этого класса, создать для него таблицу вирт функции фиксированной длины.

Автор: LSD 21.3.2006, 22:19
ОК, залезем немного во внутренности.

Возьмем все тот же java.lang.Object.toString(), когда ты пишешь в программе o.toString(), то в код вставляется вызов метода toString() из java.lang.Object. Потом во время исполнения происходит вызов этого метода причем в него передаются, помимо объявленных параметров еще и указатель на this и таблица виртуальных функций. Далее в этой таблице находится адрес функции которую надо вызвать для данного класса (если не найдем, то в C++ получим Pure Virtual Function Call, в Java IncompatibleClassChangeError). Ну а потом уже все просто метод мы знаем и вызываем его smile

Автор: Sherst 21.3.2006, 23:04
MoonCat привел пример в котором есть строка

Код

A a2 = new B();
a2.meth(); // b meth call!     


Правильно ли я понимаю:
Во время исполнения происходит вызов метода meth() по таблице виртуальных ф-ий класса B (из-за new B()), в который передается указатель this(он говорит о том что meth() был вызван для объекта a2).

И почему тогда для static все наоборот?

Автор: LSD 21.3.2006, 23:20
Цитата(Sherst @ 21.3.2006, 23:04 Найти цитируемый пост)
Во время исполнения происходит вызов метода meth() по таблице виртуальных ф-ий класса B (из-за new B()), в который передается указатель this(он говорит о том что meth() был вызван для объекта a2).

Да. Смысл указателя this (в данном контексте), в том, чтобы определить истинный класс объекта для которого вызывается метод meth(), а затем уже по таблице виртуальных функций определить которую надо вызвать.


Цитата(Sherst @ 21.3.2006, 23:04 Найти цитируемый пост)
И почему тогда для static все наоборот?

А в static функциях, нет переменной this. Поэтому тип вызываемой функции определяется на этапе компиляции, и определяется он по имени класса. Даже если вызывать его не по имени класса, а через переменную:
Код
A a2 = new B();    
a2.someStaticMethod(); //A.someStaticMethod() call!

Будет вызван статический метод из A. (кстати это плохой тон, так вызывать функции и компилятор выдает предупреждения).

Автор: Sherst 21.3.2006, 23:27
Теперь ясно.
Спасибо всем кто отозвался.

Автор: Sherst 22.3.2006, 00:07
Забыл спросить smile

Как компилятор по таблице виртуальных ф-ий находит именно нужный нам метод?

Автор: chief39 22.3.2006, 11:02
Цитата(Sherst @ 22.3.2006, 00:07 Найти цитируемый пост)
Как компилятор по таблице виртуальных ф-ий находит именно нужный нам метод?

Нууу... трудно сказать какой там код - в написании JVM ещё не участвовал smile))

А если серьёзно - то компилятор не ищет по сей таблице(она юзается во время выполнения - не компиляции).
Он просто компилит, ничего не связывая. ТОЛЬКО если находит final - МОЖЕТ встроить жёстко тело метода вместо вызова его.

А во время выполнения он берёт объект некий. Определяет его класс. например иерархия A->B->C. Создал C. Вызываешь метод f(). JVM смотрит есть ли у класса С метод f(). Есть? - отлично, выполним его! Нету - смотрим у B. Есть? - выполним! Нету? - ещё выше смотрим.
Ну и не следует забывать о private и final. Если верхние методы будут private - то JVM вообще ничего не найдёт(вернее ты не сможешь вызывать такой метод ещё на этапе компиляции - верхние недоступны а нижестоящих - нету, иначе наверх бы не полезло). А если верхние будут final - то нижестоящие классы аналогичный метод не смогут поиметь ещё на этапе компиляции. То есть для всех наследников в роли такого метода будет вызываться именно этот родительский. А свой они создать не смогут - компилятор по жопе нашлёпает. smile
ЗЫ: когда подгружается класс - в таблицу его методы заносятся. А когда я говорил "ищем" или JVM "смотрит"- это по той самой таблице smile А вот как именно оно её итерирует - это уже проблемы разработчиков Sun smile Это ведь не суть важно для нас - главное что этот механизм предоставляет функциональность - а как... нас не волнует smile

Автор: powerOn 22.3.2006, 11:24
Цитата

ЗЫ: когда подгружается класс - в таблицу его методы заносятся. А когда я говорил "ищем" или JVM "смотрит"- это по той самой таблице smile




chief39, а как понять "смотрит", в том смысле как определяется что это имеено та функция что нам нужна, а ни какая нибудь другая?

Автор: LSD 22.3.2006, 12:16
Обычно иерархия наследования не большая, так что в таблицу можно занести все классы, и для каждого указать какой метод вызывать. Хотя это все конечно зависит от реализации.

Автор: powerOn 22.3.2006, 12:20
Так вопрос немного прояснился smile

Вот документация проясняющая ситуацию в целом.
Думаю, что там есть ответы на все вопросы. smile

Спецификация Виртуальной Машины языка Ява
http://www.uni-vologda.ac.ru/java/jvm/outline.htm

Спецификация языка Ява
http://www.uni-vologda.ac.ru/java/jls/index.html



Автор: chief39 22.3.2006, 12:32
У неё есть исходные данные:
- сигнатура метода(вобщем точное его описание)
- конкретный объект
- исходя из второго - класс объекта. Неважно на объект какого класса ссылка - JVM умеет точно определить конечный класс - то есть тот, который отвечает реальному объекту. То есть, если мы передадим ссылку на Object, она всё равно может вычислить что у нас там лежит String или производное от String'a
- исходя из третьего - список родителей этого класса.
В таблице будут лежать методы нашего класса, его родителя, дальнейших родителей и прабабушек.
Поищет метод для нашего класса. Если нету - для родителя. Если и у него нету - тогда ещё выше. Пока не найдёт где мы перестали переопределять сей метод.
Такого, чтоб метода не было вообще - быть не может - ибо компилятор ругнётся ещё при создании такого кода. Если мы допишем новый - ноу проблем. но хоть что-то должно быть, когда вызываем.
Например, есть класс Animal. У него метод born().
Наследуем в Cat и переопределяем born(). Компилим. Вызываем для Cat born(). Выполнится кошачий а не животный born().
Создаём Dog, унаследовав от Animal.
Компилим.
Вызываем у Dog born(). Вызовется Animal'ский
Дописываем Догу свой born(). КОМПИЛИМ ТОЛЬКО Dog! Animal и Cat лежат скомпиленые с прошлого раза неизменно!!!
Запускаем. Вызовется born() из Dog (потому что нашло раньше чем аналог в Animal). Раньше не было - добиралось до Animal.
Суть в том, что Animal не пришлось перекомпилить. Он не знал что у Dog есть(может быть такой метод). Он обнаружил его и связался с ним во время выполнения. При раннем связывании, пришлось бы животное тоже компилить.
(принимаем что в примере мы обращались к объекту Dog по ссылке типа Animal).

Автор: powerOn 22.3.2006, 12:39


Цитата

Суть в том, что Animal не пришлось перекомпилить. Он не знал что у Dog есть(может быть такой метод). Он обнаружил его и связался с ним во время выполнения. При раннем связывании, пришлось бы животное тоже компилить.

Т.е. я так понял, что в Java все очень динамически сделано.

Остается только выразить восторг архитекторам JVM, поскольку при такой реализации вызов метода у
java класса порядком опережает (судя по вот этой статье http://www.codenet.ru/webmast/java/javavscpp.php) вызоав виртуального метода в С++.

Автор: chief39 22.3.2006, 12:58
Цитата(MoonCat @ 22.3.2006, 12:39 Найти цитируемый пост)
Т.е. я так понял, что в Java все очень динамически сделано.

АХА.
Можно final добавлять - тогда компилер МОЖЕТ РЕШИТЬ вписать намертво(всё равно не переопределят). Но не рекомендуют такое для ускорения производительности - только для запрета переопределения. Хотя и запрет надо с оглядкой юзать. Дядька Эккель попинывает создателей стандартного класса Vector за финалы в его реализации. Так что... "всю власть - гибкости!" smile))

Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)