Une traduction de cet article a été préparée spécialement pour les étudiants du cours Java Developer.
Dans mon article précédent
Tout sur la surcharge de méthode vs la substitution de méthode , nous avons examiné les rÚgles et les différences de surcharge et de substitution de méthode. Dans cet article, nous verrons comment la surcharge et le remplacement de méthode sont gérés dans la JVM.
Par exemple, prenez les cours de l'article précédent: le parent
Mammal
(mammifĂšre) et l'enfant
Human
(humain).
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"); }
Nous pouvons considérer la question du polymorphisme de deux cÎtés: du «logique» et du «physique». Voyons d'abord le cÎté logique du problÚme.
Point de vue logique
D'un point de vue logique, au stade de la compilation, la méthode appelée est considérée comme liée au type de référence. Mais au moment de l'exécution, la méthode de l'objet référencé sera appelée.
Par exemple, dans la ligne
humanMammal.speak();
le compilateur pense que
Mammal.speak()
sera appelé, car
humanMammal
déclaré comme
Mammal
. Mais au moment de l'exécution, la JVM saura que
humanMammal
contient un objet
Human
et invoquera en fait la méthode
Human.speak()
.
C'est assez simple tant que nous restons à un niveau conceptuel. Mais comment la JVM gÚre-t-elle tout cela en interne? Comment la JVM calcule-t-elle la méthode à appeler?
Nous savons également que les méthodes surchargées ne sont pas appelées polymorphes et se résolvent au moment de la compilation. Bien que parfois la surcharge de méthode soit appelée
polymorphisme au moment de la compilation ou liaison précoce / statique .
Les méthodes remplacées (remplacement) sont résolues au moment de l'exécution car le compilateur ne sait pas s'il existe des méthodes remplacées dans l'objet affecté au lien.
Point de vue physique
Dans cette section, nous essaierons de trouver des preuves «physiques» pour toutes les déclarations ci-dessus. Pour ce faire, regardez le bytecode que nous pouvons obtenir en exécutant
javap -verbose OverridingInternalExample
. Le paramĂštre
-verbose
nous permettra d'obtenir un bytecode plus intuitif correspondant Ă notre programme java.
La commande ci-dessus affichera deux sections de bytecode.
1. Le pool de constantes . Il contient presque tout ce qui est nécessaire pour exécuter le programme. Par exemple, les références de méthode (
#Methodref
), les classes (
#Class
), les littéraux de chaßne (
#String
).
2. Le bytecode du programme. Instructions de bytecode exécutables.

Pourquoi la surcharge de méthode est appelée liaison statique
Dans l'exemple ci-dessus, le compilateur pense que la méthode
humanMammal.speak()
sera appelée à partir de la classe
Mammal
, bien qu'au moment de l'exécution, elle sera appelée à partir de l'objet référencé dans
humanMammal
- ce sera un objet de la classe
Human
.
En regardant notre code et le résultat
javap
, nous voyons que différents bytecodes sont utilisés pour appeler les méthodes
humanMammal.speak()
,
human.speak()
et
human.speak("Hindi")
, car le compilateur peut les distinguer en fonction de la référence de classe .
Ainsi, en cas de surcharge d'une méthode, le compilateur est capable d'identifier des instructions de bytecode et des adresses de méthode au moment de la compilation. C'est pourquoi cela est appelé
liaison statique ou polymorphisme au moment de la compilation.Pourquoi la substitution de méthode est appelée liaison dynamique
Pour appeler les
anyMammal.speak()
et
humanMammal.speak()
, le bytecode est le mĂȘme, car du point de vue du compilateur, les deux mĂ©thodes sont appelĂ©es pour la classe
Mammal
:
invokevirtual #4 // Method org/programming/mitra/exercises/OverridingInternalExample$Mammal.speak:()V
Alors maintenant, la question est, si les deux appels ont le mĂȘme bytecode, comment la JVM sait-elle quelle mĂ©thode appeler?
La rĂ©ponse est cachĂ©e dans le bytecode lui-mĂȘme et dans l'instruction
invokevirtual
. Selon la spécification JVM
(note du traducteur: référence à la spécification JVM 2.11.8 ) :
L'instruction invokevirtual appelle la méthode d'instance par répartition sur le type (virtuel) de l'objet. Il s'agit de la répartition normale des méthodes dans le langage de programmation Java.
La JVM utilise l'
invokevirtual
pour invoquer en Java des mĂ©thodes Ă©quivalentes aux mĂ©thodes virtuelles C ++. En C ++, pour remplacer une mĂ©thode dans une autre classe, la mĂ©thode doit ĂȘtre dĂ©clarĂ©e virtuelle. Mais en Java, par dĂ©faut, toutes les mĂ©thodes sont virtuelles (Ă l'exception des mĂ©thodes finales et statiques), donc dans la classe enfant, nous pouvons remplacer n'importe quelle mĂ©thode.
L'instruction
invokevirtual
prend un pointeur sur la méthode à appeler (# 4 est l'index dans le pool constant).
invokevirtual #4
Mais la référence # 4 se réfÚre en outre à une autre méthode et classe.
#4 = Methodref #2.#27
Tous ces liens sont utilisés ensemble pour obtenir une référence à la méthode et à la classe dans laquelle se trouve la méthode souhaitée. Ceci est également mentionné dans la spécification JVM (
note du traducteur: référence à la spécification JVM 2.7 ):
La machine virtuelle Java ne nécessite aucune structure interne spécifique d'objets.
Dans certaines implĂ©mentations Java Virtual Machine d'Oracle, une rĂ©fĂ©rence Ă une instance de classe est une rĂ©fĂ©rence Ă un gestionnaire, qui se compose lui-mĂȘme d'une paire de liens: l'un pointe vers une table de mĂ©thodes d'objet et un pointeur vers un objet Class reprĂ©sentant le type de l'objet, et l'autre vers la zone donnĂ©es sur le tas contenant des donnĂ©es d'objet.
Cela signifie que chaque variable de référence contient deux pointeurs masqués:
- Un pointeur vers une table qui contient les méthodes de l'objet et un pointeur vers l'objet
Class
, par exemple, [speak(), speak(String) Class object]
- Un pointeur vers la mémoire sur le tas alloué pour les données d'objet, telles que les valeurs de champ d'objet.
Mais encore une fois la question se pose: comment fonctionne
invokevirtual
avec cela? Malheureusement, personne ne peut rĂ©pondre Ă cette question, car tout dĂ©pend de la mise en Ćuvre de la JVM et varie de JVM Ă JVM.
Du raisonnement ci-dessus, nous pouvons conclure qu'une référence à un objet contient indirectement un lien / pointeur vers une table qui contient toutes les références aux méthodes de cet objet. Java a emprunté ce concept à C ++. Cette table est connue sous différents noms, tels que
table de méthode virtuelle (VMT), table de fonction virtuelle (vftable), table virtuelle (vtable), table de répartition .
Nous ne pouvons pas ĂȘtre sĂ»rs de la façon dont vtable est implĂ©mentĂ© en Java, car cela dĂ©pend de la JVM spĂ©cifique. Mais nous pouvons nous attendre Ă ce que la stratĂ©gie soit Ă peu prĂšs la mĂȘme qu'en C ++, oĂč vtable est une structure de type tableau qui contient les noms de mĂ©thode et leurs rĂ©fĂ©rences. Chaque fois que la JVM essaie d'exĂ©cuter une mĂ©thode virtuelle, elle demande son adresse dans la table virtuelle.
Pour chaque classe, il n'y a qu'une seule table virtuelle, ce qui signifie que la table est unique et identique pour tous les objets de la classe, similaire à l'objet Class. Les objets de classe sont abordés plus en détail dans les articles
Pourquoi une classe Java externe ne peut pas ĂȘtre statique et
Pourquoi Java est-il purement orienté objet ou pourquoi pas ?
Ainsi, il n'y a qu'une seule table virtuelle pour la classe
Object
, qui contient les 11 méthodes (si
registerNatives
ne
registerNatives
pas prises en compte) et les liens correspondant à leur implémentation.

Lorsque la JVM charge la classe Mammal en mémoire, elle crée un objet
Class
pour elle et crée une vtable qui contient toutes les méthodes de la vtable de la classe
Object
avec les mĂȘmes rĂ©fĂ©rences (puisque
Mammal
ne remplace pas les méthodes d'
Object
) et ajoute une nouvelle entrée pour la méthode
speak()
.

Ensuite, la classe de la classe
Human
entre en jeu et la JVM copie toutes les entrées de la vtable de la classe
Mammal
dans la vtable de la classe
Human
et ajoute une nouvelle entrée pour la version surchargée de
speak(String)
.
La JVM sait que la classe
Human
a remplacé deux méthodes:
toString()
d'
Object
et
speak()
de
Mammal
. Maintenant, pour ces mĂ©thodes, au lieu de crĂ©er de nouveaux enregistrements avec des liens mis Ă jour, la machine virtuelle Java modifie les liens vers les mĂ©thodes existantes dans le mĂȘme index dans lequel elles Ă©taient prĂ©cĂ©demment prĂ©sentes et conserve les mĂȘmes noms de mĂ©thode.

L'instruction
invokevirtual
amÚne la JVM à traiter la valeur dans la référence à la méthode # 4 non pas comme une adresse, mais comme le nom de la méthode à rechercher dans la table virtuelle pour l'objet actuel.
J'espÚre qu'il est désormais plus clair comment la JVM utilise le pool constant et la table de méthodes virtuelles pour déterminer la méthode à appeler.
Vous pouvez trouver l'exemple de code dans le référentiel
Github .