Static vs. Dynamiczne wiązanie w Javie


103

Obecnie wykonuję zadanie dla jednej z moich klas i muszę w nim podać przykłady, używając składni Javy, statycznego i dynamicznego wiązania .

Rozumiem podstawową koncepcję, że statyczne wiązanie ma miejsce w czasie kompilacji, a dynamiczne wiązanie ma miejsce w czasie wykonywania, ale nie mogę dowiedzieć się, jak właściwie działają.

Znalazłem przykład statycznego wiązania online, który daje ten przykład:

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

I że to wypisze „zwierzę je”, ponieważ wywołanie callEatużywa statycznego wiązania , ale nie jestem pewien, dlaczego jest to uważane za statyczne wiązanie.

Jak dotąd żadnemu ze źródeł, które widziałem, nie udało się wyjaśnić tego w sposób, który mogę śledzić.



1
Zauważ, że istnieje kilka różnych pojęć określanych jako „wiązanie”. W tym konkretnym przypadku, dla tego typu powiązania (które obejmuje wybór pomiędzy podobnymi, ale nie identycznymi „sygnaturami” metody) kompilator (który podejmuje decyzje „statyczne”, ponieważ nie zmieniają się one w czasie wykonywania) decyduje, że parametr jest „Zwierzę”, ponieważ jest to (statyczny) typ zmiennej „a”.
Hot Licks

(Są języki, w których wybór określonej sygnatury metody zostałby pozostawiony do czasu wykonania i wybrano callEat (Dog).)
Hot Licks

Odpowiedzi:


115

Z wpisu na blogu Javarevisited :

Oto kilka ważnych różnic między wiązaniem statycznym i dynamicznym:

  1. Statyczne powiązanie w Javie występuje w czasie kompilacji, podczas gdy dynamiczne wiązanie występuje w czasie wykonywania.
  2. private, finala staticmetody i zmienne używają statycznego wiązania i są łączone przez kompilator, podczas gdy metody wirtualne są łączone w czasie wykonywania na podstawie obiektu środowiska wykonawczego.
  3. Wiązanie statyczne używa Type( classw języku Java) informacji do wiązania, podczas gdy wiązanie dynamiczne używa obiektu do rozwiązywania powiązania.
  4. Przeciążone metody są łączone przy użyciu powiązania statycznego, podczas gdy metody zastępowane są łączone przy użyciu dynamicznego wiązania w czasie wykonywania.

Oto przykład, który pomoże ci zrozumieć zarówno statyczne, jak i dynamiczne wiązanie w Javie.

Przykład statycznego wiązania w Javie

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

Dane wyjściowe : metoda sortowania kolekcji wewnątrz

Przykład dynamicznego wiązania w Javie

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

Dane wyjściowe: metoda rozruchu wewnętrznego samochodu



11
Nadal nie rozumiem różnicy,
technazi

9
Statyczne wiązanie @technazi po prostu patrzy na typ (co zawsze jest przed równa się, np. Collection c = new HashSet (); więc będzie postrzegane jako obiekt kolekcji, gdy w rzeczywistości jest to hashset). Wiązanie dyanmiczne bierze pod uwagę rzeczywisty obiekt (co jest po równych, więc faktycznie rozpoznaje swój zestaw HashSet).
Mark

22

Łączenie wywołania metody z treścią metody jest nazywane Binding. Jak powiedział Maulik, „Wiązanie statyczne wykorzystuje informacje o typie (klasa w Javie) do wiązania, podczas gdy wiązanie dynamiczne wykorzystuje obiekt do rozwiązywania powiązań”. Więc ten kod:

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

Daje wynik: pies je ... ponieważ używa odniesienia do obiektu, aby znaleźć metodę do zastosowania. Jeśli zmienimy powyższy kod na ten:

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

Wytworzy: zwierzę je ... ponieważ jest to metoda statyczna, więc używa Type (w tym przypadku Animal) do rozstrzygnięcia, którą metodę statyczną należy wywołać. Oprócz metod statycznych metody prywatne i ostateczne wykorzystują to samo podejście.


1
Dlaczego Java nie może wywnioskować, że w czasie kompilacji afaktycznie jest to plik Dog?
Minh Nghĩa

4

Kompilator wie tylko, że typ „a” to Animal; dzieje się to w czasie kompilacji, przez co nazywa się to wiązaniem statycznym (przeciążenie metod). Ale jeśli jest to wiązanie dynamiczne, wywoła Dogmetodę klasy. Oto przykład dynamicznego wiązania.

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

Wyjście: Wewnątrz metoda jedzenia psa


Czy nie spowodowałoby to błędu kompilacji, na przykład „Nie można odwołać się do niestatycznej klasy / metody z kontekstu statycznego”? Zawsze jestem z tym zdezorientowany, mając na uwadze, że main jest statyczny. Z góry dziękuję.
Amnor,

4

Cóż, aby zrozumieć, jak statyczne i dynamiczne wiązanie faktycznie działa ? lub jak są identyfikowane przez kompilator i JVM?

Weźmy poniższy przykład, gdzie Mammaljest klasą nadrzędną, która ma metodę speak()i Humanklasę rozszerza Mammal, zastępuje speak()metodę, a następnie ponownie ją przeciąża 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
    }
}

Kiedy kompilujemy powyższy kod i próbujemy spojrzeć na kod bajtowy za pomocą javap -verbose OverridingInternalExample, widzimy, że kompilator generuje stałą tabelę, w której przypisuje kody całkowite do każdego wywołania metody i kodu bajtowego dla programu, który wyodrębniłem i włączyłem do samego programu ( zobacz komentarze poniżej każdego wywołania metody)

Program Bytecode

Patrząc na powyższym kodzie widzimy, że bytecodes o humanMammal.speak(), human.speak()i human.speak("Hindi")są zupełnie różne ( invokevirtual #4, invokevirtual #7, invokevirtual #9) ponieważ kompilator jest w stanie rozróżniać między nimi na podstawie listy argumentów i odniesienie klasy. Ponieważ wszystko to jest rozwiązywane statycznie w czasie kompilacji, dlatego przeciążanie metod jest znane jako statyczny polimorfizm lub statyczne wiązanie .

Ale kod bajtowy dla anyMammal.speak()i humanMammal.speak()jest taki sam ( invokevirtual #4), ponieważ zgodnie z kompilatorem obie metody są wywoływane w Mammalodwołaniu.

Więc teraz pojawia się pytanie, czy oba wywołania metod mają ten sam kod bajtowy, to skąd JVM wie, którą metodę wywołać?

Cóż, odpowiedź jest ukryta w samym kodzie bajtowym i jest to invokevirtualzestaw instrukcji. JVM używainvokevirtual instrukcji do wywołania Java odpowiednika metod wirtualnych C ++. W C ++, jeśli chcemy przesłonić jedną metodę w innej klasie, musimy zadeklarować ją jako wirtualną, ale w Javie wszystkie metody są domyślnie wirtualne, ponieważ możemy przesłonić każdą metodę w klasie potomnej (z wyjątkiem metod prywatnych, końcowych i statycznych).

W Javie każda zmienna referencyjna zawiera dwa ukryte wskaźniki

  1. Wskaźnik do tabeli, która ponownie zawiera metody obiektu i wskaźnik do obiektu Class. np. [speak (), speak (String) Class object]
  2. Wskaźnik do pamięci przydzielonej na stercie dla danych tego obiektu, np. Wartości zmiennych instancji.

Zatem wszystkie odwołania do obiektu pośrednio przechowują odwołanie do tabeli, która zawiera wszystkie odwołania do metod tego obiektu. Java zapożyczyła tę koncepcję z C ++ i ta tabela jest znana jako tabela wirtualna (vtable).

Tabela vtable jest strukturą przypominającą tablicę, która przechowuje nazwy metod wirtualnych i ich odniesienia w indeksach tablic. JVM tworzy tylko jedną tabelę vtable na klasę, kiedy ładuje klasę do pamięci.

Więc za każdym razem, gdy JVM napotyka invokevirtualzestaw instrukcji, sprawdza tabelę vtable tej klasy pod kątem odwołania do metody i wywołuje określoną metodę, która w naszym przypadku jest metodą z obiektu, a nie z odwołania.

Ponieważ wszystko to jest rozwiązywane tylko w czasie wykonywania i podczas wykonywania, JVM dowiaduje się, którą metodę wywołać, dlatego przesłanianie metody jest znane jako dynamiczny polimorfizm lub po prostu polimorfizm lub dynamiczne wiązanie .

Możesz przeczytać więcej szczegółów w moim artykule Jak JVM obsługuje wewnętrzne przeciążanie i zastępowanie metod .


2

Istnieją trzy główne różnice między wiązaniem statycznym i dynamicznym podczas projektowania kompilatorów oraz sposobu przenoszenia zmiennych i procedur do środowiska uruchomieniowego środowiska . Te różnice są następujące:

Wiązanie statyczne : W wiązaniu statycznym omówiono trzy następujące problemy:

  • Definicja procedury

  • Deklaracja nazwy (zmienna itp.)

  • Zakres deklaracji

Dynamiczne wiązanie : trzy problemy, które występują w dynamicznym wiązaniu, są następujące:

  • Aktywacja procedury

  • Wiązanie nazwiska

  • Żywotność oprawy


1

Z metodą statyczną w klasie nadrzędnej i podrzędnej: 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

Dynamiczne wiązanie:

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

Wszystkie odpowiedzi są poprawne, ale chcę dodać coś, czego brakuje. kiedy zastępujesz metodę statyczną, wygląda na to, że ją zastępujemy, ale w rzeczywistości nie jest to przesłanianie metody. Zamiast tego nazywa się to ukrywaniem metody. W Javie nie można przesłonić metod statycznych .

Spójrz na poniższy przykład:

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

W dynamicznym wiązaniu metoda jest wywoływana w zależności od typu odwołania, a nie od typu obiektu, który przechowuje zmienna referencyjna. Tutaj statyczne wiązanie ma miejsce, ponieważ ukrywanie metody nie jest dynamicznym polimorfizmem. Jeśli usuniesz statyczne słowo kluczowe przed eat () i sprawisz, że będzie to metoda niestatyczna, to pokaże ci dynamiczny polimorfizm, a nie ukrywanie metod.

Znalazłem poniższy link, aby wesprzeć moją odpowiedź: https://youtu.be/tNgZpn7AeP0


0

W przypadku statycznego wiązania typu obiektu określanego w czasie kompilacji, podczas gdy w dynamicznym wiązaniu typ obiektu jest określany w czasie wykonywania.



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

Ponieważ kompilator zna powiązanie w czasie kompilacji. Jeśli na przykład wywołasz metodę w interfejsie, kompilator nie będzie mógł tego wiedzieć, a powiązanie zostanie rozwiązane w czasie wykonywania, ponieważ rzeczywisty obiekt, na którym została wywołana metoda, może być jednym z kilku. Dlatego jest to środowisko wykonawcze lub dynamiczne wiązanie.

Twoje wywołanie jest powiązane z klasą Animal w czasie kompilacji, ponieważ określono typ. Gdybyś przekazał tę zmienną do innej metody w innym miejscu, nikt nie wiedziałby (poza tobą, ponieważ ją napisałeś), jaka to byłaby klasa. Jedyną wskazówką jest zadeklarowany typ zwierzęcia.


1
Nie prawda. Kompilator podjąłby dokładnie taką samą decyzję, gdybyś wykonywał wywołanie interfejsu.
Hot Licks

@HotLicks Dokładnie taka sama decyzja jak co? Jeśli kompilujesz klasę w celu wywołania metody foo (String str) w interfejsie, kompilator nie może wiedzieć w czasie kompilacji, dla której klasy powinna zostać wywołana metoda foo (String str). Tylko w czasie wykonywania wywołanie metody można powiązać z określoną implementacją klasy.
Aaron

Jednak nadal występowałoby statyczne wiązanie z określoną sygnaturą metody. Kompilator nadal wybierałby callEat (Animal) zamiast callEat (Dog).
Hot Licks

@HotLicks Jasne, ale to nie jest pytanie, na które odpowiedziałem. Być może wprowadzało mnie w błąd: DI porównało to do wywoływania interfejsu w celu podkreślenia, że ​​w czasie kompilacji kompilator nie może wiedzieć, czy faktycznie utworzyłeś inną podklasę / implementację, czy nie.
Aaron

Właściwie w czasie kompilacji kompilator może (w tym przypadku) bardzo łatwo wiedzieć, że „a” to pies. W rzeczywistości, trzeba będzie dołożyć wszelkich starań, aby o tym „zapomnieć”.
Hot Licks
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.