Eh bien pour comprendre comment la liaison statique et dynamique fonctionne réellement ? ou comment elles sont identifiées par le compilateur et la JVM ?
Prenons l'exemple ci-dessous où Mammal
est une classe parent qui a une méthode speak()
et la classe Human
étend la classe Mammal
, substitue la méthode speak()
et la surcharge ensuite avec 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 "Classe Humaine"; }
}
// Code ci-dessous contient la sortie et le bytecode des appels de méthode
public static void main(String[] args) {
Mammal anyMammal = new Mammal();
anyMammal.speak(); // Sortie - ohlllalalalalalaoaoaoa
// 10: invokevirtual #4 // Méthode org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Mammal humanMammal = new Human();
humanMammal.speak(); // Sortie - Hello
// 23: invokevirtual #4 // Méthode org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Human human = new Human();
human.speak(); // Sortie - Hello
// 36: invokevirtual #7 // Méthode org/programming/mitra/exercises/OverridingInternalExample$Human.speak:()V
human.speak("Hindi"); // Sortie - Namaste
// 42: invokevirtual #9 // Méthode org/programming/mitra/exercises/OverridingInternalExample$Human.speak:(Ljava/lang/String;)V
}
}
Lorsque nous compilons le code ci-dessus et essayons de regarder le bytecode en utilisant javap -verbose OverridingInternalExample
, nous pouvons voir que le compilateur génère une table constante où il attribue des codes entiers à chaque appel de méthode et du bytecode pour le programme que j'ai extrait et inclus dans le programme lui-même (voir les commentaires après chaque appel de méthode)
En regardant le code ci-dessus, nous pouvons voir que les bytecodes de humanMammal.speak()
, human.speak()
et human.speak("Hindi")
sont totalement différents (invokevirtual #4
, invokevirtual #7
, invokevirtual #9
) parce que le compilateur est capable de les différencier en fonction de la liste d'arguments et de la référence de classe. Parce que tout cela est résolu au moment de la compilation statiquement c'est pourquoi la surcharge de méthode est connue sous le nom de Polymorphisme Statique ou Liaison Statique.
Mais le bytecode pour anyMammal.speak()
et humanMammal.speak()
est le même (invokevirtual #4
) car selon le compilateur les deux méthodes sont appelées sur la référence de Mammal
.
Alors maintenant la question se pose si les deux appels de méthode ont le même bytecode alors comment la JVM sait quelle méthode appeler ?
Eh bien, la réponse est cachée dans le bytecode lui-même et c'est l'instruction invokevirtual
. La JVM utilise l'instruction invokevirtual
pour appeler l'équivalent en Java des méthodes virtuelles en C++. En C++ si nous voulons substituer une méthode dans une autre classe, nous devons la déclarer comme virtuelle, mais en Java, toutes les méthodes sont virtuelles par défaut car nous pouvons substituer chaque méthode dans la classe enfant (sauf les méthodes privées, finales et statiques).
En Java, chaque variable de référence contient deux pointeurs cachés
- Un pointeur vers une table qui contient à nouveau des méthodes de l'objet et un pointeur vers l'objet Classe. par ex. [speak(), speak(String) Classe]
- Un pointeur vers la mémoire allouée sur le tas pour les données de cet objet par ex. valeurs des variables d'instance.
Ainsi, toutes les références d'objets détiennent indirectement une référence à une table qui contient toutes les références de méthodes de cet objet. Java a emprunté ce concept à C++ et cette table est connue sous le nom de table virtuelle (vtable).
Une vtable est une structure de type tableau qui contient les noms de méthodes virtuelles et leurs références sur des indices de tableau. JVM crée seulement une vtable par classe lorsqu'elle charge la classe en mémoire.
Donc chaque fois que la JVM rencontre avec l'instruction invokevirtual
, elle vérifie la vtable de cette classe pour la référence de méthode et appelle la méthode spécifique qui dans notre cas est la méthode d'un objet et non la référence.
Parce que tout cela est résolu seulement au moment de l'exécution et à l'exécution la JVM sait quelle méthode appeler, c'est pourquoi le remplacement de méthode est connu sous le nom de Polymorphisme Dynamique ou simplement Polymorphisme ou Liaison Dynamique.
Vous pouvez en lire plus de détails sur mon article Comment la JVM gère la surcharge et le remplacement des méthodes en interne.