| Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате |
| Форум программистов > Java: Общие вопросы > Позднее связывание |
| Автор: Sherst 21.3.2006, 20:14 | ||
| Привет всем! Прочитал в одной книжке: "Для решения проблемы объектно-ориентированные языки используют концепцию позднего связывания. Когда вы посылаете объекту сообщение, код, который будет вызван, не определяется, пока не начнется время выполнения. Компилятор не убеждается, что функция существует, а выполняет проверку типа аргумента и возвращаемого значения, но он не знает точный код для выполнения." Это что получается я могу послать объекту сообщение(метод), который нигде не определен?
|
| Автор: powerOn 21.3.2006, 20:46 | ||||
Нет. Тут немного другое имеется ввиду. Скажу сразу: позднее связывание это механизм вызова виртуального метода, заключается он в том, что адрес (а не имя Это имеет отношение только к виртуальным функциям, которыми в Java все и являются по умолчанию в отличии от С++. Не виртуальная функция в С++ вызывается по смещению относительно начала класса, а виртуальные как и в Java по указателю из таблицы виртуальных функций. Это все хорошо проявляется при наследовании классов и при переопределении методов. Вот пример
|
| Автор: Sherst 21.3.2006, 21:11 |
| Т.е. выходит что на этапе выполнения программы (не компиляции) для класса создается таблица виртуальных ф-ий ? А могу ли я как-то программно просмотреть содержимое таблицы виртуальных функций? И еще вопрос Заранее спасибо. |
| Автор: powerOn 21.3.2006, 21:16 | ||||||
Существуют - static функции. Поменяй добавь этот идентификатор к функциям в примере и почувствуй разницу.
Нет на этапе компиляции. Все функции по умолчанию виртуальны, кроме статических.
Точно не знаю, посмотри в сторону Java Reflections, там есть возможность получить исчерпывающую информацию об объектах. |
| Автор: powerOn 21.3.2006, 21:40 | ||||
Етественно пока класса в памяти нет, ни о какой таблице вирт.ф. говорить не стоит. Эта таблица появится только вместе с загруженным классом. Но размер таблицы виртуальных функций неизменен для определенного и зависит от количества виртуальных функции. Точнее размер вт = кол-ву виртуальных. функции. (Естественно с учетом наследования т.е. вирт. ф. предков тоже считаютя). И изменить его можно только добавив/удалив вирт.ф-цию. - Что во время выполнения невозможно. Я скорее воспринял этот вопрос как о размере таблицы, и скорее всего не много ни так.... Добавлено @ 21:42
функции final естественно не виртуальны, поскольку их нельзя переопределить. А виртуальность функции безсмысленна без переопределения. |
| Автор: LSD 21.3.2006, 21:47 | ||
Тогда такой вопрос, каков размер таблицы виртуальных функций для java.lang.Object.toString()? Во время выполнения добавляется класс (загрузка то динамическая), а вот он уже может содержать новые виртуальные функции и их приходится добавлять в таблицу. На этапе компиляции известно только какие функции виртуальны а какие нет. В С++ все классы известны на этапе компиляции и поэтому можно сразу посчитать размер данной таблицы, но не в Java. Добавлено @ 21:50 Кстати не совсем, в Delphi можно объявить функцию с такой же сигнатурой что и в базовом классе, даже если та не является виртуальной. Это приведет к тому, что та функция будет скрыта в потомках, но виртуальной не будет (как именно она будет работать не скажу). Но возможность такая есть. |
| Автор: powerOn 21.3.2006, 21:56 | ||
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). Ну а потом уже все просто метод мы знаем и вызываем его |
| Автор: Sherst 21.3.2006, 23:04 | ||
MoonCat привел пример в котором есть строка
Правильно ли я понимаю: Во время исполнения происходит вызов метода meth() по таблице виртуальных ф-ий класса B (из-за new B()), в который передается указатель this(он говорит о том что meth() был вызван для объекта a2). И почему тогда для static все наоборот? |
| Автор: LSD 21.3.2006, 23:20 | ||||
Да. Смысл указателя this (в данном контексте), в том, чтобы определить истинный класс объекта для которого вызывается метод meth(), а затем уже по таблице виртуальных функций определить которую надо вызвать. А в static функциях, нет переменной this. Поэтому тип вызываемой функции определяется на этапе компиляции, и определяется он по имени класса. Даже если вызывать его не по имени класса, а через переменную:
Будет вызван статический метод из A. (кстати это плохой тон, так вызывать функции и компилятор выдает предупреждения). |
| Автор: Sherst 21.3.2006, 23:27 |
| Теперь ясно. Спасибо всем кто отозвался. |
| Автор: Sherst 22.3.2006, 00:07 |
| Забыл спросить Как компилятор по таблице виртуальных ф-ий находит именно нужный нам метод? |
| Автор: chief39 22.3.2006, 11:02 | ||
Нууу... трудно сказать какой там код - в написании JVM ещё не участвовал А если серьёзно - то компилятор не ищет по сей таблице(она юзается во время выполнения - не компиляции). Он просто компилит, ничего не связывая. ТОЛЬКО если находит final - МОЖЕТ встроить жёстко тело метода вместо вызова его. А во время выполнения он берёт объект некий. Определяет его класс. например иерархия A->B->C. Создал C. Вызываешь метод f(). JVM смотрит есть ли у класса С метод f(). Есть? - отлично, выполним его! Нету - смотрим у B. Есть? - выполним! Нету? - ещё выше смотрим. Ну и не следует забывать о private и final. Если верхние методы будут private - то JVM вообще ничего не найдёт(вернее ты не сможешь вызывать такой метод ещё на этапе компиляции - верхние недоступны а нижестоящих - нету, иначе наверх бы не полезло). А если верхние будут final - то нижестоящие классы аналогичный метод не смогут поиметь ещё на этапе компиляции. То есть для всех наследников в роли такого метода будет вызываться именно этот родительский. А свой они создать не смогут - компилятор по жопе нашлёпает. ЗЫ: когда подгружается класс - в таблицу его методы заносятся. А когда я говорил "ищем" или JVM "смотрит"- это по той самой таблице |
| Автор: powerOn 22.3.2006, 11:24 | ||
chief39, а как понять "смотрит", в том смысле как определяется что это имеено та функция что нам нужна, а ни какая нибудь другая? |
| Автор: LSD 22.3.2006, 12:16 |
| Обычно иерархия наследования не большая, так что в таблицу можно занести все классы, и для каждого указать какой метод вызывать. Хотя это все конечно зависит от реализации. |
| Автор: powerOn 22.3.2006, 12:20 |
| Так вопрос немного прояснился Вот документация проясняющая ситуацию в целом. Думаю, что там есть ответы на все вопросы. Спецификация Виртуальной Машины языка Ява 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 | ||
Т.е. я так понял, что в Java все очень динамически сделано. Остается только выразить восторг архитекторам JVM, поскольку при такой реализации вызов метода у java класса порядком опережает (судя по вот этой статье http://www.codenet.ru/webmast/java/javavscpp.php) вызоав виртуального метода в С++. |
| Автор: chief39 22.3.2006, 12:58 |
| АХА. Можно final добавлять - тогда компилер МОЖЕТ РЕШИТЬ вписать намертво(всё равно не переопределят). Но не рекомендуют такое для ускорения производительности - только для запрета переопределения. Хотя и запрет надо с оглядкой юзать. Дядька Эккель попинывает создателей стандартного класса Vector за финалы в его реализации. Так что... "всю власть - гибкости!" |