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 Human
classe 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)
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 Mammal
riferimento.
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 invokevirtual
set di istruzioni. JVM utilizza l' invokevirtual
istruzione 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
- 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]
- 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 .