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


103

В настоящее время я выполняю задание для одного из моих классов, и в нем я должен привести примеры статической и динамической привязки с использованием синтаксиса 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использует статическую привязку , но я не уверен, почему это считается статической привязкой.

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



1
Обратите внимание, что существует несколько различных концепций, называемых «привязкой». В данном конкретном случае для этого типа привязки (который включает выбор между похожими, но не идентичными "сигнатурами" метода) компилятор (который принимает "статические" решения, поскольку они не меняются во время выполнения) решает, что параметр является «Животное», потому что это (статический) тип переменной «а».
Hot Licks

(Существуют языки, в которых выбор конкретной сигнатуры метода будет оставлен до времени выполнения и будет выбран callEat (Dog).)
Hot Licks

Ответы:


115

Из сообщения в блоге, которое посетил Ява :

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

  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");
    }
}

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



11
Я до сих пор не понимаю разницы,
technazi

9
Статическая привязка @technazi просто смотрит на тип (что когда-либо было до равенства, например, Collection c = new HashSet (); поэтому он будет рассматриваться как просто объект коллекции, когда фактически это хэш-набор). Дианмическая привязка принимает во внимание фактический объект (что после равенства, поэтому он фактически распознает его HashSet).
Марк

22

Подключение вызова метода к телу метода называется привязкой. Как сказал Маулик, «статическая привязка использует информацию типа (класс в Java) для привязки, в то время как динамическая привязка использует объект для разрешения привязки». Итак, этот код:

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...");
    }
}

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


1
Почему Java не может сделать вывод, что aэто на самом деле Dogво время компиляции?
Минь Нгха,

4

Компилятор знает только, что тип "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");
    }
}

Продукт: Метод Собаки изнутри.


Разве это не вызовет ошибку компиляции, например «Невозможно ссылаться на нестатический класс / метод из статического контекста»? Меня это всегда смущает, учитывая, что main статичен. Заранее спасибо.
Амнор,

4

Ну чтобы понять, как на самом деле работает статическая и динамическая привязка ? или как они идентифицируются компилятором и 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. Указатель на таблицу, которая снова содержит методы объекта и указатель на объект класса. например [Speak (), Speak (String) Объект класса]
  2. Указатель на память, выделенную в куче для данных этого объекта, например значений переменных экземпляра.

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

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

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

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

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


2

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

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

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

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

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

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

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

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

  • Срок службы привязки


1

С помощью статического метода в родительском и дочернем классе: Static Binding

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

0

Все ответы здесь верны, но я хочу добавить то, чего не хватает. когда вы переопределяете статический метод, похоже, что мы переопределяем его, но на самом деле это не переопределение метода. Вместо этого это называется скрытием метода. Статические методы нельзя переопределить в 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


0

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



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();
    }

}

-3

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

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


1
Не правда. Компилятор принял бы точно такое же решение, если бы вы выполняли вызов интерфейса.
Hot Licks

@HotLicks Точное такое же решение, что и что? Если вы компилируете класс для вызова метода foo (String str) в интерфейсе, компилятор не может знать во время компиляции, для какого класса следует вызвать метод foo (String str). Только во время выполнения вызов метода может быть привязан к конкретной реализации класса.
Аарон

Но статическая привязка к конкретной сигнатуре метода все равно будет иметь место. Компилятор все равно выберет callEat (Animal) вместо callEat (Dog).
Hot Licks

@HotLicks Конечно, но я ответил не на этот вопрос. Возможно, это вводило меня в заблуждение: Д. Я сравнил это с вызовом интерфейса, чтобы подчеркнуть, что во время компиляции компилятор не может знать, действительно ли вы создали экземпляр другого подкласса / реализации или нет.
Аарон

На самом деле, во время компиляции компилятор может (в этом случае) очень легко узнать, что «a» - это Dog. На самом деле, скорее всего, придется пойти на многое, чтобы «забыть» об этом.
Hot Licks
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.