1. Reihenfolge der Initialisierung eines Objekts in Java: wer zuerst, wer zuletzt?
Ein Objekt in Java zu erzeugen ist nicht einfach nur „Speicher reservieren und Werte hineinschreiben“. Es ist ein ganzer Ablauf mit klarer Reihenfolge. Stellt man sich die Initialisierung als Raketenstart vor, hat jede Stufe ihren genau festgelegten Platz.
Wenn Sie schreiben:
Person rutger = new Person("Rutger", 30);
Java führt die folgenden Schritte aus:
- Reserviert Speicher für das Objekt und weist allen Feldern ihre Standardwerte zu.
- Führt explizite Initialisierungen der Felder aus (wenn Sie Werte direkt bei der Deklaration angegeben haben).
- Führt alle nicht statischen (normalen) Initialisierungsblöcke aus – in der Reihenfolge, in der sie in der Klasse stehen.
- Führt den Rumpf des Konstruktors aus, den Sie über new aufgerufen haben.
Schauen wir uns jeden Schritt genauer an.
2. Initialisierung der Felder
Standardwerte
Wenn ein Objekt gerade erst erzeugt wird (noch bevor Sie etwas explizit zuweisen), erhalten alle seine Felder die folgenden Standardwerte:
| Feldtyp | Standardwert |
|---|---|
|
|
|
|
|
|
|
'\u0000' (Nullzeichen) |
| Referenztypen (String, andere Objekte) | |
Explizite Initialisierung
Wenn Sie einem Feld direkt bei der Deklaration einen Wert zuweisen, wird dieser Wert nach den Standardwerten, aber vor der Ausführung des Konstruktors gesetzt.
public class Person {
private String name = "Unbenannt";
private int age = 18;
}
Erzeugen Sie das Objekt über einen parameterlosen Konstruktor, bleiben diese Werte erhalten. Erzeugen Sie es über einen Konstruktor mit Parametern – werden sie in der Regel überschrieben.
3. Initialisierungsblöcke: wozu sie dienen und wie sie funktionieren
In Java kann man sogenannte nicht statische (normale) Initialisierungsblöcke deklarieren. Sie werden jedes Mal bei der Objekterzeugung ausgeführt, direkt nach der Initialisierung der Felder, aber vor dem Konstruktor.
Syntax:
public class Person {
{
System.out.println("Nichtstatischer Initialisierungsblock wird ausgeführt!");
}
}
Wenn es in der Klasse mehrere solcher Blöcke gibt, werden sie in der Reihenfolge ausgeführt, in der sie geschrieben sind.
Beispiel mit mehreren Blöcken
public class Person {
private String name = "Unbenannt";
{
System.out.println("Block 1: name = " + name);
name = "Rätsel";
}
{
System.out.println("Block 2: name = " + name);
}
public Person() {
System.out.println("Konstruktor: name = " + name);
}
}
Wenn wir ein Objekt erzeugen:
Person p = new Person();
Die Ausgabe lautet:
Block 1: name = Unbenannt
Block 2: name = Rätsel
Konstruktor: name = Rätsel
Wozu sind Initialisierungsblöcke gut?
In der Praxis werden Initialisierungsblöcke selten verwendet. In der Regel lässt sich alles, was man im Block tun könnte, entweder direkt bei der Felddeklaration oder im Konstruktor erledigen. Manchmal (z. B. wenn Sie mehrere Konstruktoren haben und ein Teil der Initialisierungslogik für alle gemeinsam sein soll) können Initialisierungsblöcke jedoch bequem sein.
4. Konstruktor: der finale Akkord der Initialisierung
Nachdem alle Felder Werte erhalten haben (Standardwerte oder explizit gesetzt) und alle Initialisierungsblöcke gelaufen sind, wird der Konstruktor aufgerufen – genau der, den Sie nach new angegeben haben.
Im Konstruktor legen Sie üblicherweise die endgültigen Feldwerte fest, nehmen Parameter entgegen und führen weitere Initialisierung durch, die von den Eingabedaten abhängt.
public class Person {
private String name;
private int age;
public Person(String name, int age) {
System.out.println("Konstruktor: name = " + name + ", age = " + age);
this.name = name;
this.age = age;
}
}
5. Demonstration der vollständigen Initialisierungsreihenfolge
Schreiben wir eine Klasse, die deutlich zeigt, in welcher Reihenfolge die Initialisierung abläuft.
public class Person {
private String name = "Unbenannt";
private int age = 18;
{
System.out.println("Initialisierungsblock: name = " + name + ", age = " + age);
age = 21;
}
public Person() {
System.out.println("Konstruktor ohne Parameter: name = " + name + ", age = " + age);
}
public Person(String name, int age) {
System.out.println("Konstruktor mit Parametern: name = " + name + ", age = " + age);
this.name = name;
this.age = age;
}
public void printInfo() {
System.out.println("Person: name = " + name + ", age = " + age);
}
}
Und nun in unserer Hauptklasse (z. B. Main):
public class Main {
public static void main(String[] args) {
System.out.println("Wir erzeugen das Objekt p1:");
Person p1 = new Person();
p1.printInfo();
System.out.println("\nWir erzeugen das Objekt p2:");
Person p2 = new Person("Peter", 30);
p2.printInfo();
}
}
Die Ausgabe sieht ungefähr so aus:
Wir erzeugen das Objekt p1:
Initialisierungsblock: name = Unbenannt, age = 18
Konstruktor ohne Parameter: name = Unbenannt, age = 21
Person: name = Unbenannt, age = 21
Wir erzeugen das Objekt p2:
Initialisierungsblock: name = Unbenannt, age = 18
Konstruktor mit Parametern: name = Unbenannt, age = 21
Person: name = Peter, age = 30
Beachten Sie: Im Konstruktor mit Parametern sind die Feldwerte noch die alten (die nach dem Initialisierungsblock), und erst nach dem Konstruktor erhalten die Felder ihre endgültigen Werte.
6. Diagramm der Initialisierungsreihenfolge
Hier ein kleines Schema (Ablaufdiagramm) zur Visualisierung:
flowchart TD
A[Speicher reservieren, Standardwerte] --> B[Explizite Initialisierung der Felder]
B --> C[Ausführung der nicht statischen Initialisierungsblöcke]
C --> D[Ausführung des Konstruktors]
D --> E[Objekt ist einsatzbereit]
7. Besonderheiten und Feinheiten
Statische Felder und statische Blöcke
In dieser Vorlesung sprechen wir nur über nicht statische (normale) Felder und Blöcke. Statische Felder und statische Blöcke werden einmalig beim Laden der Klasse initialisiert, nicht bei jeder Objekterzeugung. Mehr dazu in den Themen zur Kapselung.
Wann wird was initialisiert?
- Felder – bei jeder Objekterzeugung.
- Initialisierungsblöcke – bei jeder Objekterzeugung.
- Statische Felder und Blöcke – nur einmal beim Laden der Klasse.
Darf man in Initialisierungsblöcken auf Felder zugreifen?
Ja, darf man! Wichtig ist jedoch: Wenn ein Feld unterhalb des Blocks deklariert ist, existiert es zwar bereits, aber wenn Sie darauf zugreifen, bevor es explizit initialisiert wurde, erhalten Sie den Standardwert.
8. Typische Fehler bei der Initialisierung von Objekten
Fehler Nr. 1: Die Erwartung, dass Felder vor ihrer Deklaration initialisiert werden.
In Java ist die Reihenfolge der Deklaration von Feldern und Initialisierungsblöcken in der Klasse relevant. Wenn Sie in einem Initialisierungsblock auf ein Feld zugreifen, das weiter unten deklariert ist, existiert es bereits, ist aber noch nicht explizit initialisiert – es hat den Standardwert.
Fehler Nr. 2: Duplizierung der Initialisierung in Blöcken und Konstruktoren.
Anfänger duplizieren oft dieselbe Logik sowohl im Block als auch im Konstruktor. Besser ist es, entweder den Block oder den Konstruktor zu verwenden – oder einen Konstruktor aus einem anderen über this(...) aufzurufen.
Fehler Nr. 3: Die Erwartung, dass statische Felder bei jeder Objekterzeugung initialisiert werden.
Statische Felder und Blöcke funktionieren anders – sie werden nur einmal beim Laden der Klasse initialisiert.
Fehler Nr. 4: Verwendung nicht initialisierter Referenzfelder.
Wenn Sie vergessen, ein Referenzfeld (z. B. String name;) explizit zu initialisieren, ist es null, bis Sie ihm im Initialisierungsblock oder im Konstruktor einen Wert zuweisen.
GO TO FULL VERSION