Ну чтобы понять, как на самом деле работает статическая и динамическая привязка ? или как они идентифицируются компилятором и 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 ++. В C ++, если мы хотим переопределить один метод в другом классе, нам нужно объявить его виртуальным, но в Java все методы являются виртуальными по умолчанию, потому что мы можем переопределить каждый метод в дочернем классе (кроме частных, конечных и статических методов).
В Java каждая ссылочная переменная содержит два скрытых указателя
- Указатель на таблицу, которая снова содержит методы объекта и указатель на объект класса. например [Speak (), Speak (String) Объект класса]
- Указатель на память, выделенную в куче для данных этого объекта, например значений переменных экземпляра.
Таким образом, все ссылки на объекты косвенно содержат ссылку на таблицу, которая содержит все ссылки на методы этого объекта. В Java эта концепция позаимствована из C ++, и эта таблица известна как виртуальная таблица (vtable).
Vtable - это структура, подобная массиву, которая содержит имена виртуальных методов и их ссылки на индексы массива. JVM создает только одну виртуальную таблицу для каждого класса при загрузке класса в память.
Поэтому всякий раз, когда JVM сталкивается с invokevirtual
набором инструкций, она проверяет vtable этого класса на наличие ссылки на метод и вызывает конкретный метод, который в нашем случае является методом объекта, а не ссылкой.
Поскольку все это разрешается только во время выполнения, а во время выполнения JVM узнает, какой метод вызывать, поэтому переопределение метода известно как динамический полиморфизм или просто полиморфизм или динамическое связывание .
Вы можете прочитать об этом более подробно в моей статье Как JVM обрабатывает перегрузку и переопределение методов изнутри .