Statico vs. Associazione dinamica in Java


103

Attualmente sto svolgendo un incarico per una delle mie classi e in essa devo fornire esempi, utilizzando la sintassi Java, di binding statico e dinamico .

Comprendo il concetto di base, che l'associazione statica avviene in fase di compilazione e l'associazione dinamica avviene in fase di esecuzione, ma non riesco a capire come funzionano effettivamente in modo specifico.

Ho trovato un esempio di associazione statica online che fornisce questo esempio:

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

E che questo stamperebbe "l'animale sta mangiando" perché la chiamata a callEatusa la rilegatura statica , ma non sono sicuro del motivo per cui questa è considerata una rilegatura statica.

Finora nessuna delle fonti che ho visto è riuscita a spiegarlo in un modo che io possa seguire.



1
Notare che esistono diversi concetti diversi che vengono definiti "vincolanti". In questo caso particolare, per questo tipo di associazione (che implica una scelta tra "firme" di metodo simili ma non identiche) il compilatore (che prende decisioni "statiche", poiché non variano in fase di esecuzione) decide che il parametro è un "Animale" perché questo è il tipo (statico) della variabile "a".
Hot Licks

(Ci sono lingue in cui la scelta della firma del metodo specifico sarebbe lasciata fino al runtime e callEat (Dog) verrebbe selezionato.)
Hot Licks

Risposte:


115

Dal post del blog visitato da Javare :

Ecco alcune importanti differenze tra binding statico e dinamico:

  1. Il binding statico in Java si verifica durante la fase di compilazione mentre il binding dinamico si verifica durante il runtime.
  2. private, finale i staticmetodi e le variabili utilizzano l'associazione statica e sono collegati dal compilatore mentre i metodi virtuali sono collegati durante il runtime in base all'oggetto runtime.
  3. L'associazione statica utilizza Type( classin Java) le informazioni per l'associazione mentre l'associazione dinamica utilizza l'oggetto per risolvere l'associazione.
  4. I metodi sovraccaricati vengono collegati utilizzando l'associazione statica, mentre i metodi sostituiti vengono collegati utilizzando l'associazione dinamica in fase di esecuzione.

Ecco un esempio che ti aiuterà a comprendere sia l'associazione statica che quella dinamica in Java.

Esempio di binding statico in 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;
    }
}

Output : metodo di ordinamento Inside Collection

Esempio di associazione dinamica in 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");
    }
}

Uscita: metodo di avvio interno dell'auto



11
Continuo a non capire la differenza,
technazi

9
L'associazione statica @technazi guarda solo al tipo (cosa mai è prima dell'uguale, ad esempio Collection c = new HashSet (); quindi sarà visto come un oggetto di raccolta quando infatti è un hashset). L'associazione dinmica tiene conto dell'oggetto reale (cosa c'è dopo gli uguali in modo che riconosca effettivamente il suo HashSet).
Marco

22

La connessione di una chiamata al metodo al corpo del metodo è nota come Binding. Come ha detto Maulik, "il binding statico utilizza le informazioni Type (Class in Java) per il binding mentre il binding dinamico utilizza Object per risolvere il binding". Quindi questo codice:

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

Produrrà il risultato: il cane sta mangiando ... perché utilizza il riferimento all'oggetto per trovare il metodo da utilizzare. Se cambiamo il codice sopra in questo:

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

Produrrà: animal sta mangiando ... perché è un metodo statico, quindi utilizza Type (in questo caso Animal) per risolvere quale metodo statico chiamare. Oltre ai metodi statici, i metodi privati ​​e finali utilizzano lo stesso approccio.


1
Perché Java non può dedurre che in arealtà è un Dogmomento della compilazione?
Minh Nghĩa

4

Il compilatore sa solo che il tipo di "a" è Animal; questo accade in fase di compilazione, per questo viene chiamato binding statico (Method overloading). Ma se è un'associazione dinamica, chiamerà il Dogmetodo della classe. Ecco un esempio di associazione dinamica.

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

Risultato: metodo di mangiare dentro il cane


Questo non genererebbe un errore di compilazione come "Impossibile fare riferimento a una classe / metodo non statico da un contesto statico"? Sono sempre confuso con questo, avendo in mente che il main è statico. Grazie in anticipo.
Amnor

4

Bene, per capire come funziona effettivamente il binding statico e dinamico ? o come vengono identificati dal compilatore e dalla JVM?

Prendiamo l'esempio di seguito in cui Mammalè una classe genitore che ha un metodo speak()e la Humanclasse si estende Mammal, sovrascrive il speak()metodo e quindi lo sovraccarica di nuovo con 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
    }
}

Quando compiliamo il codice sopra e proviamo a guardare il bytecode usando javap -verbose OverridingInternalExample, possiamo vedere che il compilatore genera una tabella costante in cui assegna codici interi a ogni chiamata di metodo e codice byte per il programma che ho estratto e incluso nel programma stesso ( vedere i commenti sotto ogni chiamata di metodo)

Bytecode del programma

Guardando il codice sopra possiamo vedere che i bytecode di humanMammal.speak() , human.speak()e human.speak("Hindi")sono totalmente diversi ( invokevirtual #4, invokevirtual #7, invokevirtual #9) perché il compilatore è in grado di distinguere tra di loro in base alla lista degli argomenti e di riferimento di classe. Poiché tutto questo viene risolto staticamente in fase di compilazione, ecco perché il sovraccarico del metodo è noto come polimorfismo statico o binding statico .

Ma bytecode per anyMammal.speak()e humanMammal.speak()è uguale ( invokevirtual #4) perché secondo il compilatore entrambi i metodi sono chiamati su Mammalriferimento.

Quindi ora viene la domanda se entrambe le chiamate ai metodi hanno lo stesso bytecode, allora come fa JVM a sapere quale metodo chiamare?

Ebbene, la risposta è nascosta nel bytecode stesso ed è il invokevirtualset di istruzioni. JVM utilizza l' invokevirtualistruzione per richiamare l'equivalente Java dei metodi virtuali C ++. In C ++ se vogliamo sovrascrivere un metodo in un'altra classe dobbiamo dichiararlo come virtuale, ma in Java, tutti i metodi sono virtuali per impostazione predefinita perché possiamo sovrascrivere ogni metodo nella classe figlia (eccetto metodi privati, finali e statici).

In Java, ogni variabile di riferimento contiene due puntatori nascosti

  1. Un puntatore a una tabella che contiene nuovamente i metodi dell'oggetto e un puntatore all'oggetto Class. ad es. [speak (), speak (String) oggetto classe]
  2. Un puntatore alla memoria allocata sull'heap per i dati di quell'oggetto, ad esempio i valori delle variabili di istanza.

Quindi tutti i riferimenti agli oggetti contengono indirettamente un riferimento a una tabella che contiene tutti i riferimenti al metodo di quell'oggetto. Java ha preso in prestito questo concetto da C ++ e questa tabella è nota come tabella virtuale (vtable).

Una tabella v è una struttura simile ad un array che contiene i nomi dei metodi virtuali ei loro riferimenti sugli indici degli array. JVM crea solo una tabella vtable per classe quando carica la classe in memoria.

Quindi ogni volta che JVM incontra un file invokevirtual set di istruzioni, controlla la tabella vtable di quella classe per il riferimento al metodo e richiama il metodo specifico che nel nostro caso è il metodo di un oggetto e non il riferimento.

Poiché tutto questo viene risolto solo in fase di esecuzione e in fase di esecuzione JVM viene a sapere quale metodo invocare, ecco perché il Method Overriding è noto come Dynamic Polymorphism o semplicemente Polymorphism o Dynamic Binding .

Puoi leggere maggiori dettagli nel mio articolo In che modo JVM gestisce internamente l'overload e l'override del metodo .


2

Esistono tre differenze principali tra l'associazione statica e dinamica durante la progettazione dei compilatori e il modo in cui le variabili e le procedure vengono trasferite all'ambiente di runtime . Queste differenze sono le seguenti:

Binding statico : nel binding statico vengono discussi tre problemi seguenti:

  • Definizione di una procedura

  • Dichiarazione di un nome (variabile, ecc.)

  • Scopo della dichiarazione

Associazione dinamica : tre problemi che si incontrano nell'associazione dinamica sono i seguenti:

  • Attivazione di una procedura

  • Legatura di un nome

  • Durata di una rilegatura


1

Con il metodo statico nella classe genitore e figlio: 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

Associazione dinamica:

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

Tutte le risposte qui sono corrette ma voglio aggiungere qualcosa che manca. quando si sovrascrive un metodo statico, sembra che lo stiamo sovrascrivendo ma in realtà non è il metodo che sovrascrive. Invece si chiama metodo nascosto. I metodi statici non possono essere sovrascritti in Java.

Guarda l'esempio sotto:

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

Nel binding dinamico, il metodo viene chiamato a seconda del tipo di riferimento e non del tipo di oggetto che la variabile di riferimento sta tenendo. Qui il binding statico si verifica perché il metodo che nasconde non è un polimorfismo dinamico. Se rimuovi la parola chiave statica davanti a eat () e la rendi un metodo non statico, ti mostrerà il polimorfismo dinamico e non il metodo nascosto.

ho trovato il link sottostante per supportare la mia risposta: https://youtu.be/tNgZpn7AeP0


0

Nel caso del binding statico il tipo di oggetto è determinato in fase di compilazione mentre nel tipo di binding dinamico dell'oggetto è determinato in fase di esecuzione.



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

Perché il compilatore conosce l'associazione in fase di compilazione. Se si richiama un metodo su un'interfaccia, ad esempio, il compilatore non può saperlo e l'associazione viene risolta in fase di runtime perché l'oggetto effettivo su cui è richiamato un metodo potrebbe essere uno dei tanti. Quindi questo è il runtime o l'associazione dinamica.

La tua chiamata è vincolata alla classe Animal in fase di compilazione perché hai specificato il tipo. Se passassi quella variabile in un altro metodo da qualche altra parte, nessuno saprebbe (a parte te perché l'hai scritta) quale classe sarebbe effettivamente. L'unico indizio è il tipo dichiarato di Animale.


1
Non vero. Il compilatore prenderebbe la stessa identica decisione se stessi facendo una chiamata su un'interfaccia.
Hot Licks

@HotLicks Esatta stessa decisione di cosa? Se si compila una classe per invocare un metodo foo (String str) su un'interfaccia, il compilatore non può sapere in fase di compilazione su quale classe deve essere invocato il metodo foo (String str). Solo in fase di esecuzione il richiamo del metodo può essere associato a una particolare implementazione della classe.
Aaron

Ma si verificherebbe ancora l'associazione statica alla firma del metodo specifico. Il compilatore seleziona comunque callEat (Animal) su callEat (Dog).
Hot Licks

@HotLicks Certo, ma non è questa la domanda a cui ho risposto. Forse è stato fuorviante da parte mia: DI l'ha confrontato con l'invocazione su un'interfaccia per evidenziare che in fase di compilazione il compilatore non può sapere se hai effettivamente istanziato una sottoclasse / implementazione diversa o meno.
Aaron

In realtà, in fase di compilazione il compilatore può (in questo caso) molto facilmente sapere che "a" è un cane. In effetti, probabilmente dovrà fare di tutto per "dimenticarlo".
Hot Licks
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.