Wie wirkt sich der statische Modifikator auf diesen Code aus?

109

Hier ist mein Code:

class A {
    static A obj = new A();
    static int num1;
    static int num2=0;

    private A() {
        num1++;
        num2++;
    }
    public static A getInstance() {
        return obj;
    }
}

public class Main{
    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

Die Ausgabe ist 1 0, aber ich kann nicht verstehen.

Kann es mir jemand erklären?

lirui
quelle
10
Gute Frage! Was sollen wir daraus lernen: Tu es nicht! ;)
isnot2bad

Antworten:

116

In Java finden zwei Phasen statt: 1. Identifizierung, 2. Ausführung

  1. In der Identifikationsphase werden alle statischen Variablen erkannt und mit Standardwerten initialisiert.

    Die Werte sind nun:
    A obj=null
    num1=0
    num2=0

  2. Die zweite Phase, die Ausführung , beginnt von oben nach unten. In Java beginnt die Ausführung mit den ersten statischen Elementen.
    Hier ist Ihre erste statische Variable static A obj = new A();, also wird zuerst das Objekt dieser Variablen erstellt und der Konstruktor aufgerufen, daher der Wert von num1und num2wird 1.
    Und dann static int num2=0;wird wieder ausgeführt, was macht num2 = 0;.

Angenommen, Ihr Konstruktor sieht folgendermaßen aus:

 private A(){
    num1++;
    num2++;
    System.out.println(obj.toString());
 }

Dies wird ein werfen, NullPointerExceptionda objnoch keine Referenz von hat class A.

Shoaib Chikate
quelle
11
Ich werde verlängern: Verschieben Sie die Linie static A obj = new A();unten static int num2=0;und Sie sollten 1 und 1 bekommen.
Thomas
2
Was mich immer noch verwirrt, ist die Tatsache, dass num1 zwar keine explizite Initialisierung hat, aber (implizit) mit 0 initialisiert wird. Es sollte wirklich keinen Unterschied zwischen expliziter und impliziter Initialisierung geben ...
isnot2bad
@ isnot2bad "implizite Initialisierung" erfolgt als Teil der Deklaration. Erklärung geschieht vor der Zuweisung egal , was Sie in sie gegenwärtige Ordnung. A obj = new A(); int num1; int num2 = 0;Wird in dieser gedreht: A obj; int num1; int num2; obj = new A(); num2 = 0;. Java tut dies so, dass num1, num2es durch den Zeitpunkt definiert wird, zu dem Sie den new A()Konstruktor erreichen.
Hans Z
31

staticWenn der Modifikator auf eine Variablendeklaration angewendet wird, bedeutet dies, dass die Variable eher eine Klassenvariable als eine Instanzvariable ist. Mit anderen Worten ... es gibt nur eine num1Variable und nur eine num2Variable.

(Abgesehen: eine statische Variable ist wie . Eine globale Variable in einigen anderen Sprachen, mit der Ausnahme , dass sein Name nicht überall sichtbar ist , auch wenn es als deklariert wird public static, ist der unqualifizierte Name nur dann sichtbar , wenn es in der aktuellen Klasse oder eine übergeordneten Klasse deklariert wird oder wenn es mit einem statischen Import importiert wird. Das ist der Unterschied. Ein echtes globales ist ohne Qualifikation irgendwo sichtbar.)

Wenn Sie sich also auf obj.num1und beziehen, beziehen obj.num2Sie sich tatsächlich auf die statischen Variablen, deren tatsächliche Bezeichnungen A.num1und sind A.num2. Und in ähnlicher Weise erhöht der Konstruktor, wenn er inkrementiert num1und num2, dieselben Variablen (jeweils).

Die verwirrende Falte in Ihrem Beispiel liegt in der Klasseninitialisierung. Eine Klasse wird standardmäßig initialisiert, indem zunächst alle statischen Variablen initialisiert und dann die deklarierten statischen Initialisierer (und statischen Initialisiererblöcke) in der Reihenfolge ausgeführt werden, in der sie in der Klasse angezeigt werden . In diesem Fall haben Sie Folgendes:

static A obj = new A();
static int num1;
static int num2=0;

Es passiert so:

  1. Die Statik beginnt mit ihren Standardanfangswerten. A.objist nullund A.num1/ oder A.num2sind Null.

  2. Die erste Deklaration ( A.obj) erstellt eine Instanz von A()und den Konstruktor für AInkremente A.num1und A.num2. Wenn die Deklaration abgeschlossen ist A.num1und A.num2beides 1ist und A.objsich auf die neu erstellte AInstanz bezieht .

  3. Die zweite Deklaration ( A.num1) hat keinen Initialisierer und A.num1ändert sich daher nicht.

  4. Die dritte Deklaration ( A.num2) hat einen Initialisierer, dem Null zugewiesen wird A.num2.

Am Ende der Klasseninitialisierung A.num1ist 1und A.num2ist also 0... und das zeigen Ihre Druckanweisungen.

Dieses verwirrende Verhalten ist darauf zurückzuführen, dass Sie eine Instanz erstellen, bevor die statische Initialisierung abgeschlossen ist, und dass der von Ihnen verwendete Konstruktor von einer noch zu initialisierenden Statik abhängt und diese ändert . Dies sollten Sie in echtem Code vermeiden.

Stephen C.
quelle
16

1,0 ist richtig.

Beim Laden der Klasse werden alle statischen Daten initialisiert oder deklariert. Standardmäßig ist int 0.

  • zuerst wird A erstellt. num1 und num2 werden 1 und 1
  • als static int num1;nichts tut
  • als static int num2=0;dies schreibt 0 bis num2
Leonidos
quelle
9

Dies liegt an der Reihenfolge der statischen Initialisierer. Statische Ausdrücke in Klassen werden von oben nach unten ausgewertet.

Die erste aufgerufen werden soll Konstruktor A, die Sätze num1und num2beide auf 1:

static A obj = new A();

Dann,

static int num2=0;

wird aufgerufen und setzt erneut num2 = 0.

Deshalb ist num11 und num2ist 0.

Als Randnotiz sollte ein Konstruktor keine statischen Variablen ändern, das ist ein sehr schlechtes Design. Versuchen Sie stattdessen einen anderen Ansatz zur Implementierung eines Singleton in Java .

Domi
quelle
6

Ein Abschnitt in JLS ist zu finden: §12.4.2 .

Detailliertes Initialisierungsverfahren:

9. Führen Sie als Nächstes entweder die Klassenvariableninitialisierer und statischen Initialisierer der Klasse oder die Feldinitialisierer der Schnittstelle in Textreihenfolge aus, als wären sie ein einzelner Block, mit Ausnahme der endgültigen Klassenvariablen und Felder von Schnittstellen, deren Werte kompiliert werden Zeitkonstanten werden zuerst initialisiert

Die drei statischen Variablen werden also einzeln in Textreihenfolge initialisiert.

So

static A obj = new A();
//num1 = 1, num2 = 1;
static int num1;
//this is initilized first, see below.
static int num2=0;
//num1 = 1, num2 = 0;

Wenn ich die Reihenfolge ändere zu:

static int num1;
static int num2=0;
static A obj = new A();

Das Ergebnis wird sein 1,1.

Beachten Sie, dass dies static int num1;kein variabler Initialisierer ist, weil ( §8.3.2 ):

Wenn ein Felddeklarator einen Variableninitialisierer enthält, hat er die Semantik einer Zuordnung (§15.26) zu der deklarierten Variablen und: Wenn der Deklarator für eine Klassenvariable (dh ein statisches Feld) ist, ist der Variableninitialisierer ausgewertet und die Zuordnung genau einmal ausgeführt, wenn die Klasse initialisiert wird

Und diese Klassenvariable wird beim Erstellen der Klasse initialisiert. Dies geschieht zuerst ( §4.12.5 ).

Jede Variable in einem Programm muss einen Wert haben, bevor ihr Wert verwendet wird: Jede Klassenvariable, Instanzvariable oder Array-Komponente wird beim Erstellen mit einem Standardwert initialisiert (§15.9, §15.10): Für Typbyte der Standardwert ist Null, dh der Wert von (Byte) 0. Für den Typ short ist der Standardwert Null, dh der Wert von (short) 0. Für den Typ int ist der Standardwert Null, dh 0. Für den Typ long ist der Standardwert Null, dh 0L. Für den Typ float ist der Standardwert positiv Null, dh 0.0f. Für den Typ double ist der Standardwert positiv Null, dh 0.0d. Für den Typ char ist der Standardwert das Nullzeichen, dh '\ u0000'. Für den Typ Boolean ist der Standardwert false. Für alle Referenztypen (§4.3) ist der Standardwert null.

StarPinkER
quelle
2

Vielleicht hilft es, so darüber nachzudenken.

Klassen sind Blaupausen für Objekte.

Objekte können Variablen haben, wenn sie instanziiert werden.

Klassen können auch Variablen haben. Diese werden als statisch deklariert. Sie werden also eher für die Klasse als für die Objektinstanzen festgelegt.

Sie können nur eine Klasse in einer Anwendung haben, so dass es sich um einen globalen Speicher handelt, der speziell für diese Klasse entwickelt wurde. Auf diese statischen Variablen kann natürlich von überall in Ihrer Anwendung zugegriffen und geändert werden (vorausgesetzt, sie sind öffentlich).

Hier ist ein Beispiel für eine "Dog" -Klasse, die statische Variablen verwendet, um die Anzahl der von ihr erstellten Instanzen zu verfolgen.

"Hund" -Klasse ist die Wolke, während die orangefarbenen Kästchen "Hund" -Instanzen sind.

Hundeklasse

Weiterlesen

Hoffe das hilft!

Wenn Sie Lust auf Kleinigkeiten haben, wurde diese Idee zuerst von Platon eingeführt

Goran
quelle
1

Das statische Schlüsselwort wird in Java hauptsächlich für die Speicherverwaltung verwendet. Wir können statische Schlüsselwörter mit Variablen, Methoden, Blöcken und verschachtelten Klassen anwenden. Das statische Schlüsselwort gehört zur Klasse als Instanz der Klasse. Für eine kurze Erläuterung des statischen Schlüsselworts:

http://www.javatpoint.com/static-keyword-in-java

Rachana
quelle
0

Viele der obigen Antworten sind richtig. Aber um wirklich zu veranschaulichen, was passiert, habe ich unten einige kleine Änderungen vorgenommen.

Wie oben mehrfach erwähnt, wird eine Instanz der Klasse A erstellt, bevor die Klasse A vollständig geladen ist. Was also als normales "Verhalten" angesehen wird, wird nicht beobachtet. Dies ist nicht allzu unähnlich zum Aufrufen von Methoden aus einem Konstruktor, die überschrieben werden können. In diesem Fall befinden sich Instanzvariablen möglicherweise nicht in einem intuitiven Zustand. In diesem Beispiel befinden sich Klassenvariablen nicht in einem intuitiven Zustand.

class A {
    static A obj = new A();
    static int num1;
    static int num2;
    static {
        System.out.println("Setting num2 to 0");
        num2 = 0;
    }

    private A() {
        System.out.println("Constructing singleton instance of A");
        num1++;
        num2++;
    }

    public static A getInstance() {
        return obj;
    }
}

public class Main {

    public static void main(String[] arg) {
        A obj = A.getInstance();
        System.out.println(obj.num1);
        System.out.println(obj.num2);
    }
}

Ausgabe ist

Constructing singleton instance of A
Setting num2 to 0
1
0
w25r
quelle
0

Java initialisiert den Wert eines statischen oder nicht statischen Datenelements erst, wenn es nicht aufgerufen wird, sondern erstellt wird.

so dass hier, wenn num1 und num2 in main aufgerufen werden, es mit Werten initialisiert wird

num1 = 0 + 1; und

num2 = 0;

Deepak Tiwari
quelle