Heim  >  Artikel  >  Java  >  Detaillierte Einführung in die statische Bindung und dynamische Bindung in Java

Detaillierte Einführung in die statische Bindung und dynamische Bindung in Java

高洛峰
高洛峰Original
2016-12-26 16:17:231227Durchsuche

Die Ausführung eines Java-Programms erfordert zwei Schritte: Kompilierung und Ausführung (Interpretation). Gleichzeitig ist Java eine objektorientierte Programmiersprache. Wenn die Unterklasse und die übergeordnete Klasse dieselbe Methode haben und die Unterklasse die Methode der übergeordneten Klasse überschreibt, sollte das Programm beim Aufrufen der Methode zur Laufzeit die Methode der übergeordneten Klasse oder die überschriebene Methode der Unterklasse aufrufen? Dies sollte die Frage sein, auf die wir beim ersten Erlernen von Java stoßen. Hier legen wir zunächst fest, welche Methode aufgerufen werden soll bzw. welche Operation von Variablen als Bindung bezeichnet wird.

In Java gibt es zwei Bindungsmethoden, eine davon ist die statische Bindung, auch frühe Bindung genannt. Die andere ist die dynamische Bindung, auch als späte Bindung bekannt.

Differenzvergleich

1. Statische Bindung erfolgt zur Kompilierungszeit, dynamische Bindung erfolgt zur Laufzeit
2. Verwenden Sie private oder statische oder endgültig geänderte Variablen oder Methoden, verwenden Sie statische Bindung. Virtuelle Methoden (Methoden, die von Unterklassen überschrieben werden können) werden entsprechend dem Laufzeitobjekt dynamisch gebunden.
3. Die statische Bindung wird mithilfe von Klasseninformationen abgeschlossen, während die dynamische Bindung mithilfe von Objektinformationen abgeschlossen werden muss.
4. Die überladene Methode wird mithilfe der statischen Bindung abgeschlossen, während die überschreibende Methode mithilfe der dynamischen Bindung abgeschlossen wird.

Beispiel für eine überladene Methode

Hier ist ein Beispiel für eine überladene Methode. Das Ergebnis der

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");
      }
  }
}

-Ausführung ist

22:19 $ java TestMain
a String instance in in Caller

Im obigen Code gibt es zwei überladene Implementierungen der Aufrufmethode, eine empfängt ein Objekt vom Typ Object als Parameter und der andere Es empfängt ein Objekt vom Typ String als Parameter. str ist ein String-Objekt und alle Aufrufmethoden, die Parameter vom Typ String empfangen, werden aufgerufen. Die Bindung hier ist eine statische Bindung basierend auf dem Parametertyp zur Kompilierungszeit.

Überprüfung

Ein bloßer Blick auf das Erscheinungsbild kann nicht beweisen, dass es statisch gebunden ist. Sie können es überprüfen, indem Sie es mit Javap kompilieren.

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
}

Ich habe diese Zeile 18 gesehen: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V ist tatsächlich statisch gebunden und es wird bestätigt, dass der Aufruf String empfangen hat Objekt als Parameter für die aufrufende Methode.

Beispiel für eine überschreibende Methode

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");
      }
  }
}

Das Ausführungsergebnis ist

22:27 $ java TestMain
a String instance in SubCaller

Der obige Code enthält eine Implementierung der Aufrufmethode in Caller, SubCaller erbt Caller Und die Implementierung der Aufrufmethode wurde neu geschrieben. Wir haben eine Variable callerSub vom Typ Caller deklariert, aber diese Variable zeigt auf ein SubCaller-Objekt. Anhand der Ergebnisse ist ersichtlich, dass die Aufrufmethodenimplementierung von SubCaller anstelle der Aufrufmethode von Caller aufgerufen wird. Der Grund für dieses Ergebnis liegt darin, dass die dynamische Bindung zur Laufzeit erfolgt und während des Bindungsprozesses ermittelt werden muss, welche Version der Aufrufmethodenimplementierung aufgerufen werden soll.

Überprüfung

Dynamische Bindung kann nicht direkt mit Javap überprüft werden. Wenn sich herausstellt, dass keine statische Bindung durchgeführt wird, bedeutet dies, dass dynamische Bindung durchgeführt wird.

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
}

Wie oben gezeigt, 18: invokevirtual #6 // Methode TestMain$Caller.call:(Ljava/lang/String;)V Hier ist TestMain$Caller.call anstelle von TestMain$SubCaller Da der Compiler nicht bestimmen kann, ob die Implementierung der Unterklasse oder der übergeordneten Klasse aufgerufen werden soll, kann dies nur durch dynamische Bindung zur Laufzeit verarbeitet werden.

Wenn Überladung auf Überschreibung trifft

Das folgende Beispiel ist etwas ungewöhnlich. Es gibt zwei Überladungen der Caller-Klasse. Komplizierter ist, dass SubCaller Caller integriert und diese beiden neu schreibt Methoden. Tatsächlich handelt es sich bei dieser Situation um eine zusammengesetzte Situation aus den beiden oben genannten Situationen.

Der folgende Code führt zunächst eine statische Bindung durch, um die Aufrufmethode zu bestimmen, deren Parameter ein String-Objekt ist, und führt dann zur Laufzeit eine dynamische Bindung durch, um zu bestimmen, ob die Aufrufimplementierung der Unterklasse oder der übergeordneten Klasse ausgeführt werden soll.

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");
      }
  }
}

Das Ausführungsergebnis ist

22:30 $ java TestMain
a String instance in in SubCaller

Verifizierung

Da es oben eingeführt wurde, werde ich hier nur das Dekompilierungsergebnis veröffentlichen

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
}

Neugierige Frage

Geht das nicht ohne dynamische Bindung?

Tatsächlich kann die Bindung bestimmter Methoden theoretisch auch durch statische Bindung erreicht werden. Beispiel:

public static void main(String[] args) {
      String str = new String();
      final Caller callerSub = new SubCaller();
      callerSub.call(str);
}

Hier enthält callerSub beispielsweise das Objekt von subCaller und die Variable callerSub ist endgültig, und die Aufrufmethode wird sofort ausgeführt. Theoretisch kann der Compiler wissen, dass die Aufrufmethode von SubCaller ist sollte durch ausreichende Analyse des Codes aufgerufen werden.

Aber warum gibt es keine statische Bindung?
Angenommen, unser Caller erbt von der BaseCaller-Klasse eines bestimmten Frameworks, das die Call-Methode implementiert, und BaseCaller erbt von SuperCaller. Die Call-Methode ist auch in SuperCaller implementiert.

Angenommen, BaseCaller und SuperCaller in einem bestimmten 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");
  }
}

und wir verwenden Framework 1.0, um dies zu implementieren. Der Aufrufer erbt von BaseCaller und ruft die super.call-Methode auf.

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");
      }
  }
}

Dann haben wir die Klassendatei basierend auf Version 1.0 dieses Frameworks kompiliert. Unter der Annahme, dass die statische Bindung bestimmen kann, dass der super.call des oben genannten Callers als BaseCaller.call implementiert ist.

Dann gehen wir erneut davon aus, dass BaseCaller die Aufrufmethode von SuperCaller in Version 1.1 dieses Frameworks nicht überschreibt. Dann wird die obige Annahme, dass die Aufrufimplementierung, die statisch gebunden werden kann, in Version 1.1 Probleme verursachen 1.1 super. Der Aufruf sollte mit der Aufrufmethode von SuperCall und nicht mit der durch statische Bindung bestimmten Aufrufmethode von BaseCaller implementiert werden.

Daher werden einige Dinge, die tatsächlich statisch gebunden werden können, aus Sicherheits- und Konsistenzgründen einfach dynamisch gebunden.

Haben Sie Anregungen zur Optimierung?

Da die dynamische Bindung bestimmen muss, welche Version der Methodenimplementierung oder Variablen zur Laufzeit ausgeführt werden soll, ist sie zeitaufwändiger als die statische Bindung.

Ohne das Gesamtdesign zu beeinträchtigen, können wir erwägen, Methoden oder Variablen mit private, static oder final zu ändern.

Eine ausführlichere Einführung in die statische Bindung und dynamische Bindung in Java finden Sie auf der chinesischen PHP-Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn