Статический Vs. Динамическое связывание в Java

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

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

Я нашел пример статического связывания онлайн, который дает этот пример:

public static void callEat(Animal animal) {
    System.out.println("Animal is eating");
}

public static void callEat(Dog dog) {
    System.out.println("Dog is eating");
}

public static void main(String args[])
{
    Animal a = new Dog();
    callEat(a);
}

И что это напечатало бы "животное ест", потому что призыв к callEat использует статическое связывание, но я не уверен, почему это считается статическим связыванием.

До сих пор ни один из источников, которые я видел, не смог объяснить это так, чтобы я мог следить.

6 ответов

Решение

Из сообщения в блоге Javarevisited:

Вот несколько важных различий между статическим и динамическим связыванием:

  1. Статическое связывание в Java происходит во время компиляции, в то время как динамическое связывание происходит во время выполнения.
  2. private, final а также static методы и переменные используют статическую привязку и связаны компилятором, в то время как виртуальные методы связаны во время выполнения на основе объекта времени выполнения.
  3. Статическое связывание использует Type (class в Java) информация для привязки, в то время как динамическая привязка использует объект для разрешения привязки.
  4. Перегруженные методы связываются с использованием статического связывания, а переопределенные методы связываются с использованием динамического связывания во время выполнения.

Вот пример, который поможет вам понять как статическое, так и динамическое связывание в Java.

Пример статического связывания в Java

public class StaticBindingTest {  
    public static void main(String args[]) {
        Collection c = new HashSet();
        StaticBindingTest et = new StaticBindingTest();
        et.sort(c);
    }
    //overloaded method takes Collection argument
    public Collection sort(Collection c) {
        System.out.println("Inside Collection sort method");
        return c;
    }
    //another overloaded method which takes HashSet argument which is sub class
    public Collection sort(HashSet hs) {
        System.out.println("Inside HashSet sort method");
        return hs;
    }
}

Вывод: метод сортировки внутри коллекции

Пример динамического связывания в Java

public class DynamicBindingTest {   
    public static void main(String args[]) {
        Vehicle vehicle = new Car(); //here Type is vehicle but object will be Car
        vehicle.start(); //Car's start called because start() is overridden method
    }
}

class Vehicle {
    public void start() {
        System.out.println("Inside start method of Vehicle");
    }
}

class Car extends Vehicle {
    @Override
    public void start() {
        System.out.println("Inside start method of Car");
    }
}

Вывод: метод внутреннего запуска автомобиля

Подключение вызова метода к телу метода называется Binding. Как сказал Маулик, "статическое связывание использует информацию Type(Class in Java) для привязки, а динамическое связывание использует Object для разрешения привязки". Итак, этот код:

public class Animal {
    void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {
        Animal a = new Dog();
        a.eat(); // prints >> dog is eating...
    }

    @Override
    void eat() {
        System.out.println("dog is eating...");
    }
}

Будет производить результат: собака ест... потому что она использует ссылку на объект, чтобы найти, какой метод использовать. Если мы изменим приведенный выше код на это:

class Animal {
    static void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {

        Animal a = new Dog();
        a.eat(); // prints >> animal is eating...

    }

    static void eat() {
        System.out.println("dog is eating...");
    }
}

Он выдаст: animal is eat... потому что это статический метод, поэтому он использует Type (в данном случае Animal) для определения, какой статический метод вызывать. Помимо статических методов частные и конечные методы используют один и тот же подход.

Ну, чтобы понять, как на самом деле работает статическое и динамическое связывание? или как они идентифицируются компилятором и 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 каждая ссылочная переменная содержит два скрытых указателя

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

Таким образом, все ссылки на объекты косвенно содержат ссылку на таблицу, которая содержит все ссылки на методы этого объекта. Java заимствовал эту концепцию из C++, и эта таблица называется виртуальной таблицей (vtable).

Vtable - это структура, похожая на массив, которая содержит имена виртуальных методов и их ссылки на индексы массива. JVM создает только одну виртуальную таблицу на класс, когда загружает класс в память.

Поэтому всякий раз, когда JVM сталкивается с invokevirtual набор команд, он проверяет vtable этого класса для ссылки на метод и вызывает конкретный метод, который в нашем случае является методом из объекта, а не ссылки.

Поскольку все это разрешается только во время выполнения и во время выполнения, JVM узнает, какой метод вызывать, поэтому переопределение метода называется динамическим полиморфизмом или просто полиморфизмом или динамическим связыванием.

Вы можете прочитать его более подробно в моей статье Как JVM обрабатывает перегрузку и переопределение метода внутри системы.

Компилятор знает только, что тип "a" Animal; это происходит во время компиляции, из-за чего оно называется статическим связыванием (перегрузка метода). Но если это динамическое связывание, то это вызвало бы Dog метод класса. Вот пример динамического связывания.

public class DynamicBindingTest {

    public static void main(String args[]) {
        Animal a= new Dog(); //here Type is Animal but object will be Dog
        a.eat();       //Dog's eat called because eat() is overridden method
    }
}

class Animal {

    public void eat() {
        System.out.println("Inside eat method of Animal");
    }
}

class Dog extends Animal {

    @Override
    public void eat() {
        System.out.println("Inside eat method of Dog");
    }
}

Вывод: метод внутри собаки

Существует три основных различия между статическим и динамическим связыванием при разработке компиляторов и тем, как переменные и процедуры передаются в среду выполнения. Эти различия заключаются в следующем:

Статическое связывание. В статическом связывании обсуждаются три следующие проблемы:

  • Определение процедуры

  • Объявление имени (переменная и т. Д.)

  • Объем декларации

Динамическое связывание. При динамическом связывании встречаются три проблемы:

  • Активация процедуры

  • Привязка имени

  • Время жизни привязки

С помощью статического метода в родительском и дочернем классах: статическое связывание

public class test1 {   
    public static void main(String args[]) {
        parent pc = new child(); 
        pc.start(); 
    }
}

class parent {
    static public void start() {
        System.out.println("Inside start method of parent");
    }
}

class child extends parent {

    static public void start() {
        System.out.println("Inside start method of child");
    }
}

// Output => Inside start method of parent

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

public class test1 {   
    public static void main(String args[]) {
        parent pc = new child();
        pc.start(); 
    }
}

class parent {
   public void start() {
        System.out.println("Inside start method of parent");
    }
}

class child extends parent {

   public void start() {
        System.out.println("Inside start method of child");
    }
}

// Output => Inside start method of child

Все ответы здесь верны, но я хочу добавить то, чего не хватает. когда вы переопределяете статический метод, похоже, что мы переопределяем его, но на самом деле это не переопределение метода. Вместо этого это называется скрытием метода. Статические методы нельзя переопределить в Java.

Посмотрите на пример ниже:

class Animal {
    static void eat() {
        System.out.println("animal is eating...");
    }
}

class Dog extends Animal {

    public static void main(String args[]) {

        Animal a = new Dog();
        a.eat(); // prints >> animal is eating...

    }

    static void eat() {
        System.out.println("dog is eating...");
    }
}

При динамическом связывании метод вызывается в зависимости от типа ссылки, а не типа объекта, который содержит ссылочная переменная. Здесь происходит статическое связывание, поскольку скрытие метода не является динамическим полиморфизмом. Если вы удалите ключевое слово static перед eat() и сделаете его нестатическим методом, он покажет вам динамический полиморфизм, а не скрытие метода.

я нашел ссылку ниже, чтобы поддержать свой ответ:https://youtu.be/tNgZpn7AeP0

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



class Dainamic{

    void run2(){
        System.out.println("dainamic_binding");
    }

}


public class StaticDainamicBinding extends Dainamic {

    void run(){
        System.out.println("static_binding");
    }

    @Override
    void run2() {
        super.run2();
    }

    public static void main(String[] args) {
        StaticDainamicBinding st_vs_dai = new StaticDainamicBinding();
        st_vs_dai.run();
        st_vs_dai.run2();
    }

}

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

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

Другие вопросы по тегам