Maison > Java > javaDidacticiel > Introduction détaillée à la liaison statique et à la liaison dynamique en Java

Introduction détaillée à la liaison statique et à la liaison dynamique en Java

高洛峰
Libérer: 2016-12-26 16:17:23
original
1305 Les gens l'ont consulté

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");
      }
  }
}
Copier après la connexion

est

22:19 $ java TestMain
a String instance in in Caller
Copier après la connexion

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
}
Copier après la connexion

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");
      }
  }
}
Copier après la connexion

Le résultat de l'exécution est

22:27 $ java TestMain
a String instance in SubCaller
Copier après la connexion

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
}
Copier après la connexion

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");
      }
  }
}
Copier après la connexion

Le résultat de l'exécution est

22:30 $ java TestMain
a String instance in in SubCaller
Copier après la connexion

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
}
Copier après la connexion

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);
}
Copier après la connexion

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");
  }
}
Copier après la connexion

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");
      }
  }
}
Copier après la connexion

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 !

Étiquettes associées:
source:php.cn
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal