Ну для того, щоб зрозуміти, як насправді працює статичне та динамічне прив'язування ? або як їх ідентифікує компілятор та JVM?
Давайте візьмемо нижче приклад, де Mammal
знаходиться батьківський клас, який має метод speak()
і Human
клас розширюється Mammal
, замінює speak()
метод, а потім знову перевантажує його speak(String language)
.
public class OverridingInternalExample {
private static class Mammal {
public void speak() { System.out.println("ohlllalalalalalaoaoaoa"); }
}
private static class Human extends Mammal {
@Override
public void speak() { System.out.println("Hello"); }
// Valid overload of speak
public void speak(String language) {
if (language.equals("Hindi")) System.out.println("Namaste");
else System.out.println("Hello");
}
@Override
public String toString() { return "Human Class"; }
}
// Code below contains the output and bytecode of the method calls
public static void main(String[] args) {
Mammal anyMammal = new Mammal();
anyMammal.speak(); // Output - ohlllalalalalalaoaoaoa
// 10: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Mammal humanMammal = new Human();
humanMammal.speak(); // Output - Hello
// 23: invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Human human = new Human();
human.speak(); // Output - Hello
// 36: invokevirtual #7 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V
human.speak("Hindi"); // Output - Namaste
// 42: invokevirtual #9 // Method org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V
}
}
Коли ми компілюємо вищезазначений код і намагаємось поглянути на байт-код javap -verbose OverridingInternalExample
, ми бачимо, що компілятор генерує константну таблицю, де присвоює цілі коди кожному виклику методу та байт-коду для програми, яку я витягнув і включив у саму програму ( дивіться коментарі під кожним викликом методу)
Дивлячись на коді вище ми бачимо , що в байткод з humanMammal.speak()
, human.speak()
і human.speak("Hindi")
зовсім різні ( invokevirtual #4
, invokevirtual #7
, invokevirtual #9
) , так як компілятор здатний розрізняти між ними на основі списку аргументів і засланні класу. Оскільки все це вирішується під час компіляції статично, тому перевантаження методу відоме як статичний поліморфізм або статичне прив’язування .
Але байт-код для anyMammal.speak()
і humanMammal.speak()
є однаковим ( invokevirtual #4
), оскільки згідно з компілятором обидва методи викликаютьсяMammal
посилання.
Тож тепер постає питання, якщо обидва виклики методів мають однаковий байт-код, то як JVM знає, який метод викликати?
Ну, відповідь прихована в самому байт-коді, і це invokevirtual
набір інструкцій. JVM використовує invokevirtual
інструкцію для виклику Java-еквівалента віртуальних методів C ++. У С ++, якщо ми хочемо замінити один метод в іншому класі, нам потрібно оголосити його як віртуальний, але в Java всі методи за замовчуванням є віртуальними, оскільки ми можемо замінити кожен метод у дочірньому класі (крім приватних, кінцевих та статичних методів).
У Java кожна посилальна змінна містить два приховані вказівники
- Вказівник на таблицю, яка знову містить методи об'єкта та вказівник на об'єкт Class. наприклад [speak (), speak (String) Class object]
- Вказівник на пам'ять, виділену в купі для даних цього об'єкта, наприклад значення змінних екземпляра.
Отже, усі посилання на об’єкти опосередковано містять посилання на таблицю, яка містить усі посилання на методи цього об’єкта. Java запозичила цю концепцію у C ++, і ця таблиця відома як віртуальна таблиця (vtable).
Vtable - це масивна структура, яка містить імена віртуальних методів та їх посилання на індекси масивів. JVM створює лише одну vtable на клас, коли завантажує клас в пам’ять.
Тому щоразу, коли JVM стикається з invokevirtual
набором команд, він перевіряє vtable цього класу на посилання на метод і викликає конкретний метод, який у нашому випадку є методом з об'єкта, а не посилання.
Оскільки все це вирішується лише під час виконання та під час виконання, JVM дізнається, який метод викликати, саме тому Заміна методу відома як Динамічний поліморфізм або просто Поліморфізм або Динамічне прив'язка .
Ви можете прочитати це докладніше в моїй статті Як JVM обробляє перевантаження та внутрішнє заміщення методів .