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. Die statische Bindung erfolgt zur Kompilierungszeit und die dynamische Bindung erfolgt zur Laufzeit.
2. Verwenden Sie Variablen oder Methoden, die mit private, static oder final geändert wurden, und verwenden Sie statische Bindung. Virtuelle Methoden (Methoden, die von Unterklassen überschrieben werden können) werden basierend auf 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 einer überladenen Methode
Hier ist ein Beispiel für überladene Methoden.
Kopieren Sie den Codecode wie folgt:
öffentliche Klasse TestMain {
public static void main(String[] args) {
String str = new String();
Anrufer caller = new Caller();
caller.call(str);
}
statischer Klassenaufrufer {
public void call(Object obj) {
System.out.println("eine Objektinstanz im Aufrufer");
}
public void call(String str) {
System.out.println("eine String-Instanz im Aufrufer");
}
}
}
Das Ausführungsergebnis ist
Kopieren Sie den Codecode wie folgt:
22:19 $javaTestMain
eine String-Instanz in Caller
Im obigen Code gibt es zwei überladene Implementierungen der Aufrufmethode. Eine empfängt ein Objekt vom Typ Object als Parameter und die andere 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.
verifizieren
Ein bloßer Blick auf das Erscheinungsbild kann nicht beweisen, dass eine statische Bindung durchgeführt wird. Sie können dies überprüfen, indem Sie Javap zum Kompilieren verwenden.
Kopieren Sie den Codecode wie folgt:
22:19 $ javap -c TestMain
Zusammengestellt aus „TestMain.java“
öffentliche Klasse TestMain {
public TestMain();
Code:
0: aload_0
1: invokespecial #1 // Methode java/lang/Object."<init>":()V
4: Rückkehr
public static void main(java.lang.String[]);
Code:
0: new #2 // Klasse java/lang/String
3: dup
4: invokespecial #3 // Methode java/lang/String."<init>":()V
7: astore_1
8: neue #4 // Klasse TestMain$Caller
11: dup
12: invokespecial #5 // Methode TestMain$Caller."<init>":()V
15: astore_2
16: aload_2
17: aload_1
18: invokevirtual #6 // Methode TestMain$Caller.call:(Ljava/lang/String;)V
21: Rückkehr
}
Ich habe diese Zeile 18 gesehen: invokevirtual #6 // Method TestMain$Caller.call:(Ljava/lang/String;)V ist tatsächlich statisch gebunden, was bestätigt, dass die aufrufende Methode aufgerufen wird, die ein String-Objekt als Parameter empfängt.
Beispiel für das Überschreiben einer Methode
Kopieren Sie den Codecode wie folgt:
öffentliche Klasse TestMain {
public static void main(String[] args) {
String str = new String();
Anrufer caller = new SubCaller();
caller.call(str);
}
statischer Klassenaufrufer {
public void call(String str) {
System.out.println("eine String-Instanz im Aufrufer");
}
}
statische Klasse SubCaller erweitert Caller {
@Override
public void call(String str) {
System.out.println("eine String-Instanz in SubCaller");
}
}
}
Das Ausführungsergebnis ist
Kopieren Sie den Codecode wie folgt:
22:27 $javaTestMain
eine String-Instanz in SubCaller
Im obigen Code gibt es eine Implementierung der Call-Methode in Caller, die Caller erbt und die Implementierung der Call-Methode neu schreibt. 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.
verifizieren
Die dynamische Bindung kann nicht direkt mit Javap überprüft werden. Wenn nachgewiesen wird, dass keine statische Bindung durchgeführt wird, bedeutet dies, dass eine dynamische Bindung durchgeführt wird.
Kopieren Sie den Codecode wie folgt:
22:27 $ javap -c TestMain
Zusammengestellt aus „TestMain.java“
öffentliche Klasse TestMain {
public TestMain();
Code:
0: aload_0
1: invokespecial #1 // Methode java/lang/Object."<init>":()V
4: Rückkehr
public static void main(java.lang.String[]);
Code:
0: new #2 // Klasse java/lang/String
3: dup
4: invokespecial #3 // Methode java/lang/String."<init>":()V
7: astore_1
8: neue #4 // Klasse TestMain$SubCaller
11: dup
12: invokespecial #5 // Methode TestMain$SubCaller."<init>":()V
15: astore_2
16: aload_2
17: aload_1
18: invokevirtual #6 // Methode TestMain$Caller.call:(Ljava/lang/String;)V
21: Rückkehr
}
Wie das Ergebnis oben, 18: invokevirtual #6 // Methode TestMain$Caller.call:(Ljava/lang/String;)V Dies ist TestMain$Caller.call anstelle von TestMain$SubCaller.call, da die aufrufende Subroutine nicht ermittelt werden kann Zur Kompilierungszeit ist die Klasse immer noch die Implementierung der übergeordneten Klasse und kann daher nur durch dynamische Bindung zur Laufzeit verarbeitet werden.
Wenn Neuladen auf Neuschreiben trifft
Das folgende Beispiel ist etwas ungewöhnlich. Es gibt zwei Überladungen der Caller-Klasse. Komplizierter ist, dass SubCaller Caller integriert und diese beiden Methoden überschreibt. 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.
Kopieren Sie den Codecode wie folgt:
öffentliche Klasse TestMain {
public static void main(String[] args) {
String str = new String();
Anrufer callerSub = new SubCaller();
callerSub.call(str);
}
statischer Klassenaufrufer {
public void call(Object obj) {
System.out.println("eine Objektinstanz im Aufrufer");
}
public void call(String str) {
System.out.println("eine String-Instanz im Aufrufer");
}
}
statische Klasse SubCaller erweitert Caller {
@Override
public void call(Object obj) {
System.out.println("eine Objektinstanz in SubCaller");
}
@Override
public void call(String str) {
System.out.println("eine String-Instanz in SubCaller");
}
}
}
Das Ausführungsergebnis ist
Kopieren Sie den Codecode wie folgt:
22:30 $javaTestMain
eine String-Instanz in SubCaller
verifizieren
Da es oben vorgestellt wurde, werde ich hier nur die Dekompilierungsergebnisse veröffentlichen.
Kopieren Sie den Codecode wie folgt:
22:30 $ javap -c TestMain
Zusammengestellt aus „TestMain.java“
öffentliche Klasse TestMain {
public TestMain();
Code:
0: aload_0
1: invokespecial #1 // Methode java/lang/Object."<init>":()V
4: Rückkehr
public static void main(java.lang.String[]);
Code:
0: new #2 // Klasse java/lang/String
3: dup
4: invokespecial #3 // Methode java/lang/String."<init>":()V
7: astore_1
8: neue #4 // Klasse TestMain$SubCaller
11: dup
12: invokespecial #5 // Methode TestMain$SubCaller."<init>":()V
15: astore_2
16: aload_2
17: aload_1
18: invokevirtual #6 // Methode TestMain$Caller.call:(Ljava/lang/String;)V
21: Rückkehr
}
Neugierige Fragen
Ist es nicht möglich, dynamische Bindung zu verwenden?
Tatsächlich kann die Bindung bestimmter Methoden theoretisch auch durch statische Bindung erreicht werden. Zum Beispiel:
Kopieren Sie den Codecode wie folgt:
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 durch ausreichende Analyse des Codes erkennen, dass die Aufrufmethode von SubCaller aufgerufen werden sollte.
Aber warum gibt es keine statische Bindung?
Angenommen, unser Caller erbt von der BaseCaller-Klasse eines bestimmten Frameworks, die die Call-Methode implementiert, und BaseCaller erbt von SuperCaller. Die Call-Methode ist auch in SuperCaller implementiert.
Gehen Sie davon aus, dass BaseCaller und SuperCaller in einem bestimmten Framework 1.0 liegen
Kopieren Sie den Codecode wie folgt:
statische Klasse SuperCaller {
public void call(Object obj) {
System.out.println("eine Objektinstanz in SuperCaller");
}
}
statische Klasse BaseCaller erweitert SuperCaller {
public void call(Object obj) {
System.out.println("eine Objektinstanz in BaseCaller");
}
}
Wir haben dies mit Framework 1.0 implementiert. Der Aufrufer erbt von BaseCaller und ruft die super.call-Methode auf.
Kopieren Sie den Codecode wie folgt:
öffentliche Klasse TestMain {
public static void main(String[] args) {
Objekt obj = neues Objekt();
SuperCaller callerSub = new SubCaller();
callerSub.call(obj);
}
statische Klasse Caller erweitert BaseCaller{
public void call(Object obj) {
System.out.println("eine Objektinstanz im Aufrufer");
super.call(obj);
}
public void call(String str) {
System.out.println("eine String-Instanz im Aufrufer");
}
}
statische Klasse SubCaller erweitert Caller {
@Override
public void call(Object obj) {
System.out.println("eine Objektinstanz in SubCaller");
}
@Override
public void call(String str) {
System.out.println("eine String-Instanz 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 neu schreibt. Dann führt die obige Annahme, dass die Aufrufimplementierung, die statisch gebunden werden kann, in Version 1.1 zu Problemen, da super.call SuperCall in Version verwenden sollte 1.1. Implementierung der Aufrufmethode, anstatt davon auszugehen, dass die Implementierung der Aufrufmethode von BaseCaller durch statische Bindung bestimmt wird.
Daher werden einige Dinge, die tatsächlich statisch gebunden werden können, aus Sicherheits- und Konsistenzgründen einfach dynamisch gebunden.
Optimierungsinspiration erhalten?
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.
Daher können wir, ohne das Gesamtdesign zu beeinträchtigen, erwägen, Methoden oder Variablen mit private, static oder final zu ändern.