Чтобы выяснить, в чем состоит различие между ранним (статическим) и поздним (динамическим) связыванием в Java, нужно сначала понять, что такое это самое связывание. Связывание означает наличие связи между ссылкой и кодом. Например, переменная, на которую вы ссылаетесь, привязана к коду, в котором она определена. Аналогично, вызываемый метод привязан к месту в коде, где он определен.
Существует два типа связывания методов в языке Java: ранее связывание (его ещё называют статическим) и позднее (соответственно, динамическое) связывание. Вызов метода в Java означает, что этот метод привязывается к конкретному коду или в момент компиляции, или во время выполнения, при запуске программы и создании объектов.
Можно понять из названия, статическое связывание носит более статический характер, так как происходит во время компиляции, то есть код «знает», какой метод вызывать после компиляции исходного кода на Java в файлы классов. А поскольку это относится к ранней стадии жизненного цикла программы, то называется также ранним связыванием (early binding).
С другой стороны, динамическое связывание происходит во время выполнения, после запуска программы виртуальной машиной Java. В этом случае то, какой метод вызвать, определяется конкретным объектом, так что в момент компиляции информация недоступна, ведь объекты создаются во время выполнения. А поскольку это происходит на поздней стадии жизненного цикла программы, то называется в языке Java поздним связыванием (late binding).
Итак, фундаментальное различие между статическим и динамическим связыванием в Java состоит в том, что первое происходит рано, во время компиляции на основе типа ссылочной переменной, а второе – позднее, во время выполнения, с использованием конкретных объектов.
Давайте рассмотрим еще несколько отличий, чтобы лучше разобраться с этим, а, кроме того, мочь ответить на этот очень популярный вопрос, который задают на собеседованиях по Java.
Раннее и позднее связывание в Java
Существует множество различий статического и динамического связывания в языке Java, но важнейшее – то, как их использует JVM. Задумывались ли вы когда-нибудь, каким образом JVM решает, какой метод вызвать, если в области видимости содержится более одного метода с одним именем? Если вы когда-либо использовали перегрузку или переопределение методов, то знаете, что в Java может быть несколько методов с одним именем. В случае с Java виртуальная машина JVM использует как статическое, так и динамическое связывание для выбора нужного метода.
Пример статического и динамического связывания в Java
В этой программе вы увидите, что привязка виртуальных методов не происходит во время компиляции при помощи статического связывания, поскольку в этом случае вызывался бы метод из суперкласса, как происходит со статическими методами, которые связываются рано. Если будет вызван метод из подкласса, то для связывания функции использовался конкретный объект во время выполнения, а, следовательно, для связывания виртуальных функций используется динамическое связывание.
public class Main {
public static void main(String[] args) {
// Пример статического и динамического связывания в Java
Insurance current = new CarInsurance();
// Динамическое связывание на основе объекта
int premium = current.premium();
// Статическое связывание на основе класса
String category = current.category();
System.out.println("premium : " + premium);
System.out.println("category : " + category);
}
}
class Insurance{
public static final int LOW = 100;
public int premium(){
return LOW;
}
public static String category(){
return "Insurance";
}
}
class CarInsurance extends Insurance{
public static final int HIGH = 200;
public int premium(){
return HIGH;
}
public static String category(){
return "Car Insurance";
}
}
Результаты выполнения:
premium : 200
category : Insurance
Как вы видите, вызов метода premium() привел к выполнению метода из подкласса, в то время как вызов метода category() привел к выполнению метода суперкласса. Это происходит из-за того, что premium() – виртуальный метод, который разрешается при помощи позднего связывания, в то время как category() – статический метод, который разрешается при помощи статического связывания во время компиляции по имени класса.
Интересно читать о Java? Вступайте в группу Java Developer!
Различия между ранним и поздним связыванием в языке Java
Теперь, когда вы разобрались и понимаете, как в языке Java связываются вызовы методов и как функционирует статическое и динамическое связывание, давайте еще раз перечислим ключевые различия между ранним и поздним связыванием в языке Java:
Статическое связывание происходит во время компиляции, а динамическое – во время выполнения.
Поскольку статическое связывание происходит на ранней стадии жизненного цикла программы, его называют ранним связыванием. Аналогично, динамическое связывание называют также поздним связыванием, поскольку оно происходит позже, во время работы программы.
Статическое связывание используется в языке Java для разрешения перегруженных методов, в то время как динамическое связывание используется в языке Java для разрешения переопределенных методов.
Аналогично, приватные, статические и терминальные методы разрешаются при помощи статического связывания, поскольку их нельзя переопределять, а все виртуальные методы разрешаются при помощи динамического связывания.
В случае статического связывания используются не конкретные объекты, а информация о типе, то есть для обнаружения нужного метода используется тип ссылочной переменной. С другой стороны, при динамическом связывании для нахождения нужного метода в Java используется конкретный объект.
Вот неплохое упражнение, основанное на понятиях статического и динамического связывания в языке Java. Сможете ли вы ответить на вопрос: "Что будет выведено при выполнении следующей программы?"
Что выведет эта программа? Collection, Set или HashSet?
Вот и все, что мы хотели рассказать вам о различиях между ранним (статическим) и поздним (динамическим) связыванием в языке Java. Это один из лучших вопросов для телефонного собеседования по языку Java, поскольку оно предоставляет немало возможностей проверки глубины знаний кандидата. Всегда помните, что приватные, статические и final-методы связываются при помощи статического связывания, а виртуальные – динамического. Аналогично, лучший пример статического связывания – перегрузка методов, а переопределение – динамического.
Источник
Василий начал программировать еще в школе, но считал это занятие довольно скучным. Поэтому получил образование по электротехнике и ...
[Читать полную биографию]
Странный пример для самопроверки. В методы с аргументами Set и HashSet вообще нельзя передать аргумент типа Collection. Если удалить метод принимающий Collection, то код вообще не скомпилируется.
В чем странность? вам дают пример для самопроверки и этот пример валидный. он компилируется. даже более скажу, стат анализатор идеи подсветит какой метод будет выполняться, без какой либо компиляции. Как уже ранее говорилось, вызов стат метода определяется на этапе компиляции, на основе ТИПА переменной. По поводу второго замечания, есть четкая иерархия классов коллекций, так что когда вы удаляете метод с сигнатурой print(Collection) и пытаетесь в оставшиеся методы передать Collection, который является суперклассом для Set и HashSet, вы на что рассчитываете?
Сам тип переменной объявлен как Collection и компилятор будет выбирать подходящую сигнатуру метода
Раннее связывание (Early Binding) также известно как статическое связывание. В этом случае компилятор заранее определяет, какой метод будет вызван. Это происходит на этапе компиляции, поэтому также называется статическим связыванием. Раннее связывание используется, когда методы не переопределены.
public class Animal {
public void sound() {
System.out.println("This is Animal sound");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Animal();
animal.sound(); // Раннее связывание
}
}
В этом случае, когда мы вызываем метод sound() для объекта animal, компилятор заранее знает, какую реализацию метода использовать, и связывает вызов с реализацией из класса Animal.
Позднее связывание (Late Binding) также известно как динамическое связывание. Этот процесс происходит во время выполнения программы, когда точная реализация метода определяется динамически. Позднее связывание используется в Java для методов, которые были переопределены в подклассах (override).
public class Animal {
public void sound() {
System.out.println("This is Animal sound");
}
}
public class Dog extends Animal {
@Override
public void sound() {
System.out.println("This is Dog sound");
}
}
public class Main {
public static void main(String[] args) {
Animal animal = new Dog();
animal.sound(); // Позднее связывание
}
}
Здесь, когда мы вызываем метод sound() для объекта animal, который ссылается на объект типа Dog, конкретная реализация метода определяется во время выполнения. Несмотря на то что тип ссылки — Animal, вызывается переопределённый метод из класса Dog.
import java.util.*;
public class StaticBinding {
public static void main(String[] args) {
Collection collection = new HashSet();
//print(collection);
/*
Если методы static - output: Collection
Статические или / и перегруженные методы связываются статически т.е.
во время компиляции и по ТИПУ ПЕРЕМЕННОЙ,
т.е. "collection" относится к Collection.
*/
new StaticBinding().print(collection);
/*
Если методы не static - output: Collection
Почему? Здесь тоже самое - нестатические методы перегружены,
а соответственно действует ранее связывание,
т.е. "collection" относится к Collection.
*/
}
public void print(Collection collection) {
System.out.println("Collection");
}
public void print(Set set) {
System.out.println("Set");
}
public void print(HashSet hashSet) {
System.out.println("HashSet");
}
}
И еще: Полиморфизм поддерживается только для обычных вызовов методов. Например, прямое обращение к полю будет обработано на стадии компиляции (по типу переменной).
"В этой программе вы увидите, что привязка виртуальных методов не происходит во время компиляции при помощи статического связывания, поскольку в этом случае вызывался бы метод из суперкласса, как происходит со статическими методами, которые связываются рано. Если будет вызван метод из подкласса, то для связывания функции использовался конкретный объект во время выполнения, а, следовательно, для связывания виртуальных функций используется динамическое связывание."
Вот подобная формулировка это одна из самых основных причин, почему изучать надо на английском, так как простые вещи русские переводчики могут завернуть в инверсионные макароны с кучей сложноподчиненных предложений, объясняя результат кода до самого кода.
Статические методы существуют в единственном экземпляре их нельзя переопределить. В данном случае для компилятора это два различных метода и без явного указания, что нам нужен именно метод подкласса, он его не увидит, так как его перекрывает родительский.
Проще говоря когда методы с одинаковыми сигнатурами является не статиками - они переопределяются в классах наследниках и вызываются согласно подклассу, когда такие методы статичны - всегда вызывается только родительский класс если иное не указано явно, так как компилятор при создании статика ничего не знает о подклассах, они еще не существуют.
то вызовется именно статический метод класса CarInsurance, поскольку для статических методов происходит статическое связывание по типу переменной (не по типу объекта)
Похоже ошибка:
> Аналогично, приватные, статические и -=терминальные=_ методы разрешаются при помощи статического связывания, поскольку их нельзя переопределять, а все виртуальные методы разрешаются при помощи динамического связывания.
Потому что есть такая информация:
> A common misconception is that declaring a method as final improves efficiency by allowing the compiler to directly insert the method wherever it is called (see inline expansion). Because the method is loaded at runtime, compilers are unable to do this. Only the runtime environment and JIT compiler know exactly which classes have been loaded, and so only they are able to make decisions about when to inline, whether or not the method is final.[5]
Хорошая картинка, но есть ошибка:
Названы вирутальными только public, тогда как
> В Java виртуальная функция означает функцию, которая может быть переопределена в своих подклассах. Таким образом, все нестатические методы Java являются виртуальной функцией
ну или тут
> Виртуальный метод (виртуальная функция) — в объектно-ориентированном программировании метод (функция) класса, который может быть переопределён в классах-наследниках так, что конкретная реализация метода для вызова будет определяться во время исполнения. Таким образом, программисту необязательно знать точный тип объекта для работы с ним через виртуальные методы: достаточно лишь знать, что объект принадлежит классу или наследнику класса, в котором объявлен метод. Одним из переводов слова virtual с английского языка может быть «фактический», что больше подходит по смыслу.
т.e. это именно нестатические, нечастные или неконечные методы
Попытаюсь объяснить суть доступным языком по поводу связывания. Если в чем-то не прав, то поправьте, пожалуйста.
Допустим, программа требует ввести какое-либо число (не обговорено, какое именно).
Поставим задачу так, что для вывода числа на экран мы воспользовались методами, имеющие одинаковую реализацию. За исключением того, что принимают разные типы данных.
Например, методы: public void outputNumber(int number), public void outputNumber(double number).
Условились этим моментом. Теперь перейдем к главному. Если мы заранее дадим понять JVM, с каким типом данных будем работать, то произойдет раннее (статическое) связывание. То есть, допустим, мы напишем вот так:
int number = 5;
outputNumber(number);
Из кода понятно, что мы создали переменную number с типом данных int. Тогда JVM на этапе компиляции будет понимать, в какой метод посылать ей значение переменной.
Рассмотрим теперь такой вариант. Допустим, наш метод получает значение из какой-либо части кода. Причем нам неизвестно, какой тип данных переменная имеет (нам не важно, каким способом передается значение в метод. Там может быть всё, что угодно. Для простоты отписал переменную number. Причем (number) не обязательно int).
outputNumber(number);
Тогда JVM и определяет во время исполнения кода, какое значение передается в метод. То есть, происходит позднее (динамическое) связывание. Надеюсь, что всё, что я написал, является верным.
Прочитав еще кое-какой материал, я пришел к выводу, что выше описал только раннее связывание (перегрузку методов). Динамическое связывание проявляется при переопределении методов. Если кто-то желает разобраться - оставьте комментарий под моим.
Я не понял п.4 : "Аналогично, приватные, статические и терминальные методы разрешаются при помощи статического связывания, поскольку их нельзя переопределять..."
Нельзя переопределять?
А разве в примере с классом Insurance мы не переопределили метод category() ?
Заметь, что метод category() статический, а статический метод переопределить нельзя. В классе CarInsurance свой метод category() не подозревающий о методе category() из класса Insurance.
Да, все верно. Можно проверить - если в классе CarInsurance обозначить метод category() аннотацией @Override- будет ошибка, т.к. метод category() не является переопределенным.
Короче, перечитал много раз, много интересных и умных и красивых слов. Для тех кто в танке поясню на пальцах, потому что всем все очевидно, но никто так и не понял.
VarA var = new VarB;
Слева от знака = пишется переменная/класс/итп откуда берется описание/названия полей/методов/итп.
Справа от знака = после new пишется откуда берутся реализация этих полей/методов/итп.
слово static означает что-то типа единственный экземпляр, объект из которого создать нельзя, однако если создать объект с вариацией как сверху, то реализация со словом static будет браться оттуда, где пишутся названия.
В теории, вроде, все ясно. Но вот пример для меня оказался не показательным.
Переписал его так, чтобы не было static, final, private - результат оказался таким же.
public class Test {
Collection c = new HashSet();
public Test() {
print(c);
}
public void print(Collection c) {
System.out.println("Collection");
}
public void print(Set s) {
System.out.println("Set");
}
public void print(HashSet hs) {
System.out.println("HashSet");
}
}
Здесь получается, что JVM в таком случае не понимает какую реализацию ей брать, и чтобы исключить возможность ошибки, создатели JVM ее приравняли типа как к статике, т.е. она берет свою реализацию не из реализации справа, а из описания слева. ))) Если бы так не сделали, то процессор бы завис в бесконечном цикле выбора 1 реализации из трех. )))
вот так позднее связывание работает, если добавить static будет раннее
public class Test {
public static class Collection {
public void print() {
System.out.println("Collection");
}
}
public static class Set extends Collection {
public void print() {
System.out.println("Set");
}
}
public static class HashSet extends Set {
public void print() {
System.out.println("HashSet");
}
}
public static void main(String[] args) {
Collection c = new HashSet();
c.print(); // HashSet
}
}
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ
CarInsurance
, поскольку для статических методов происходит статическое связывание по типу переменной (не по типу объекта)