Bei der Betrachtung der Klasseninitialisierung wissen wir alle, dass bei der Durchführung der Unterklasse -Initialisierung die Unterklasse zuerst initialisiert werden muss, wenn die übergeordnete Klasse initialisiert wird. Die Dinge sind jedoch nicht so einfach wie ein einzelner Satz.
Schauen wir uns zunächst die Bedingungen für die Initialisierung an, die in Java ausgelöst werden:
(1) Wenn neue Objekte instanziiert und auf statische Daten und Methoden zugreifen, dh bei der Begegnung mit Anweisungen: Neu, Getstatic/Putstatic und invocestatic;
(2) bei Reflexion, um die Klasse aufzurufen;
(3) Bei der Initialisierung einer Klasse wird die Initialisierung der übergeordneten Klasse zuerst ausgelöst, wenn die übergeordnete Klasse nicht initialisiert wurde.
(4) die Klasse, in der sich die Hauptmethode der Eingangsmethode befindet;
(5) Die Klasse, in der sich die Methodenhandle in der dynamischen Sprachunterstützung von JDK1.7 befindet, wenn die Initialisierung nicht ausgelöst wird;
Nach der Zusammenstellung wird eine <clinit> -Methode erzeugt und die Klasseninitialisierung in dieser Methode durchgeführt. Diese Methode wird nur ausgeführt, und das JVM sorgt dafür und führt eine Synchronisationsregelung durch.
Unter ihnen ist die Bedingung (3) aus Sicht der Methodenaufrufs der Unterklasse <clinit>, die die übergeordnete Klasse <Clinit> zu Beginn rekursiv aufruft, was der Tatsache ähnlich ist, dass wir zuerst den Konstruktor der Elternklassen im Unterklassenkonstruktor aufrufen müssen.
Es ist jedoch zu beachten, dass die "Auslösen" die Initialisierung nicht abgeschlossen hat, was bedeutet, dass die Initialisierung der Unterklasse vor der Initialisierung der übergeordneten Klasse im Voraus endet, wo die "Gefahr" liegt.
1. Ein Beispiel für die Klasseninitialisierung:
In diesem Beispiel verwende ich eine periphere Klasse, um 2 statische Mitgliedsklassen mit Erbschaftsbeziehungen zu enthalten. Da die Initialisierung der peripheren Klasse und der statischen Mitgliedsklassen keine kausale Beziehung aufweist, ist es sicher und bequem, sie so zu zeigen.
Die Elternklasse A bzw. die Klasse B der Klasse A enthalten die Hauptfunktionen. Aus der obigen Auslöserbedingung (4) ist ersichtlich, dass verschiedene Klasseninitialisierungspfade durch Aufrufen dieser beiden Hauptfunktionen ausgelöst werden.
Das Problem mit diesem Beispiel ist, dass die übergeordnete Klasse die statische Referenz der untergeordneten Klasse enthält und sie in der Definition initialisiert:
öffentliche Klasse WrapperClass {private statische Klasse A {static {System.out.println ("Klasse -A -Initialisierung Start ..."); } // Die übergeordnete Klasse enthält statische Referenzen der Kinderklasse Private static b b = new B (); geschützte statische int Aint = 9; static {system.out.println ("Klasse -A -Initialisierung Ende ..."); } public static void main (String [] args) {}} private statische Klasse B erweitert ein {static {System.out.println ("Klasse -B -Initialisierung Start ..."); } // Die Domäne der Unterklasse hängt von der Domäne der übergeordneten Klasse Private Static int BINT = 9 + A.AINT ab; public b () {// Die statische Domäne des Konstruktors hängt vom Klassensystem ab. } static {system.out.println ("Klasse -B -Initialisierung endet ..." + "Wert von Aint:" + bint); } public static void main (String [] args) {}}} Szenario 1: Das Ausgabeergebnis, wenn der Eintrag eine Hauptfunktion der Klasse B ist:
/** * Die Initialisierung der Klasse A beginnt ... * Der Konstruktor der Klasse B nennt den Wert von Bint 0 * Klasse A Initialisierung endet
Analyse: Es ist zu erkennen, dass der Aufruf der Hauptfunktion die Initialisierung von Klasse B auslöst und die Methode der Klasse A in die Initialisierung der <Clinit> der Klasse A eingibt. Zu diesem Zeitpunkt wird B instanziiert, was sich bereits in der <klinit> der Klasse B befindet. Der Haupt -Thread hat das Schloss erhalten und begonnen, die <clinit> von Klasse B. auszuführen. Wir sagten zu Beginn, dass die JVM sicherstellen wird, dass die Initialisierungsmethode einer Klasse nur einmal ausgeführt wird. Nach dem Erhalt der neuen Anweisung wird die JVM nicht wieder in die <klinit> -Methode der Klasse B eingetragen, sondern direkt sofort instanziiert. Zu diesem Zeitpunkt hat die Klasse B die Klasseninitialisierung jedoch nicht abgeschlossen, sodass Sie feststellen können, dass der Wert von BINT 0 beträgt (diese 0 ist die Null -Initialisierung, die nach der Zuordnung des Speicheres des Methodenbereichs während der Vorbereitungsstufe der Klassenbelastung durchgeführt wird).
Daher kann der Schluss gezogen werden, dass die übergeordnete Klasse die statische Domäne des untergeordneten Typs enthält und die Zuordnungsaktion ausführt, die dazu führen kann, dass die Unterklasse -Instanziierung vor Abschluss der Klasseninitialisierung durchgeführt wird.
Szenario 2: Das Ausgabeergebnis, wenn der Eintrag eine Hauptfunktion von Klasse A ist:
/** * Die Initialisierung der Klasse A beginnt ... * Die Initialisierung der Klasse B beginnt ... * Die Initialisierung der Klasse B endet ... Der Wert von Aint: 9 * Der Konstruktor der Klasse B nennt den Wert von Bint 9 * Klasse A -Initialisierung endet ... *////////
Analyse: Nach der Analyse von Szenario 1 wissen wir, dass die Auslösen der Initialisierung der Klasse A durch Initialisierung der Klasse B die Instanziierung der Klassenvariablen B in der Klasse A verursacht, bevor die Initialisierung der Klasse B abgeschlossen ist. Wenn also die Klasse A zuerst initialisiert wird, kann die Klasse B zuerst bei der Klassenvariable -Instanziierung ausgelöst werden, so dass die Initialisierung vor der Instanziierung vorgenommen wird? Die Antwort lautet ja, aber es gibt immer noch Probleme.
Gemäß der Ausgabe können wir feststellen, dass die Initialisierung der Klasse B vor Abschluss der Initialisierung der Klasse A durchgeführt wird, was dazu führt, dass Variablen wie Klassenvariable erst nach der Initialisierung der Klasse B initialisiert werden, sodass der Wert von Aint, die durch Domain -Bint in der Klasse B erhalten wurden, "0" und nicht "18" ist.
Schlussfolgerung: Zusammenfassend kann der Schluss gezogen werden, dass es sehr gefährlich ist, Klassenvariablen von Unterklasse -Typen in die übergeordnete Klasse einzubeziehen und sie bei der Definition zu instanziieren. Die spezifische Situation ist möglicherweise nicht so einfach wie ein Beispiel. Das Aufrufen von Methoden zum Zuweisen von Werten in der Definition ist ebenfalls gefährlich. Selbst wenn Sie statische Domänen von Unterklassentypen einbeziehen möchten, sollten Sie auch Werte durch statische Methoden zuweisen, da die JVM sicherstellen kann, dass alle Initialisierungsaktionen abgeschlossen sind, bevor die statische Methode aufgerufen wird (natürlich ist diese Garantie, dass Sie nicht statisches b b = neu b () einbeziehen sollten.
2. Ein instanziiertes Beispiel:
Zunächst müssen Sie den Prozess der Objekterstellung kennen:
(1) Bei der Begegnung einer neuen Anweisung prüfen Sie, ob die Klasse das Laden, die Überprüfung, die Vorbereitung, die Analyse und die Initialisierung abgeschlossen hat (der Analyseprozess besteht darin, die Symbolreferenz in eine direkte Referenz zu unterteilen, wie z.
(2) Speicher zuweisen, die kostenlose Liste oder Zeiger -Kollisionsmethode und den neu zugewiesenen Speicher "Null" verwenden. Daher werden alle Instanzvariablen in diesem Link standardmäßig auf 0 initialisiert (als NULL als NULL bezeichnet).
(3) Führen Sie die Methode <Init> aus, einschließlich der Überprüfung des Aufrufs in die <Gut> -Methode (Konstruktor) der übergeordneten Klasse, die von der Instanzvariablen definierten Zuordnungsaktionen, der Instantiator wird im Instantiator ausgeführt und schließlich die Aktionen im Konstruktor aufrufen.
Dieses Beispiel kann bekannter sein, dh es verstößt gegen "Aufrufe nicht überschreibbare Methoden in der Konstruktor -Klonmethode und der ReadObject -Methode". Der Grund dafür ist, dass Polymorphismus in Java, dh dynamische Bindung.
Der Konstruktor der übergeordneten Klasse A enthält eine geschützte Methode, und Klasse B ist seine Unterklasse.
öffentliche Klasse WrongInStantiation {private statische Klasse A {public a () {doSomething (); } protected void dosomething () {System.out.println ("A's Dosen etwas"); }} private statische Klasse B erweitert einen {privaten int bint = 9; @Override Protected void dosomething () {System.out.println ("Bs Dosen, Bint:" + bint); }} public static void main (String [] args) {b b = new b (); }}Ausgangsergebnis:
/** * Bs Dosen etwas, Bint: 0 */
Analyse: Zunächst müssen Sie wissen, dass der Java -Compiler, wenn es keine Anzeige gibt, den Standardkonstruktor generiert und den Konstruktor der übergeordneten Klasse am Anfang aufruft. Daher nennt der Konstruktor der Klasse B den Konstruktor der Klasse A zuerst zu Beginn.
Die geschützte Methode, die etwas in Klasse A aufgerufen wird. Aus dem Ausgangsergebnis wird, dass die Methodeimplementierung der Unterklasse tatsächlich aufgerufen wird und die Instanziierung der Unterklasse noch nicht begonnen hat, sodass BINT nicht 9 als "erwartet", sondern 0 ist;
Dies liegt an der dynamischen Bindung, Dosen etwas ist eine geschützte Methode, daher wird sie durch die Invokevirtual -Richtlinie aufgerufen, die die entsprechende Methodenimplementierung basierend auf der Art der Objektinstanz findet (hier ist das Instanzobjekt von B und die entsprechende Methode ist die Methode -Implementierung von Klasse B), sodass dieses Ergebnis dies ist.
Schlussfolgerung: Wie bereits erwähnt, "rufen Sie nicht überschriebene Methoden in der Konstruktor -Methode, Klonmethode und ReadObject -Methode auf".
Die oben genannten sind die beiden "Minenfelder" in der Initialisierung und Instanziierung der Java -Klasse, die Ihnen vorgestellt wurden. Ich hoffe, es wird für das Lernen aller hilfreich sein.