L'exécution d'un programme Java nécessite deux étapes : la compilation et l'exécution (interprétation). En même temps, Java est un langage de programmation orienté objet. Lorsque la sous-classe et la classe parent ont la même méthode et que la sous-classe remplace la méthode de la classe parent, lorsque le programme appelle la méthode au moment de l'exécution, doit-il appeler la méthode de la classe parent ou la méthode substituée de la sous-classe ? devrait être la question lorsque nous apprenons pour la première fois les problèmes rencontrés en Java. Ici, nous allons d’abord déterminer quelle méthode appeler ou quelle opération de variables est appelée liaison.
Il existe deux méthodes de liaison en Java, l'une est la liaison statique, également appelée liaison anticipée. L’autre est la liaison dynamique, également appelée liaison tardive.
Comparaison des différences
1. La liaison statique se produit au moment de la compilation et la liaison dynamique se produit au moment de l'exécution.
2. Utilisez des variables ou des méthodes modifiées avec private, static ou final, et utilisez la liaison statique. Les méthodes virtuelles (méthodes qui peuvent être remplacées par des sous-classes) seront liées dynamiquement en fonction de l'objet d'exécution.
3. La liaison statique est effectuée à l'aide des informations de classe, tandis que la liaison dynamique doit être effectuée à l'aide des informations sur l'objet.
4. La méthode surchargée est complétée à l'aide d'une liaison statique, tandis que la méthode de substitution est complétée à l'aide d'une liaison dynamique.
Exemple de méthode surchargée
Voici un exemple de méthodes surchargées.
Copiez le code comme suit :
classe publique TestMain {
public static void main (String[] arguments) {
Chaîne str = nouvelle chaîne ();
Appelant appelant = nouvel appelant ();
appelant.call(str);
}
Appelant de classe statique {
appel public void (Objet obj) {
System.out.println("une instance d'objet dans l'appelant");
}
appel public void (String str) {
System.out.println("une instance de chaîne dans l'appelant");
}
}
}
Le résultat de l'exécution est
Copiez le code comme suit :
22:19 $javaTestPrincipal
une instance de chaîne dans l'appelant
Dans le code ci-dessus, il existe deux implémentations surchargées de la méthode d'appel. L'une reçoit un objet de type Object en tant que paramètre et l'autre reçoit un objet de type String en tant que paramètre. str est un objet String et toutes les méthodes d'appel qui reçoivent des paramètres de type String seront appelées. La liaison ici est une liaison statique basée sur le type de paramètre au moment de la compilation.
vérifier
Le simple fait de regarder l'apparence ne peut pas prouver que la liaison statique est effectuée. Vous pouvez la vérifier en utilisant javap pour la compiler.
Copiez le code comme suit :
22:19 $ javap -c TestMain
Compilé à partir de "TestMain.java"
classe publique TestMain {
public TestMain();
Code:
0 : aload_0
1 : invoque spécial #1 // Méthode java/lang/Object."<init>":()V
4 : retour
public static void main(java.lang.String[]);
Code:
0 : nouveau #2 // classe java/lang/String
3 : dupé
4: Invocationspecial #3 // Méthode java/lang/String."<init>":()V
7 : astore_1
8 : nouveau #4 // classe TestMain$Caller
11 : dupé
12 : Invocationspecial #5 // Méthode TestMain$Caller."<init>":()V
15 : astore_2
16 : aload_2
17 : aload_1
18 : invoquervirtual #6 // Méthode TestMain$Caller.call:(Ljava/lang/String;)V
21 : retour
}
J'ai vu cette ligne 18 : invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V est bien liée statiquement, ce qui confirme que la méthode appelante qui reçoit un objet String en paramètre est appelée.
Exemple de remplacement d'une méthode
Copiez le code comme suit :
classe publique TestMain {
public static void main (String[] arguments) {
Chaîne str = nouvelle chaîne ();
Appelant appelant = new SubCaller();
appelant.call(str);
}
Appelant de classe statique {
appel public void (String str) {
System.out.println("une instance de chaîne dans l'appelant");
}
}
la classe statique SubCaller étend l'appelant {
@Outrepasser
appel public void (String str) {
System.out.println("une instance de String dans SubCaller");
}
}
}
Le résultat de l'exécution est
Copiez le code comme suit :
22:27 $javaTestPrincipal
une instance de chaîne dans SubCaller
Dans le code ci-dessus, il existe une implémentation de la méthode d'appel dans Caller. SubCaller hérite de Caller et réécrit l'implémentation de la méthode d'appel. Nous avons déclaré une variable callerSub de type Caller, mais cette variable pointe vers un objet SubCaller. D'après les résultats, on peut voir qu'il appelle l'implémentation de la méthode d'appel de SubCaller au lieu de la méthode d'appel de Caller. La raison de ce résultat est que la liaison dynamique se produit au moment de l'exécution et que pendant le processus de liaison, il est nécessaire de déterminer quelle version de l'implémentation de la méthode d'appel appeler.
vérifier
La liaison dynamique ne peut pas être directement vérifiée à l'aide de javap, et s'il est prouvé que la liaison statique n'est pas effectuée, cela signifie que la liaison dynamique est effectuée.
Copiez le code comme suit :
22:27 $ javap -c TestMain
Compilé à partir de "TestMain.java"
classe publique TestMain {
public TestMain();
Code:
0 : aload_0
1 : invoque spécial #1 // Méthode java/lang/Object."<init>":()V
4 : retour
public static void main(java.lang.String[]);
Code:
0 : nouveau #2 // classe java/lang/String
3 : dupé
4: Invocationspecial #3 // Méthode java/lang/String."<init>":()V
7 : astore_1
8 : nouveau #4 // classe TestMain$SubCaller
11 : dupé
12 : Invocationspecial #5 // Méthode TestMain$SubCaller."<init>":()V
15 : astore_2
16 : aload_2
17 : aload_1
18 : invoquervirtual #6 // Méthode TestMain$Caller.call:(Ljava/lang/String;)V
21 : retour
}
Comme résultat ci-dessus, 18 : invokevirtual #6 // Méthode TestMain$Caller.call:(Ljava/lang/String;)V Il s'agit de TestMain$Caller.call au lieu de TestMain$SubCaller.call, car le sous-programme appelant ne peut pas être déterminé au moment de la compilation La classe est toujours l'implémentation de la classe parent, elle ne peut donc être gérée que par liaison dynamique au moment de l'exécution.
Quand le rechargement rencontre la réécriture
L'exemple suivant est un peu anormal. Il y a deux surcharges de la méthode call dans la classe Caller. Ce qui est plus compliqué, c'est que SubCaller intègre Caller et remplace ces deux méthodes. En fait, cette situation est une situation composée des deux situations ci-dessus.
Le code suivant effectuera d'abord une liaison statique pour déterminer la méthode d'appel dont le paramètre est un objet String, puis effectuera une liaison dynamique au moment de l'exécution pour déterminer s'il convient d'exécuter l'implémentation d'appel de la sous-classe ou de la classe parent.
Copiez le code comme suit :
classe publique TestMain {
public static void main (String[] arguments) {
Chaîne str = nouvelle chaîne ();
Appelant callerSub = new SubCaller();
callerSub.call(str);
}
Appelant de classe statique {
appel public void (Objet obj) {
System.out.println("une instance d'objet dans l'appelant");
}
appel public void (String str) {
System.out.println("une instance de chaîne dans l'appelant");
}
}
la classe statique SubCaller étend l'appelant {
@Outrepasser
appel public void (Objet obj) {
System.out.println("une instance d'objet dans SubCaller");
}
@Outrepasser
appel public void (String str) {
System.out.println("une instance de String dans SubCaller");
}
}
}
Le résultat de l'exécution est
Copiez le code comme suit :
22h30 $javaTestPrincipal
une instance de chaîne dans SubCaller
vérifier
Puisqu'il a été présenté ci-dessus, je publierai ici uniquement les résultats de la décompilation.
Copiez le code comme suit :
22h30 $ javap -c TestMain
Compilé à partir de "TestMain.java"
classe publique TestMain {
public TestMain();
Code:
0 : aload_0
1 : invoque spécial #1 // Méthode java/lang/Object."<init>":()V
4 : retour
public static void main(java.lang.String[]);
Code:
0 : nouveau #2 // classe java/lang/String
3 : dupé
4: Invocationspecial #3 // Méthode java/lang/String."<init>":()V
7 : astore_1
8 : nouveau #4 // classe TestMain$SubCaller
11 : dupé
12 : Invocationspecial #5 // Méthode TestMain$SubCaller."<init>":()V
15 : astore_2
16 : aload_2
17 : aload_1
18 : invoquervirtual #6 // Méthode TestMain$Caller.call:(Ljava/lang/String;)V
21 : retour
}
Questions curieuses
N'est-il pas possible d'utiliser la liaison dynamique ?
En fait, en théorie, la liaison de certaines méthodes peut également être réalisée par liaison statique. Par exemple:
Copiez le code comme suit :
public static void main (String[] arguments) {
Chaîne str = nouvelle chaîne ();
Appelant final callerSub = new SubCaller();
callerSub.call(str);
}
Par exemple, ici callerSub contient l'objet de subCaller et la variable callerSub est finale, et la méthode d'appel est exécutée immédiatement. En théorie, le compilateur peut savoir que la méthode d'appel de SubCaller doit être appelée par une analyse suffisante du code.
Mais pourquoi n’y a-t-il pas de liaison statique ?
Supposons que notre Caller hérite de la classe BaseCaller d'un certain framework, qui implémente la méthode d'appel, et que BaseCaller hérite de SuperCaller. La méthode d'appel est également implémentée dans SuperCaller.
Supposons que BaseCaller et SuperCaller dans un certain framework 1.0
Copiez le code comme suit :
classe statique SuperCaller {
appel public void (Objet obj) {
System.out.println("une instance d'objet dans SuperCaller");
}
}
la classe statique BaseCaller étend SuperCaller {
appel public void (Objet obj) {
System.out.println("une instance d'objet dans BaseCaller");
}
}
Nous avons implémenté cela en utilisant le framework 1.0. L'appelant hérite de BaseCaller et appelle la méthode super.call.
Copiez le code comme suit :
classe publique TestMain {
public static void main (String[] arguments) {
Objet obj = new Object();
SuperCaller callerSub = new SubCaller();
callerSub.call(obj);
}
L'appelant de classe statique étend BaseCaller{
appel public void (Objet obj) {
System.out.println("une instance d'objet dans l'appelant");
super.call(obj);
}
appel public void (String str) {
System.out.println("une instance de chaîne dans l'appelant");
}
}
la classe statique SubCaller étend l'appelant {
@Outrepasser
appel public void (Objet obj) {
System.out.println("une instance d'objet dans SubCaller");
}
@Outrepasser
appel public void (String str) {
System.out.println("une instance de String dans SubCaller");
}
}
}
Ensuite, nous avons compilé le fichier de classe basé sur la version 1.0 de ce framework, en supposant que la liaison statique puisse déterminer que le super.call de l'appelant ci-dessus est implémenté en tant que BaseCaller.call.
Ensuite, nous supposons à nouveau que BaseCaller ne réécrit pas la méthode d'appel de SuperCaller dans la version 1.1 de ce framework. Ensuite, l'hypothèse ci-dessus selon laquelle l'implémentation d'appel qui peut être liée statiquement posera des problèmes dans la version 1.1, car super.call devrait utiliser SuperCall dans la version. 1.1. Implémentation de la méthode d'appel, plutôt que de supposer que l'implémentation de la méthode d'appel de BaseCaller est déterminée par une liaison statique.
Par conséquent, certaines choses qui peuvent réellement être liées statiquement sont simplement liées dynamiquement en tenant compte de la sécurité et de la cohérence.
Inspiration d’optimisation obtenue ?
Étant donné que la liaison dynamique doit déterminer quelle version de l’implémentation de la méthode ou de la variable exécuter au moment de l’exécution, elle prend plus de temps que la liaison statique.
Par conséquent, sans affecter la conception globale, nous pouvons envisager de modifier des méthodes ou des variables avec private, static ou final.