1. Variablen initialisieren

Wie Sie bereits wissen, können Sie in Ihrer Klasse mehrere Variablen deklarieren und diese nicht nur deklarieren, sondern auch sofort mit ihren Anfangswerten initialisieren.

Und dieselben Variablen können auch in einem Konstruktor initialisiert werden. Dies bedeutet, dass diesen Variablen theoretisch zwei Werte zugewiesen werden könnten. Beispiel

Code Notiz
class Cat
{
   public String name;
   public int age = -1;

   public Cat(String name, int age)
   {
     this.name = name;
     this.age = age;
   }

   public Cat()
   {
     this.name = "Nameless";
   }
}



Der ageVariablen wird ein Anfangswert zugewiesen.




Der Anfangswert wird überschrieben.


Die Altersvariable speichert ihren Anfangswert.
 Cat cat = new Cat("Whiskers", 2);
Dies ist zulässig: Der erste Konstruktor wird aufgerufen
 Cat cat = new Cat();
Dies ist zulässig: Der zweite Konstruktor wird aufgerufen

Folgendes passiert, wenn Folgendes Cat cat = new Cat("Whiskers", 2);ausgeführt wird:

  • Ein CatObjekt wird erstellt
  • Alle Instanzvariablen werden mit ihren Anfangswerten initialisiert
  • Der Konstruktor wird aufgerufen und sein Code ausgeführt.

Mit anderen Worten: Die Variablen erhalten zunächst ihre Anfangswerte und erst dann wird der Code des Konstruktors ausgeführt.


2. Reihenfolge der Initialisierung von Variablen in einer Klasse

Variablen werden nicht nur initialisiert, bevor der Konstruktor ausgeführt wird – sie werden in einer genau definierten Reihenfolge initialisiert: der Reihenfolge, in der sie in der Klasse deklariert werden.

Schauen wir uns einen interessanten Code an:

Code Notiz
public class Solution
{
   public int a = b + c + 1;
   public int b = a + c + 2;
   public int c = a + b + 3;
}

Dieser Code lässt sich nicht kompilieren, da zum Zeitpunkt der aErstellung der Variablen noch keine b und- c Variablen vorhanden sind. Aber Sie können Ihren Code wie folgt schreiben – dieser Code wird kompiliert und läuft einwandfrei.

Code Notiz
public class Solution
{
   public int a;
   public int b = a + 2;
   public int c = a + b + 3;
}


0
0+2
0+2+3

Denken Sie jedoch daran, dass Ihr Code für andere Entwickler transparent sein muss. Es ist besser, solche Techniken nicht zu verwenden, da sie die Lesbarkeit des Codes beeinträchtigen.

Hier müssen wir bedenken, dass Variablen, bevor ihnen ein Wert zugewiesen wird, einen Standardwert haben . Für den intTyp ist dieser Null.

Wenn die JVM die aVariable initialisiert, weist sie einfach den Standardwert für den int-Typ zu: 0.

Wenn es erreicht b, ist die Variable a bereits bekannt und hat einen Wert, sodass die JVM ihr den Wert 2 zuweist.

Und wenn die cVariable erreicht wird, sind die Variablen aund bbereits initialisiert, sodass die JVM problemlos den Anfangswert für Folgendes berechnen kann c: 0+2+3.

Wenn Sie eine Variable innerhalb einer Methode erstellen, können Sie diese nur verwenden, wenn Sie ihr zuvor einen Wert zugewiesen haben. Dies gilt jedoch nicht für die Variablen einer Klasse! Wenn einer Variablen einer Klasse kein Anfangswert zugewiesen ist, wird ihr ein Standardwert zugewiesen.


3. Konstanten

Während wir analysieren, wie Objekte erstellt werden, lohnt es sich, auf die Initialisierung von Konstanten, also Variablen, mit dem finalModifikator einzugehen.

Wenn eine Variable den finalModifikator hat, muss ihr ein Anfangswert zugewiesen werden. Das wissen Sie bereits und es ist nichts Überraschendes daran.

Was Sie jedoch nicht wissen, ist, dass Sie den Anfangswert nicht sofort zuweisen müssen, wenn Sie ihn im Konstruktor zuweisen. Dies wird für eine endgültige Variable gut funktionieren. Die einzige Voraussetzung besteht darin, dass bei mehreren Konstruktoren in jedem Konstruktor einer endgültigen Variablen ein Wert zugewiesen werden muss.

Beispiel:

public class Cat
{
   public final int maxAge = 25;
   public final int maxWeight;

   public Cat (int weight)
   {
     this.maxWeight = weight; // Assign an initial value to the constant
   }
}


4. Code in einem Konstruktor

Und noch ein paar wichtige Hinweise zu Konstruktoren. Später, wenn Sie Java weiter lernen, werden Sie auf Dinge wie Vererbung, Serialisierung, Ausnahmen usw. stoßen. Sie alle beeinflussen die Arbeit von Konstruktoren in unterschiedlichem Maße. Es macht keinen Sinn, jetzt tief in diese Themen einzutauchen, aber wir sind verpflichtet, sie zumindest anzusprechen.

Hier ist zum Beispiel eine wichtige Bemerkung zu Konstruktoren. Theoretisch können Sie in einem Konstruktor Code beliebiger Komplexität schreiben. Aber tu das nicht. Beispiel:

class FilePrinter
{
   public String content;

   public FilePrinter(String filename) throws Exception
   {
      FileInputStream input = new FileInputStream(filename);
      byte[] buffer = input.readAllBytes();
      this.content = new String(buffer);
   }

   public void printFile()
   {
      System.out.println(content);
   }
}






Öffnen Sie einen Dateilesestream.
Lesen Sie die Datei in ein Byte-Array.
Speichern Sie das Byte-Array als Zeichenfolge.




Zeigen Sie den Inhalt der Datei auf dem Bildschirm an

Im FilePrinter-Klassenkonstruktor haben wir sofort einen Bytestream für eine Datei geöffnet und deren Inhalt gelesen. Dies ist ein komplexes Verhalten und kann zu Fehlern führen.

Was wäre, wenn es keine solche Datei gäbe? Was passiert, wenn beim Lesen der Datei Probleme auftreten? Was wäre, wenn es zu groß wäre?

Komplexe Logik impliziert eine hohe Fehlerwahrscheinlichkeit und das bedeutet, dass der Code Ausnahmen korrekt behandeln muss.

Beispiel 1 – Serialisierung

In einem Standard-Java-Programm gibt es viele Situationen, in denen Sie nicht derjenige sind, der Objekte Ihrer Klasse erstellt. Angenommen, Sie entscheiden sich, ein Objekt über das Netzwerk zu senden: In diesem Fall konvertiert die Java-Maschine selbst Ihr Objekt in eine Menge von Bytes, sendet es und erstellt das Objekt aus der Menge von Bytes neu.

Angenommen, Ihre Datei ist auf dem anderen Computer nicht vorhanden. Im Konstruktor liegt ein Fehler vor, den niemand beheben kann. Und das kann durchaus zum Abbruch des Programms führen.

Beispiel 2 – Felder einer Klasse initialisieren

Wenn Ihr Klassenkonstruktor geprüfte Ausnahmen auslösen kann, also mit dem Schlüsselwort throws gekennzeichnet ist, müssen Sie die angegebenen Ausnahmen in der Methode abfangen, die Ihr Objekt erstellt.

Was aber, wenn es keine solche Methode gibt? Beispiel:

Code  Notiz
class Solution
{
   public FilePrinter reader = new FilePrinter("c:\\readme.txt");
}
Dieser Code lässt sich nicht kompilieren.

Der FilePrinterKlassenkonstruktor kann eine geprüfte Ausnahme auslösen , was bedeutet, dass Sie kein FilePrinterObjekt erstellen können, ohne es in einen Try-Catch-Block einzuschließen. Und ein Try-Catch-Block kann nur in einer Methode geschrieben werden



5. Basisklassenkonstruktor

In den vorherigen Lektionen haben wir uns ein wenig mit der Vererbung befasst. Leider ist unsere vollständige Erörterung von Vererbung und OOP der OOP gewidmeten Ebene vorbehalten, und die Vererbung von Konstruktoren ist für uns bereits relevant.

Wenn Ihre Klasse eine andere Klasse erbt, wird ein Objekt der übergeordneten Klasse in ein Objekt Ihrer Klasse eingebettet. Darüber hinaus verfügt die übergeordnete Klasse über eigene Variablen und eigene Konstruktoren.

Das bedeutet, dass es für Sie sehr wichtig ist zu wissen und zu verstehen, wie Variablen initialisiert und Konstruktoren aufgerufen werden, wenn Ihre Klasse eine übergeordnete Klasse hat und Sie deren Variablen und Methoden erben.

Klassen

Woher wissen wir, in welcher Reihenfolge Variablen initialisiert und Konstruktoren aufgerufen werden? Beginnen wir mit dem Schreiben des Codes für zwei Klassen. Einer wird den anderen erben:

Code Notiz
class ParentClass
{
   public String a;
   public String b;

   public ParentClass()
   {
   }
}

class ChildClass extends ParentClass
{
   public String c;
   public String d;

   public ChildClass()
   {
   }
}










Die ChildClass Klasse erbt die ParentClassKlasse.

Wir müssen die Reihenfolge bestimmen, in der Variablen initialisiert und Konstruktoren aufgerufen werden. Die Protokollierung wird uns dabei helfen.

Protokollierung

Bei der Protokollierung werden die von einem Programm während der Ausführung ausgeführten Aktionen aufgezeichnet, indem sie in die Konsole oder eine Datei geschrieben werden.

Es ist ganz einfach festzustellen, ob der Konstruktor aufgerufen wurde: Schreiben Sie im Hauptteil des Konstruktors eine Nachricht an die Konsole. Aber wie können Sie feststellen, ob eine Variable initialisiert wurde?

Eigentlich ist das auch nicht sehr schwierig: Schreiben Sie eine spezielle Methode, die den zum Initialisieren der Variablen verwendeten Wert zurückgibt, und protokollieren Sie die Initialisierung. So könnte der Code aussehen:

Endgültiger Code

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

   public static String print(String text)
   {
      System.out.println(text);
      return text;
   }
}

class ParentClass
{
   public String a = Main.print("ParentClass.a");
   public String b = Main.print("ParentClass.b");

   public ParentClass()
   {
      Main.print("ParentClass.constructor");
   }
}

class ChildClass extends ParentClass
{
   public String c = Main.print("ChildClass.c");
   public String d = Main.print("ChildClass.d");

   public ChildClass()
   {
      Main.print("ChildClass.constructor");
   }
}




ChildClassObjekt erstellen


Diese Methode schreibt den übergebenen Text in die Konsole und gibt ihn auch zurück.





Deklarieren Sie die ParentClassKlasse

Display text und initialisieren Sie damit auch die Variablen.




Schreiben Sie eine Nachricht, dass der Konstruktor aufgerufen wurde. Ignorieren Sie den Rückgabewert.


Deklarieren Sie die ChildClassKlasse

Display text und initialisieren Sie damit auch die Variablen.




Schreiben Sie eine Nachricht, dass der Konstruktor aufgerufen wurde. Ignorieren Sie den Rückgabewert.

Wenn Sie diesen Code ausführen, wird der Text wie folgt auf dem Bildschirm angezeigt:

Konsolenausgabe der MethodeMain.print()
ParentClass.a
ParentClass.b
ParentClass.constructor
ChildClass.c
ChildClass.d
ChildClass.constructor

So können Sie immer persönlich dafür sorgen, dass die Variablen einer Klasse initialisiert werden, bevor der Konstruktor aufgerufen wird. Eine Basisklasse wird vor der Initialisierung der geerbten Klasse vollständig initialisiert.