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, la liaison dynamique se produit au moment de l'exécution
2 Utilisez des variables ou des méthodes privées ou statiques ou finales modifiées, 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éthode surchargée. Le résultat de l'exécution de
public class TestMain { public static void main(String[] args) { String str = new String(); Caller caller = new Caller(); caller.call(str); } static class Caller { public void call(Object obj) { System.out.println("an Object instance in Caller"); } public void call(String str) { System.out.println("a String instance in in Caller"); } } }
est
22:19 $ java TestMain a String instance in in Caller
Dans le code ci-dessus, il y a deux implémentations surchargées de la méthode d'appel, l'une reçoit un objet de type Objet en paramètre, et l'autre Il reçoit un objet de type String en 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érification
Le simple fait de regarder l'apparence ne peut pas prouver qu'elle est liée statiquement. Vous pouvez la vérifier en utilisant javap pour la compiler.
22:19 $ javap -c TestMain Compiled from "TestMain.java" public class TestMain { public TestMain(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/String 3: dup 4: invokespecial #3 // Method java/lang/String."<init>":()V 7: astore_1 8: new #4 // class TestMain$Caller 11: dup 12: invokespecial #5 // Method TestMain$Caller."<init>":()V 15: astore_2 16: aload_2 17: aload_1 18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V 21: return }
J'ai vu cette ligne 18 : invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V est bien lié statiquement, et il est confirmé que l'appel reçu String Objet en tant que paramètre de la méthode appelante.
Exemple de méthode de remplacement
public class TestMain { public static void main(String[] args) { String str = new String(); Caller caller = new SubCaller(); caller.call(str); } static class Caller { public void call(String str) { System.out.println("a String instance in Caller"); } } static class SubCaller extends Caller { @Override public void call(String str) { System.out.println("a String instance in SubCaller"); } } }
Le résultat de l'exécution est
22:27 $ java TestMain a String instance in SubCaller
Le code ci-dessus, il y a une implémentation de la méthode d'appel dans Caller, SubCaller hérite de Caller , Et l'implémentation de la méthode d'appel a été réécrite. 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érification
La liaison dynamique ne peut pas être directement vérifiée à l'aide de javap, et si cela prouve que la liaison statique n'est pas effectuée, cela signifie que la liaison dynamique est effectuée.
22:27 $ javap -c TestMain Compiled from "TestMain.java" public class TestMain { public TestMain(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/String 3: dup 4: invokespecial #3 // Method java/lang/String."<init>":()V 7: astore_1 8: new #4 // class TestMain$SubCaller 11: dup 12: invokespecial #5 // Method TestMain$SubCaller."<init>":()V 15: astore_2 16: aload_2 17: aload_1 18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V 21: return }
Comme indiqué ci-dessus, 18 : invokevirtual #6 // Méthode TestMain$Caller.call:(Ljava/lang/String;)V Voici TestMain$Caller.call au lieu de l'appel TestMain$SubCaller. , car le compilateur ne peut pas déterminer s'il doit appeler l'implémentation de la sous-classe ou de la classe parent, elle ne peut donc être gérée que par liaison dynamique au moment de l'exécution.
Quand la surcharge rencontre la substitution
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 réécrit ce Two. 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 faut exécuter l'implémentation d'appel de la sous-classe ou de la classe parent.
public class TestMain { public static void main(String[] args) { String str = new String(); Caller callerSub = new SubCaller(); callerSub.call(str); } static class Caller { public void call(Object obj) { System.out.println("an Object instance in Caller"); } public void call(String str) { System.out.println("a String instance in in Caller"); } } static class SubCaller extends Caller { @Override public void call(Object obj) { System.out.println("an Object instance in SubCaller"); } @Override public void call(String str) { System.out.println("a String instance in in SubCaller"); } } }
Le résultat de l'exécution est
22:30 $ java TestMain a String instance in in SubCaller
Vérification
Puisqu'il a été introduit ci-dessus, je ne publierai ici que le résultat de la décompilation
22:30 $ javap -c TestMain Compiled from "TestMain.java" public class TestMain { public TestMain(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: new #2 // class java/lang/String 3: dup 4: invokespecial #3 // Method java/lang/String."<init>":()V 7: astore_1 8: new #4 // class TestMain$SubCaller 11: dup 12: invokespecial #5 // Method TestMain$SubCaller."<init>":()V 15: astore_2 16: aload_2 17: aload_1 18: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V 21: return }
Question curieuse
N'est-ce pas possible sans liaison dynamique ?
En fait, théoriquement, la liaison de certaines méthodes peut également être réalisée par liaison statique. Par exemple :
public static void main(String[] args) { String str = new String(); final Caller 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é 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 BaseCaller et SuperCaller dans un certain framework 1.0
static class SuperCaller { public void call(Object obj) { System.out.println("an Object instance in SuperCaller"); } } static class BaseCaller extends SuperCaller { public void call(Object obj) { System.out.println("an Object instance in BaseCaller"); } }
et nous utilisons le framework 1.0 pour l'implémenter. L'appelant hérite de BaseCaller et appelle la méthode super.call.
public class TestMain { public static void main(String[] args) { Object obj = new Object(); SuperCaller callerSub = new SubCaller(); callerSub.call(obj); } static class Caller extends BaseCaller{ public void call(Object obj) { System.out.println("an Object instance in Caller"); super.call(obj); } public void call(String str) { System.out.println("a String instance in in Caller"); } } static class SubCaller extends Caller { @Override public void call(Object obj) { System.out.println("an Object instance in SubCaller"); } @Override public void call(String str) { System.out.println("a String instance in in SubCaller"); } } }
Ensuite, nous avons compilé le fichier de classe basé sur la version 1.0 de ce framework en supposant que la liaison statique peut 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 de l'appel qui peut être liée statiquement posera des problèmes dans la version 1.1, car dans la version. 1.1 super. L'appel doit être implémenté en utilisant la méthode d'appel de SuperCall, plutôt que la méthode d'appel de BaseCaller déterminée par une liaison statique.
Ainsi, 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.
Avez-vous des idées d'optimisation ?
É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.
Ainsi sans affecter la conception globale, on peut envisager de modifier des méthodes ou des variables avec private, static ou final.
Pour une introduction plus détaillée à la liaison statique et à la liaison dynamique en Java, veuillez faire attention au site Web PHP chinois !