Статичні проти Динамічне прив'язка в 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

Відповіді:


114

З переглянутого повідомлення в блозі Java :

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

  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
Я все ще не розумію різниці,
техназі

9
Статичне прив'язування @technazi просто дивиться на тип (що є до того, як дорівнює, наприклад, Collection c = new HashSet (); тому воно буде розглядатися як просто об'єкт колекції, якщо в дійсності це хешсет). Дианмічне прив'язування враховує фактичний об'єкт (що після рівного, тому він фактично розпізнає свій HashSet).
Позначте

22

Підключення виклику методу до тіла методу відоме як Binding. Як сказав Маулік, "статичне прив'язування використовує інформацію типу (класу в 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часом компіляції?
Minh Nghĩa

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

Результат: Всередині їдять метод собаки


Чи не спричинить це помилка компіляції, наприклад "Не вдається посилатись на нестатичний клас / метод із статичного контексту"? Мене це завжди плутає, маючи на увазі, що основне - статичне. Заздалегідь спасибі.
Амнор

3

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

  1. Вказівник на таблицю, яка знову містить методи об'єкта та вказівник на об'єкт Class. наприклад [speak (), speak (String) Class object]
  2. Вказівник на пам'ять, виділену в купі для даних цього об'єкта, наприклад значення змінних екземпляра.

Отже, усі посилання на об’єкти опосередковано містять посилання на таблицю, яка містить усі посилання на методи цього об’єкта. Java запозичила цю концепцію у C ++, і ця таблиця відома як віртуальна таблиця (vtable).

Vtable - це масивна структура, яка містить імена віртуальних методів та їх посилання на індекси масивів. JVM створює лише одну vtable на клас, коли завантажує клас в пам’ять.

Тому щоразу, коли 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...");
    }
}

У динамічному прив'язуванні метод викликається залежно від типу посилання, а не від типу об'єкта, який містить посилальна змінна. Тут статичне прив'язування відбувається, оскільки приховування методу не є динамічним поліморфізмом. Якщо ви видалите статичне ключове слово перед 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 під час компіляції, оскільки ви вказали тип. Якби ви передали цю змінну в інший метод в іншому місці, ніхто б не знав (крім вас, тому що ви її написали), яким це буде фактичний клас. Єдиною підказкою є заявлений тип Тварини.


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

@HotLicks Точно таке ж рішення, як і що? Якщо ви компілюєте клас для виклику методу foo (String str) на інтерфейсі, компілятор не може знати під час компіляції, до якого класу слід викликати метод foo (String str). Лише під час виконання виклик методу може бути прив'язаний до певної реалізації класу.
Аарон

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

@HotLicks Звичайно, але це не питання, на яке я відповів. Можливо, це вводило мене в оману: DI порівняв це із викликом інтерфейсу, щоб підкреслити, що під час компіляції компілятор не може знати, чи ви насправді створили інстанцію іншого підкласу / реалізації чи ні.
Аарон

Насправді, під час компіляції компілятор може (у даному випадку) дуже легко міг знати, що "а" - це Собака. Насправді це, мабуть, має докласти певних зусиль, щоб "забути" це.
Hot Licks
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.