Что такое позднее и раннее связывание java
Перейти к содержимому

Что такое позднее и раннее связывание java

  • автор:

Позднее связывание «bindLate»

Материал на этой странице устарел, поэтому скрыт из оглавления сайта.

Обычный метод bind называется «ранним связыванием», поскольку фиксирует привязку сразу же.

Как только значения привязаны – они уже не могут быть изменены. В том числе, если метод объекта, который привязали, кто-то переопределит – «привязанная» функция этого не заметит.

Позднее связывание – более гибкое, оно позволяет переопределить привязанный метод когда угодно.

Раннее связывание

Например, попытаемся переопределить метод при раннем связывании:

function bind(func, context) < return function() < return func.apply(context, arguments); >; > var user = < sayHi: function() < alert('Привет!'); >> // привязали метод к объекту var userSayHi = bind(user.sayHi, user); // понадобилось переопределить метод user.sayHi = function() < alert('Новый метод!'); >// будет вызван старый метод, а хотелось бы - новый! userSayHi(); // выведет "Привет!"

…Привязка всё ещё работает со старым методом, несмотря на то что он был переопределён.

Позднее связывание

При позднем связывании bind вызовет не ту функцию, которая была в sayHi на момент привязки, а ту, которая есть на момент вызова.**

Встроенного метода для этого нет, поэтому нужно реализовать.

Синтаксис будет таков:

var func = bindLate(obj, "method");

obj Объект method Название метода (строка)

function bindLate(context, funcName) < return function() < return context[funcName].apply(context, arguments); >; >

Этот вызов похож на обычный bind , один из вариантов которого как раз и выглядит как bind(obj, «method») , но работает по-другому.

Поиск метода в объекте: context[funcName] , осуществляется при вызове, самой обёрткой.

Поэтому, если метод переопределили – будет использован всегда последний вариант.

В частности, пример, рассмотренный выше, станет работать правильно:

function bindLate(context, funcName) < return function() < return context[funcName].apply(context, arguments); >; > var user = < sayHi: function() < alert('Привет!'); >> var userSayHi = bindLate(user, 'sayHi'); user.sayHi = function() < alert('Здравствуйте!'); >userSayHi(); // Здравствуйте!

Привязка метода, которого нет

Позднее связывание позволяет привязать к объекту даже метод, которого ещё нет!

Конечно, предполагается, что к моменту вызова он уже будет определён ;).

function bindLate(context, funcName) < return function() < return context[funcName].apply(context, arguments); >; > // метода нет var user = < >; // ..а привязка возможна! var userSayHi = bindLate(user, 'sayHi'); // по ходу выполнения добавили метод.. user.sayHi = function() < alert('Привет!'); >userSayHi(); // Метод работает: Привет!

В некотором смысле, позднее связывание всегда лучше, чем раннее. Оно удобнее и надёжнее, так как всегда вызывает нужный метод, который в объекте сейчас.

Но оно влечёт и небольшие накладные расходы – поиск метода при каждом вызове.

Итого

Позднее связывание ищет функцию в объекте в момент вызова.

Оно используется для привязки в тех случаях, когда метод может быть переопределён после привязки или на момент привязки не существует.

Обёртка для позднего связывания (без карринга):

function bindLate(context, funcName) < return function() < return context[funcName].apply(context, arguments); >; >

Что такое позднее и раннее связывание java

На этом шаге рассмотрим полиморфизм.

Функция transmit, определенная в классе TelemetryData, может передавать идентификатор потока телеметрической информации и его временную метку. Та же самая функция в классе ElectricalData может вызывать функцию transmit из класса TelemetryData, передавая дополнительно величину напряжения и значения силы тока.

Такое поведение обеспечивается полиморфизмом, а операции называются полиморфными (polymorphic). Полиморфизм — это концепция теории типов, согласно которому одно и то же имя может обозначать экземпляры разных классов, связанных с общим суперклассом. Таким образом, любой объект с этим именем может по-разному выполнять общий набор операций. Используя полиморфизм, одну и ту же операцию можно по-разному реализовывать в классах, образующих иерархию. В результате подкласс может расширять возможности суперкласса или замещать базовые операции, как это делал класс ElectricalData.

Впервые идею полиморфизма описал Стрейчи (Strachey) [As quoted in Harland, D., Szyplewski, M., and Wainwright, J. October 1985. An Alternative View of Polymorphism. SIGPLAN Notices, vol. 20(10).], имея в виду возможность переопределять смысл символов, таких как «+». Эта концепция называется перегрузкой (overloading). В языке C++ можно объявить несколько функций с одним и тем же именем, которые при вызове различаются по своим сигнатурам, состоящим из различного количества и типов аргументов (в языке C++, в отличие от языка Ada, тип возвращаемого значения частью сигнатуры не считается). В противоположность этому язык Java не допускает перегрузки операторов. Кроме того, Стрейчи писал о параметрическом полиморфизме, который в настоящее время называется просто полиморфизмом.

Код программы, не использующей полиморфизм, содержит множество операторов многовариантного выбора. Без полиморфизма невозможно создать иерархию классов для разных типов телеметрических данных. В этом случае проектировщику пришлось бы создавать единую монолитную вариантную запись, содержащую все свойства, связанные со всеми типами данных. Для того чтобы отличать один вариант от другого, пришлось бы проверять дескриптор, связанный с записью.

Для того чтобы создать новый тип телеметрических данных, необходимо модифицировать эту вариантную запись, добавляя новый тип в каждый оператор case в каждом экземпляре записи. Это увеличивает вероятность ошибок и делает проект неустойчивым.

Наследование позволяет разделять разные виды абстракций, поэтому необходимость в монолитных типах исчезает. Каплан (Kaplan) и Джонсон (Johnson) отмечают, что полиморфизм наиболее полезен в тех случаях, когда несколько классов имеют один и тот же протокол [Kaplan, S., and Johnson, R. July 21, 1986. Designing and Implementing for Reuse. Urbana, IL: University of Illinois, Department of Computer Science, p. 8.]. Полиморфизм позволяет обойтись без операторов многовариантного выбора, поскольку каждый объект неявно сам знает свой тип.

Наследование без полиморфизма возможно, но не очень полезно. Полиморфизм тесно связан с механизмом позднего связывания. При полиморфизме связь метода и имени определяется только в процессе выполнения программы. В языке C++ программист имеет возможность выбирать между ранним и поздним связыванием имени с членом-функцией. В частности, если метод объявлен как виртуальный, то реализуется позднее связывание, а функция считается полиморфной. Если объявление виртуальной функции отсутствует, то используется раннее связывание и метод уточняется на этапе компиляции. В языке Java позднее связывание не требует использования ключевого слова virtual.

В традиционных языках программирования вызов подпрограмм носит исключительно статический характер. Например, в языке Pascal для оператора, вызывающего подпрограмму Р, обычно генерируется код, создающий новый стек, размещающий в нем аргументы, а затем изменяющий поток управления для выполнения кода, связанного с процедурой Р. Однако в языках, поддерживающих полиморфизм, таких как Smalltalk и C++, активизация операции может осуществляться динамически, поскольку класс объекта, из которого будет вызван метод, может быть неизвестным до выполнения программы. Ситуация становится еще интереснее, если используется механизм наследования. Семантика активизации операции при использовании механизма наследования без полиморфизма совпадает с простым статическим вызовом подпрограмм, но наличие полиморфизма вынуждает программиста прибегнуть к более сложным приемам.

Рассмотрим иерархию классов (рис. 1), в которую входят базовый класс Displayltem и три подкласса с именами Circle, Triangle и Rectangle. В свою очередь класс Rectangle имеет подкласс SolidRectangle.

Рис.1. Диаграмма классов

  • draw — нарисовать элемент,
  • move — передвинуть элемент,
  • location — вернуть координаты элемента.

Операция location является общей для всех подклассов и не требует переопределения, но операции draw и move должны быть переопределены, поскольку только подклассы могут знать, как изображать и передвигать элементы.

Класс Circle должен иметь переменную theRadius и соответствующие операции для установки и чтения значения этой переменной set и retrieve соответственно. Операция draw, переопределенная в подклассе, рисует окружность заданного радиуса с центром в точке theCenter. Аналогично, в классе Rectangle следует предусмотреть переменные theHeight и theWidth и соответствующие операции set и retrieve для установки и чтения их значений. Операция draw, переопределенная в этом подклассе, рисует прямоугольник заданной высоты и ширины с центром в заданной точке theCenter. Подкласс SolidRectangle наследует все свойства класса Rectangle, но операция draw в этом подклассе также переопределена. В частности, реализация функции draw в классе SolidRectangle сначала вызывает функцию draw, определенную в суперклассе Rectangle, а затем полученный контур заполняется цветом.

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

  • Объект ищет сообщение в словаре сообщений своего класса.
  • Если сообщение найдено, то вызывается код локально определенного метода.
  • Если сообщение не найдено, то поиск производится в словаре сообщений суперкласса.

Этот процесс распространяется вверх по иерархии, пока сообщение не будет найдено или не будет достигнут базовый класс Object. В последнем варианте язык Smalltalk предусматривает передачу сообщения doesNotUnderstand, служащего признаком ошибки.

Главной особенностью этого алгоритма является словарь сообщений (message dictionary), являющийся частью представления класса и потому скрытый от клиента.

Этот словарь создается одновременно с классом и содержит все методы, связанные с экземплярами данного класса. Поиск сообщения в словаре требует довольно много времени. Вызов метода в языке Smalltalk требует примерно в 1,5 раза больше времени, чем простой вызов подпрограммы. В коммерческих версиях языка Smalltalk предусмотрена оптимизация диспетчеризации методов за счет кеширования доступа к словарю, поэтому часто передаваемые сообщения находятся быстро.

Как правило, кеширование ускоряет быстродействие программ на 20-30% [Deutsch, P. 1983. Efficient Implementation of the Smalltalk-80 System. In: Proceedings of the 11th Annual ACM Symposium on the Principles of Programming Languages, p. 300.].

Операция draw, определенная в подклассе SolidRectangle, представляет собой особый случай. Как отмечено выше, ее реализация сначала вызывает одноименный метод, определенный в суперклассе Rectangle. В языке Smalltalk методы суперкласса выделяются с помощью ключевого слова super.

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

Исследования Дейча (Deutsch) дают основание утверждать, что в 85% случаев полиморфизм не нужен, поэтому вызов метода часто можно свести к обычному вызову процедуры. Дафф (Duff) отмечает, что, разрабатывая такие классы, программисты часто неявно предполагают существование механизма раннего связывания [Duff, С August 1986. Designing an Efficient Language. Byte vol. 11(8), p. 216.]. К сожалению, в языках без контроля типов, таких как Smalltalk, не существует общепринятых средств для передачи этих предположений компилятору.

В языках программирования с более строгим контролем типов, таких как C++, разработчик имеет такую возможность. Поскольку диспетчеризацию методов следует применять как можно реже, сохраняя тем не менее возможность полиморфной диспетчеризации, вызов метода в этих языках немного отличается от описанного выше.

Программист, работающий на языке C++, может выбрать операции для позднего связывания, объявив их виртуальными. Все вызовы остальных методов обрабатываются компилятором с помощью раннего связывания, т.е. как простой вызов процедуры.

Для управления виртуальными функциями в языке C++ используется концепция виртуальной таблицы vtable, определяемой для каждого объекта, требующего полиморфной диспетчеризации, в ходе его создания (т.е. когда класс объекта фиксирован). Виртуальная таблица, как правило, содержит список указателей на виртуальные функции. Например, при создании объекта класса Rectangle виртуальная таблица будет включать запись для виртуальной функции draw, содержащую указатель на ближайшую реализацию функции draw. Если же в класс Displayltem включена виртуальная функция Rotate, которая в классе Rectangle не переопределяется, то соответствующий указатель будет ссылаться на ее реализацию в классе Displayltem. Это позволяет исключить поиск на этапе выполнения программы. Ссылка на виртуальную функцию-член объекта представляет собой лишь косвенное обращение через соответствующий указатель, а требуемый код выполняется немедленно без какого-либо поиска [Stroustrup, B. 1988. Private communication.].

На следующем шаге рассмотрим множественное наследование.

Связывание в Java: динамическое – позднее, статическое – раннее

Ассоциация вызова метода с телом метода известна как связывание в Java. Есть два вида связывания.

Статическое связывание

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

Пример

class Super < public static void sample()< System.out.println("This is the method of super class"); >> Public class Sub extends Super < Public static void sample()< System.out.println("This is the method of sub class"); >Public static void main(String args[]) < Sub.sample() >>

Итог

This is the method of sub class

Динамическое связывание

В динамическом связывании вызов метода связан с телом метода во время выполнения. Это также известно как позднее связывание. Делается с помощью методов экземпляра.

Пример

class Super < public void sample()< System.out.println("This is the method of super class"); >> Public class extends Super < Public static void sample()< System.out.println("This is the method of sub class"); >Public static void main(String args[]) < new Sub().sample() >>

Итог

This is the method of sub class

Ниже перечислены существенные различия.

  • Это происходит во время компиляции.
  • Это наблюдается в частных, статических и конечных методах.
  • Он известен как раннее связывание.
  • Происходит во время выполнения.
  • Это наблюдается в методах экземпляра.
  • Он известен как позднее связывание.

Средняя оценка 1.4 / 5. Количество голосов: 63

Спасибо, помогите другим — напишите комментарий, добавьте информации к статье.

Или поделись статьей

Видим, что вы не нашли ответ на свой вопрос.

Помогите улучшить статью.

Напишите комментарий, что можно добавить к статье, какой информации не хватает.

Раннее и позднее связывание Java?

1. Что это такое? (возможно на конкретных примерах (ссылки))
2. Основное, что нужно знать об этом, чтобы понимать
3. Зачем это нужно?

  • Вопрос задан более трёх лет назад
  • 8866 просмотров

2 комментария

Оценить 2 комментария

zeekenru

Гуглить не умеете?

NeToster

Nikita @NeToster Автор вопроса

Vladislav Kovalev: Умею, конечно. Цель создания вопроса, получить доступный ответ простыми словами. Человек который в этом разобрался, возможно расскажет свое понимание на данном ресурсе

Решения вопроса 0
Ответы на вопрос 2
Пишу комментарии в комментарии, а не в ответы

Этим и полезно знание знакомство с C++ (=

Суть в чем: куда-то в память загружается объект-тип (не экземпляр с данными, а именно представление типа), в котором физически располагается код методов. У каждого экземпляра классов есть ссылка на свой объект-тип. Когда тип ссылки на объект совпадает с его типом, то все просто: мы знаем, что метод можно найти напрямую. Открываются перспективы для оптимизации компилятора: подстановка конкретного адреса метода или даже инлайнинг (когда вызов метода заменяется непосредственно на код из метода, чтобы не тратить такты на передачу параметров). Это раннее связывание.
Если же у нас возможен полиморфный вызов (переменная-ссылка может указывать на объект-наследник, у которого может быть переопределение родительского метода), то нужно узнать точно какого типа объект, чтобы вызвать подходящее определение метода из нужного типа. Увы, это мы узнаем только в рантайме, поэтому компилятор не поможет оптимизировать этот процесс. Это позднее связывание.

Во многих Си-подобных языках (C++, C# и т.п.) в угоду производительности используется раннее связывание по умолчанию: полиморфные методы должны явно помечаться программистом ключевым словом virtual (иначе будет вызываться метод из типа переменной-ссылки, не полиморфно), в Java же в угоду читаемости кода используется позднее связывание по умолчанию: все методы неявно «virtual», так что качество связывания отдается на откуп компилятору.
Конечно, есть возможность пометить метод ключевым словом final , что запретит переопределять метод в наследниках, т.е. у компилятора появятся все полномочия жестко вбить необходимый адрес метода в точку вызова без страха, ибо альтернативных версий метода в наследниках просто быть не может.
(В С++11 также есть ключевое слово final , а в C# — sealed )

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *