Рубрики
Без рубрики

Как JVM Обрабатывает полиморфизм (Перегрузка и переопределение методов) Внутренне

В моей предыдущей статье Все О Перегрузке Методов Против переопределения метода, я уже обсуждал met… Помеченный java, полиморфизм, переопределение, jvm.

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

В этой статье мы увидим, Как JVMHandle Выполняет Внутреннюю Перегрузку И Переопределение Метода, как JVM определяет, какой метод следует вызывать.

Давайте возьмем пример родительского класса Млекопитающее и ребенок Человек классы из нашего предыдущего блога, чтобы понять это более четко.

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
    }
}

Мы можем ответить на этот вопрос двумя способами: логическим и физическим, давайте рассмотрим логический способ.

Логический Способ

Логически можно сказать, что на этапе компиляции метод вызова рассматривается из ссылочного типа. Но во время выполнения метод будет вызван из объекта, на который ссылается ссылка.

Например, в human Mammal.speak(); компилятор строки скажет Mammal.speak () () вызывается, потому что млекопитающее человека относится к типу Млекопитающее . Но во время выполнения JVM знает, что млекопитающее человека держит Человеческий объект так что Human.speak()

Ну, это довольно просто, пока мы не сохраним это только на концептуальном уровне. Как только у нас возникнет сомнение в том, как JVM справляется со всем этим внутренне? или как JVM вычисляет, какой метод он должен вызывать.

Кроме того, мы знаем, что перегруженные методы не называются полиморфными и разрешаются во время компиляции, и именно поэтому иногда перегрузка метода также известна как**полиморфизм во время компиляции или раннее/статическое связывание**.

Но переопределенные методы разрешаются во время выполнения, потому что компилятор не знает, что объект, который мы назначаем нашей ссылке, переопределил метод или нет.

Физический Способ

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

Приведенная выше команда показывает байт-код в двух разделах

1. Постоянный пул: содержит почти все, что необходимо для выполнения нашей программы, например, ссылки на методы ( #Methodref ), объекты классов ( #Класс ), строковые литералы ( #Строка ), пожалуйста, нажмите на изображение для увеличения.

2. Байт-код программы : исполняемые инструкции по байт-коду, пожалуйста, нажмите на изображение для увеличения.

Почему перегрузка метода называется статической привязкой

В вышеупомянутом коде человеческое млекопитающее.speak() компилятор скажет speak() вызывается из Млекопитающее но во время выполнения он будет вызван из объекта, который держит млекопитающее человека , который является объектом класса Человек .

И, взглянув на приведенный выше код и изображения, мы можем видеть, что байт-коды человеческое млекопитающее.говорить() , человек.говорить() и human.speak ("хинди") совершенно разные, потому что компилятор может различать их на основе ссылки на класс.

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

Почему переопределение метода называется динамической привязкой

Байт-код для любого млекопитающего.говорить() и млекопитающего человека.говорить() одинаковы ( вызов виртуального #4//Метод организации/программирование/митра/упражнения/Переопределение международного примера$Млекопитающее.говорить:()V ), потому что в соответствии с компилятором оба метода вызываются Млекопитающее ссылка.

Итак, теперь возникает вопрос, если оба вызова метода имеют одинаковый байт-код, то как JVM узнает, какой метод вызывать?

Ну, ответ скрыт в самом байт-коде, и это invokevirtual инструкция, в соответствии со спецификацией JVM

invokevirtual вызывает метод экземпляра объекта, отправляющий (виртуальный) тип объекта. Это обычная отправка метода на языке программирования Java.

JVM использует инструкцию invokevirtual для вызова Java-эквивалента виртуальных методов C++. InC++ если мы хотим переопределить один метод в другом классе, нам нужно объявить его как виртуальный , Но в Java все методы по умолчанию являются виртуальными (кроме конечных и статических методов), потому что мы можем переопределить каждый метод в дочернем классе.

Операция invokevirtual принимает указатель на вызов ссылки на метод ( #4 индекс в пул констант)

invokevirtual #4   // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V

И эта ссылка на метод № 4 снова ссылается на имя метода и ссылку на класс

#4 = Methodref   #2.#27   // org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
#2 = Class   #25   // org/programming/mitra/exercises/OverridingInternalExample$Mammal
#25 = Utf8   org/programming/mitra/exercises/OverridingInternalExample$Mammal
#27 = NameAndType   #35:#17   // speak:()V
#35 = Utf8   speak
#17 = Utf8

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

Виртуальная машина Java не требует какой-либо определенной внутренней структуры для объектов 4.

И в закладке 4 состояния

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

Это означает, что каждая ссылочная переменная содержит два скрытых указателя

  1. Указатель на таблицу, которая снова содержит методы объекта, и указатель на объект класса. например, [говорить(), говорить(строка) Объект класса]
  2. Указатель на память, выделенную в куче для данных этого объекта, например, значений переменных экземпляра.

Но снова возникает вопрос, как вызвать виртуальное внутренне сделать это? Ну, никто не может ответить на этот вопрос, потому что это зависит от реализации JVM и варьируется от JVM к JVM.

И из приведенных выше утверждений мы можем сделать вывод, что ссылка на объект косвенно содержит ссылку/указатель на таблицу, которая содержит все ссылки на методы этого объекта. Java позаимствовала эту концепцию из C++, и эта таблица известна под различными именами, такими как таблица виртуальных методов ( VMT ), таблица виртуальных функций (** vftable**), виртуальная таблица ( vtable ), таблица отправки .

Мы не можем быть уверены, как vtable реализован в Java, потому что он зависит от JVM. Но мы можем ожидать, что он будет следовать той же стратегии, что и C++, где vtable представляет собой структуру, подобную массиву, которая содержит имена методов и их ссылки на индексы массива. И всякий раз, когда JVM пытается выполнить виртуальный метод, он всегда запрашивает vtable его адрес.

Существует только один vtable для каждого класса, что означает, что он уникален и одинаков для всех объектов класса, аналогичного Классу объекту. Я подробнее обсуждал Класс объект в своих статьях Почему внешний класс Java не может быть статичным и Почему Java является чисто объектно-ориентированным языком Или Почему Бы И Нет .

Таким образом, существует только одна таблица для Объекта класса, который содержит все 11 методов (если мы не учитываем регистранты) и ссылки на их соответствующие тела методов.

Когда JVM загружает класс Mammal в память, он создает для него объект Class и создает vtable , который содержит все методы из vtable класса объектов с одинаковыми ссылками (потому что Mammal не переопределяет ни один метод из объекта) и добавляет новую запись для speak метода.

Теперь наступает очередь Человека класса, и теперь JVM скопирует все записи из vtable из Млекопитающего класса в vtable из Человека и добавляет новую запись для перегруженной версии speak(строка) .

JVM знает, что Человеческий класс переопределил два метода, один из которых toString() из Объекта и второе – пятнышко() от Млекопитающего . Теперь вместо создания новых записей для этих методов с обновленными ссылками. JVM изменит ссылки на уже существующие методы в том же индексе, где они присутствовали ранее, и сохранит те же имена методов.

invokevirtual заставляет JVM обрабатывать значение при ссылке на метод #4 , не как адрес, а как имя метода для поиска текущего объекта в vtable .

Я надеюсь, что теперь стало бы немного понятно, как JVM смешивает постоянный пул записи и vtable чтобы сделать вывод, какой метод он собирается вызывать.

Вы можете найти полный код в этом Репозитории Github и, пожалуйста, не стесняйтесь оставлять свои ценные отзывы.

Оригинал: “https://dev.to/njnareshjoshi/how-does-jvm-handle-polymorphism-method-overloading-and-overriding-internally-2m26”