JavaRush /Java-Blog /Random-DE /Zuweisung und Initialisierung in Java
Viacheslav
Level 3

Zuweisung und Initialisierung in Java

Veröffentlicht in der Gruppe Random-DE

Einführung

Der Hauptzweck von Computerprogrammen ist die Datenverarbeitung. Um Daten zu verarbeiten, müssen Sie sie irgendwie speichern. Ich schlage vor, zu verstehen, wie Daten gespeichert werden.
Zuweisung und Initialisierung in Java - 1

Variablen

Variablen sind Container, die beliebige Daten speichern. Schauen wir uns das offizielle Tutorial von Oracle an: Declaring Member Variables . Laut diesem Tutorial gibt es verschiedene Arten von Variablen:
  • Felder : In der Klasse deklarierte Variablen;
  • Lokale Variablen : Variablen in einer Methode oder einem Codeblock;
  • Parameter : Variablen in der Methodendeklaration (in der Signatur).
Alle Variablen müssen einen Variablentyp und einen Variablennamen haben.
  • Der Typ einer Variablen gibt an, welche Daten die Variable darstellt (d. h. welche Daten sie speichern kann). Wie wir wissen, kann der Typ einer Variablen primitiv (primitiv ) oder ein Objekt sein , nicht primitiv (nicht primitiv). Bei Objektvariablen wird ihr Typ durch eine bestimmte Klasse beschrieben.
  • Der Variablenname muss in Kleinbuchstaben (camel case) geschrieben werden. Weitere Informationen zur Benennung finden Sie unter „ Variablen:Benennung “.
Auch wenn eine Variable auf Klassenebene, d. h. ist ein Klassenfeld, für das ein Zugriffsmodifikator angegeben werden kann. Weitere Einzelheiten finden Sie unter Steuern des Zugriffs auf Mitglieder einer Klasse .

Variable Aussage

Wir erinnern uns also daran, was eine Variable ist. Um mit einer Variablen arbeiten zu können, müssen Sie sie deklarieren. Schauen wir uns zunächst eine lokale Variable an. Anstelle einer IDE verwenden wir der Einfachheit halber die Online-Lösung von Tutorialspoint: Online IDE . Lassen Sie uns dieses einfache Programm in ihrer Online-IDE ausführen:
public class HelloWorld{
    public static void main(String []args){
        int number;
        System.out.println(number);
    }
}
Wie Sie sehen, haben wir also eine lokale Variable mit Namen numberund Typ deklariert int. Wir klicken auf die Schaltfläche „Ausführen“ und erhalten die Fehlermeldung:
HelloWorld.java:5: error: variable number might not have been initialized
        System.out.println(number);
Was ist passiert? Wir haben eine Variable deklariert, ihren Wert jedoch nicht initialisiert. Es ist erwähnenswert, dass dieser Fehler nicht zur Ausführungszeit (also nicht zur Laufzeit), sondern zur Kompilierungszeit auftrat. Der intelligente Compiler prüfte, ob die lokale Variable vor dem Zugriff darauf initialisiert werden würde oder nicht. Daher ergeben sich daraus folgende Aussagen:
  • Auf lokale Variablen sollte erst zugegriffen werden, nachdem sie initialisiert wurden.
  • Lokale Variablen haben keine Standardwerte;
  • Die Werte lokaler Variablen werden zur Kompilierzeit überprüft.
Daher wird uns gesagt, dass die Variable initialisiert werden muss. Beim Initialisieren einer Variablen wird einer Variablen ein Wert zugewiesen. Lassen Sie uns dann herausfinden, was es ist und warum.

Initialisieren einer lokalen Variablen

Das Initialisieren von Variablen ist eines der kniffligsten Themen in Java, weil... hängt sehr eng mit der Arbeit mit dem Speicher, der JVM-Implementierung, der JVM-Spezifikation und anderen ebenso beängstigenden wie kniffligen Dingen zusammen. Aber Sie können versuchen, es zumindest einigermaßen herauszufinden. Gehen wir vom Einfachen zum Komplexen. Um die Variable zu initialisieren, verwenden wir den Zuweisungsoperator und ändern die Zeile in unserem vorherigen Code:
int number = 2;
Bei dieser Option treten keine Fehler auf und der Wert wird auf dem Bildschirm angezeigt. Was passiert in diesem Fall? Versuchen wir zu argumentieren. Wenn wir einer Variablen einen Wert zuweisen möchten, soll diese Variable einen Wert speichern. Es stellt sich heraus, dass der Wert irgendwo gespeichert werden muss, aber wo? Auf der Festplatte? Dies ist jedoch sehr langsam und kann zu Einschränkungen für uns führen. Es stellt sich heraus, dass der einzige Ort, an dem wir Daten „hier und jetzt“ schnell und effizient speichern können, der Speicher ist. Das bedeutet, dass wir etwas Speicherplatz zuweisen müssen. So ist das. Wenn eine Variable initialisiert wird, wird ihr Speicherplatz im Speicher zugewiesen, der dem Java-Prozess zugewiesen ist, in dem unser Programm ausgeführt wird. Der einem Java-Prozess zugewiesene Speicher ist in mehrere Bereiche oder Zonen unterteilt. Welcher von ihnen Speicherplatz zuweist, hängt davon ab, welcher Typ der Variablen deklariert wurde. Der Speicher ist in die folgenden Abschnitte unterteilt: Heap, Stack und Non-Heap . Beginnen wir mit dem Stapelspeicher. Stapel wird als Stapel (zum Beispiel ein Stapel Bücher) übersetzt. Es handelt sich um eine LIFO-Datenstruktur (Last In, First Out). Das heißt, wie ein Stapel Bücher. Wenn wir Bücher hinzufügen, legen wir sie oben drauf, und wenn wir sie wegnehmen, nehmen wir das oberste (also das zuletzt hinzugefügte). Also starten wir unser Programm. Wie wir wissen, wird ein Java-Programm von einer JVM, also einer Java Virtual Machine, ausgeführt. Die JVM muss wissen, wo die Programmausführung beginnen soll. Dazu deklarieren wir eine Hauptmethode, die „Einstiegspunkt“ genannt wird. Zur Ausführung in der JVM wird ein Hauptthread (Thread) erstellt. Wenn ein Thread erstellt wird, wird ihm ein eigener Stapel im Speicher zugewiesen. Dieser Stapel besteht aus Frames. Wenn jede neue Methode in einem Thread ausgeführt wird, wird ihr ein neuer Frame zugewiesen und oben im Stapel hinzugefügt (wie ein neues Buch in einem Bücherstapel). Dieser Rahmen enthält Verweise auf Objekte und primitive Typen. Ja, ja, unser int wird auf dem Stapel gespeichert, weil ... int ist ein primitiver Typ. Bevor ein Frame zugewiesen wird, muss die JVM verstehen, was dort gespeichert werden soll. Aus diesem Grund erhalten wir die Fehlermeldung „Variable wurde möglicherweise nicht initialisiert“, denn wenn sie nicht initialisiert ist, kann die JVM den Stapel nicht für uns vorbereiten. Daher hilft uns ein intelligenter Compiler beim Kompilieren eines Programms, Fehler zu vermeiden und alles kaputt zu machen. (!) Der Klarheit halber empfehle ich einen Super-Duper -Artikel: „ Java Stack and Heap: Java Memory Allocation Tutorial “. Es verlinkt auf ein ebenso cooles Video:
Nachdem die Ausführung einer Methode abgeschlossen ist, werden die für diese Methoden zugewiesenen Frames aus dem Stapel des Threads gelöscht und zusammen mit ihnen wird der für diesen Frame zugewiesene Speicher mit allen Daten gelöscht.

Lokale Objektvariablen initialisieren

Lassen Sie uns unseren Code noch einmal ändern, um ihn etwas kniffliger zu gestalten:
public class HelloWorld{

    private int number = 2;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }

}
Was wird hier passieren? Lasst uns noch einmal darüber reden. Die JVM weiß, von wo aus sie das Programm ausführen soll, d. h. Sie sieht die Hauptmethode. Es erstellt einen Thread und weist ihm Speicher zu (schließlich muss ein Thread die für die Ausführung erforderlichen Daten irgendwo speichern). In diesem Thread wird der Hauptmethode ein Frame zugewiesen. Als nächstes erstellen wir ein HelloWorld-Objekt. Dieses Objekt wird nicht mehr auf dem Stack, sondern auf dem Heap erstellt. Denn Objekt ist kein primitiver Typ, sondern ein Objekttyp. Und der Stapel speichert nur einen Verweis auf das Objekt im Heap (wir müssen irgendwie auf dieses Objekt zugreifen). Als nächstes werden im Stapel der Hauptmethode Frames für die Ausführung der println-Methode zugewiesen. Nach Ausführung der Hauptmethode werden alle Frames zerstört. Wenn der Rahmen zerstört wird, werden alle Daten zerstört. Das Objektobjekt wird nicht sofort zerstört. Erstens wird die Referenz darauf zerstört und somit wird niemand mehr auf das Objektobjekt verweisen und ein Zugriff auf dieses Objekt im Speicher ist nicht mehr möglich. Eine smarte JVM verfügt hierfür über einen eigenen Mechanismus – einen Garbage Collector (kurz Garbage Collector oder GC). Anschließend werden Objekte aus dem Speicher entfernt, auf die sonst niemand verweist. Dieser Vorgang wurde im oben angegebenen Link noch einmal beschrieben. Es gibt sogar ein Video mit einer Erklärung.

Felder initialisieren

Die Initialisierung der in einer Klasse angegebenen Felder erfolgt auf besondere Weise, je nachdem, ob das Feld statisch ist oder nicht. Wenn ein Feld das Schlüsselwort static enthält, bezieht sich dieses Feld auf die Klasse selbst. Wenn das Wort static nicht angegeben ist, verweist dieses Feld auf eine Instanz der Klasse. Schauen wir uns das anhand eines Beispiels an:
public class HelloWorld{
    private int number;
    private static int count;

    public static void main(String []args){
        HelloWorld object = new HelloWorld();
        System.out.println(object.number);
    }
}
In diesem Beispiel werden die Felder zu unterschiedlichen Zeiten initialisiert. Das Zahlenfeld wird initialisiert, nachdem das HelloWorld-Klassenobjekt erstellt wurde. Das Zählfeld wird jedoch initialisiert, wenn die Klasse von der Java Virtual Machine geladen wird. Das Laden von Klassen ist ein separates Thema, daher werden wir es hier nicht vermischen. Es ist nur wichtig zu wissen, dass statische Variablen initialisiert werden, wenn die Klasse zur Laufzeit bekannt wird. Hier ist etwas anderes wichtiger, und das haben Sie bereits bemerkt. Wir haben den Wert nirgendwo angegeben, aber es funktioniert. Und in der Tat. Variablen, bei denen es sich um Felder handelt. Wenn für sie kein Wert angegeben ist, werden sie mit einem Standardwert initialisiert. Bei numerischen Werten ist dies 0 bzw. 0,0 bei Gleitkommazahlen. Für boolesche Werte ist dies falsch. Und für alle Objekttypvariablen ist der Wert null (wir werden später darüber sprechen). Es scheint, warum ist das so? Sondern weil Objekte im Heap (im Heap) erstellt werden. Die Arbeit mit diesem Bereich erfolgt zur Runtime. Und wir können diese Variablen zur Laufzeit initialisieren, im Gegensatz zum Stack, dessen Speicher vor der Ausführung vorbereitet werden muss. So funktioniert Speicher in Java. Aber es gibt hier noch eine weitere Funktion. Dieses kleine Stück berührt verschiedene Ecken der Erinnerung. Wie wir uns erinnern, wird der Hauptmethode ein Frame im Stapelspeicher zugewiesen. Dieser Frame speichert einen Verweis auf ein Objekt im Heap-Speicher. Aber wo wird die Zählung dann gespeichert? Wie wir uns erinnern, wird diese Variable sofort initialisiert, bevor das Objekt im Heap erstellt wird. Das ist eine wirklich knifflige Frage. Vor Java 8 gab es einen Speicherbereich namens PERMGEN. Ab Java 8 hat sich dieser Bereich geändert und heißt METASPACE. Im Wesentlichen sind statische Variablen Teil der Klassendefinition, d. h. seine Metadaten. Daher ist es logisch, dass es im Metadaten-Repository METASPACE gespeichert wird. MetaSpace gehört zum selben Nicht-Heap-Speicherbereich und ist Teil davon. Es ist auch wichtig zu berücksichtigen, dass die Reihenfolge, in der Variablen deklariert werden, berücksichtigt wird. In diesem Code liegt beispielsweise ein Fehler vor:
public class HelloWorld{

    private static int b = a;
    private static int a = 1;

    public static void main(String []args){
        System.out.println(b);
    }

}

Was ist null

Wie oben erwähnt, werden Variablen von Objekttypen, wenn sie Felder einer Klasse sind, auf Standardwerte initialisiert und dieser Standardwert ist null. Aber was ist in Java null? Das Erste, woran Sie denken sollten, ist, dass primitive Typen nicht null sein können. Und das alles, weil null eine spezielle Referenz ist, die nirgendwo und auf kein Objekt verweist. Daher kann nur eine Objektvariable null sein. Das zweite, was wichtig zu verstehen ist, ist, dass null eine Referenz ist. Ich beziehe mich auch auf ihr Gewicht. Zu diesem Thema können Sie die Frage zum Stackoverflow lesen: „ Benötigt die Nullvariable Speicherplatz im Speicher ?“.

Initialisierungsblöcke

Bei der Betrachtung der Initialisierung von Variablen wäre es eine Sünde, die Initialisierungsblöcke nicht zu berücksichtigen. Es sieht aus wie das:
public class HelloWorld{

    static {
        System.out.println("static block");
    }

    {
        System.out.println("block");
    }

    public HelloWorld () {
        System.out.println("Constructor");
    }

    public static void main(String []args){
        HelloWorld obj = new HelloWorld();
    }

}
Die Ausgabereihenfolge lautet: statischer Block, Block, Konstruktor. Wie wir sehen können, werden die Initialisierungsblöcke vor dem Konstruktor ausgeführt. Und manchmal kann dies eine praktische Möglichkeit zur Initialisierung sein.

Abschluss

Ich hoffe, dass dieser kurze Überblick einen Einblick in die Funktionsweise und die Gründe dafür geben konnte. #Wjatscheslaw
Kommentare
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION