Konstruktoren und Destruktoren in Java

Konstruktoren und Destruktoren werden in Java verwendet, um Objekte zu initialisieren bzw. zu zerstören (finalize).

Konstruktoren sind als Methoden ohne Rückgabewert in Java implementiert. Sie tragen den Namen ihrer Klasse. Es ist möglich in einer Klasse mehrere verschiedene Konstruktoren zu definieren. Diese müssen sich durch ihre Parameterliste unterscheiden und können wie Methoden überladen werden.

Die Syntax von Konstruktoren ist wie folgt:

public Schaf() // Default-Konstruktor
{
   Anweisungen;
}

public Schaf(String name, byte alter) // Konstruktor mit Parameterliste
{
   Anweisungen;
}

Der Aufruf eines Konstruktors erfolgt immer mit Hilfe des new-Schlüsselworts gefolgt von dem Konstruktornamen. Der Konstruktor liefert automatisch eine Referenz auf das initialisierte Objekt zurück, obwohl der Konstruktor selbst keine return-Anweisung besitzt.

Ein Konstruktor kann auch mit einer Parameterliste wie eine Methode aufgerufen werden, dabei werden die Argumente in dem Klammernpaar nach dem Konstruktornamen angegeben.

Schaf schafObjekt = new Schaf("Cloud", 4);

In dem oberen Java-Code wird zunächst eine Variable der Klasse Schaf deklariert und anschließend das Objekt mit Hilfe des Konstruktors instanziert und initialisiert. Die zwei übergebenen Parameter werden zur Initialisierung an den Konstruktor übergeben.

Der Default-Konstruktor in Java

Wird für eine Klasse kein Konstruktor explizit angegeben, dann generiert der Java-Compiler automatisch den parameterlosen Default-Konstruktor.

Der Default-Konstruktor führt nur eine einzige Aufgabe aus. Er ruft den parameterlosen Konstruktor seiner Superklasse (Vaterklasse) auf.

Der Default-Konstruktor wird nicht erzeugt, wenn in der Klassendeklaration nur parametrisierte Konstruktoren definiert sind. In diesem Sonderfall besitzt die Klasse überhaupt keinen parameterlosen Konstruktor.

plhq_teaser_hbox_gelb_fotolia_RA Studio_46292813

Unser großes
Android Online-Kurs
Gesamtpaket



Weitere Infos

Konstruktoren können in Java verkettet werden

Innerhalb eines Konstruktors kann ein weiterer Konstruktor aufgerufen werden. Der Aufruf muss als erste Anweisung erfolgen. Erfolgt der Aufruf nicht an erster Stelle, wird ein Compiler-Fehler ausgelöst.

Der explizit aufgerufene Konstruktor wird als normale Methode angesehen und mit dem this-Schlüsselwort aufgerufen. Dem this-Schlüsselwort muss ein Klammernpaar folgen, in dem eine Parameterliste stehen kann.

Beispielanwendung für das Verketten von Konstruktoren

/*
* Beispielanwendung für das Verketten von Konstruktoren
*/

public class Schaf
{
  // Deklaration der Instanzvariablen (Membervariablen, Instanzmerkmale)
  String name;
  byte alter;
  float wollMenge;

  // Konstruktor 1
  public Schaf()
  {
     System.out.println("Im parameterlosen Konstruktor 1.");
  }

  // Konstruktor 2
  public Schaf(String name)
  {
     this.name = name;
     System.out.println("Im Konstruktor 2.");
  }

  // Konstruktor 3
  public Schaf(String name, byte alter, float wolle)
  {
     this(name); // verketteter Konstruktoraufruf muss als Erstes erfolgen
     this.alter = alter; // Unterscheidung von Instanzvariable und lokaler
                         // Variable mit Hilfe des this-Punktoperators
     wollMenge  = wolle;
     System.out.println("Im Konstruktor 3.");
  }

  public static void main(String[] args)
  {
     System.out.println("\n--- Instanziere objekt1 ---");
     Schaf objekt1 = new Schaf();

     System.out.println("\n--- Instanziere objekt2 ---");
     Schaf objekt2 = new Schaf("Othello");

     System.out.println("\n--- Instanziere objekt3 ---");
     Schaf objekt3 = new Schaf("Cloud", (byte)4, 2.15F);

     System.out.println();

     objekt1.merkmaleAusgeben("objekt1");
     objekt2.merkmaleAusgeben("objekt2");
     objekt3.merkmaleAusgeben("objekt3");
  }

  public void merkmaleAusgeben(String objektName)
  {
     System.out.println("\n- Ausgabe der Merkmale von " + objektName + " -");
     System.out.println("Name:      " + name);
     System.out.println("Alter:     " + alter + " Jahre");
     System.out.println("Wollmenge: " + wollMenge + " m^2");
  }
}

In der folgenden Abbildung ist die Kommandozeilenausgabe der oben vorgestellten Beispielanwendung „Verkettung von Konstruktoren“ dargestellt:

Java Verkettung von Konstruktoren Beispiel

Java Verkettung von Konstruktoren – Ausgabe der Beispielanwendung

Durch die Verkettung von Konstruktoren kann vorhandener Quellcode wiederverwendet werden.

Oft führt der parameterlose Konstruktor die grundlegenden Initialisierungsaufgaben durch und spezialisierte Konstruktoren mit Parameterlisten nutzen diese Aktionen, indem sie den parameterlosen Konstruktor zu Beginn aufrufen.

Somit muss der Quellcode nicht dupliziert werden, wodurch die Programme robuster und leichter zu warten sind.

Die Initialisierungsreihenfolge von Konstruktoren in Java

Die Instanzierung von Objekten verläuft in Java nach einer genau festgelegten Reihenfolge.

Die folgenden fünf Schritte werden bei jeder Objekt-Instanzierung in Java in der hier angegebenen Reihenfolge ausgeführt:

  1. Aufrufen des Superklassen-Konstruktors (Vaterklasse)
  2. Initialisieren aller Instanzvariablen der Superklasse in der textuellen Reihenfolge
  3. Ausführen der Anweisungen des Superklassen-Konstruktors
  4. Initialisieren aller Instanzvariablen der eigenen Klasse in der textuellen Reihenfolge
  5. Ausführen der Anweisungen des Konstruktors

In der folgenden Beispielanwendung wird die Initialisierungsreihenfolge von Konstruktoren erprobt. So können wir anhand der Konsolenausgabe direkt erkennen was hinter den Kulissen in Java passiert.

Initialisierungsreihenfolge von Konstruktoren

/*
* Beispielanwendung für die Initialisierungsreihenfolge von Konstruktoren
*/

public class Konstruktortest
{
  
  public static String initialisiereVariable(String text)
  {
     System.out.println(text);
     return text;
  }


  public static void main(String[] args)
  {
     System.out.println("\n--- Erzeugen einer Instanz vom Typ Sohnklasse ---");
     Sohnklasse objekt = new Sohnklasse();
  }

}

class GrossVaterklasse
{
  public String meldung = Konstruktortest.initialisiereVariable("\nInstanzvariable der GrossVaterklasse wird initialisiert.");

  public GrossVaterklasse()
  {
     System.out.println("Anweisung im Konstruktor der GrossVaterklasse wird ausgefuehrt.\n");
  }
}

class Vaterklasse extends GrossVaterklasse
{
  public String meldung = Konstruktortest.initialisiereVariable("Instanzvariable der Vaterklasse wird initialisiert.");

  public Vaterklasse()
  {
     System.out.println("Anweisung im Konstruktor der Vaterklasse wird ausgefuehrt.\n");
  }
}

class Sohnklasse extends Vaterklasse
{
  public String meldung = Konstruktortest.initialisiereVariable("Instanzvariable der Sohnklasse wird initialisiert.");

  public Sohnklasse()
  {
     System.out.println("Anweisung im Konstruktor der Sohnklasse wird ausgefuehrt.");
  }
}

Die obere Beispielanwendung besteht aus vier Klassen, der Hauptklasse Konstruktortest und den drei inneren Klassen GrossVaterklasse, Vaterklasse und Sohnklasse. Die Sohnklasse ist von der Vaterklasse abgeleitet, siehe Zeile 43: class Sohnklasse extends Vaterklasse. Und die Vaterklasse ist von der GrossVaterklasse abgeleitet, siehe Zeile 33.

In der Hauptklasse Konstruktortest wird eine Instanz der Klasse Sohnklasse erstellt. Dazu wird der parameterlose Konstruktor Sohnklasse objekt = new Sohnklasse(); der Sohnklasse in Zeile 18 aufgerufen. Aufgrund der automatischen Konstruktor-Verkettung in Java wird zunächst zum Konstruktor der Vaterklasse und darin zum Konstruktor der GrossVaterklasse verzweigt.

In der GrossVaterklasse wird als Erstes die Instanzvariable meldung der GrossVaterklasse initialisiert und anschließend wird die Anweisung der Konstruktors des GrossVaterklasse ausgeführt. Danach werden diese beiden Schritte in der Vaterklasse und anschließend in der Sohnklasse ausgeführt.

Es wird also die Instanzvariable meldung der Vaterklasse initialisiert und anschließend die Anweisung des Konstruktors der Vaterklasse ausgeführt. Als Letztes wird die Instanzvariable meldung der Sohnklasse initialisiert und anschließend die Anweisung des Konstruktors der Sohnklasse ausgeführt.

In der folgenden Abbildung ist die Kommandozeilenausgabe der oben vorgestellten Beispielanwendung „Initialisierungsreihenfolge von Konstruktoren“ dargestellt:

Java Initialisierungsreihenfolge von Konstruktoren Beispiel

Java Initialisierungsreihenfolge von Konstruktoren – Ausgabe der Beispielanwendung

Destruktoren in Java – Objekte zerstören mit finalize()

Objekte können in Java erzeugt und auch wieder zerstört werden. Für die Instanzierung (Erstellung) von Objekten werden Konstruktoren verwendet. Das Zerstören von Objekten übernimmt der Destruktor.

Die finalize-Methode stellt den Destruktor in Java dar.

Die parameterlose Methode finalize() wird ausgeführt kurz bevor der Garbage Collector das Objekt zerstört und den vom Objekt belegten Speicher wieder freigibt.

Die Syntax der finalize-Methode ist wie folgt:

protected void finalize()
{
   Anweisungen;
}

Um die finalize-Methode richtig verwenden zu können, muss der Entwickler das Speicherverwaltungskonzept (Garbage Collection) von Java gut verstanden haben.

Garbage Collection mit dem Garbage Collector in Java

In einigen objektorientierten Programmiersprachen muss der Entwickler sich um das Zerstören von Objekten kümmern und dadurch nicht mehr benötigten Speicherplatz wieder freigeben. Diese Art der Speicherverwaltung ist ermüdend und sehr fehleranfällig.

Android Apps Programmieren Online-Kurs

Unser großes
Android Online-Kurs
Gesamtpaket



Weitere Infos

In Java wird der nicht mehr benötigte Speicher automatisch freigegeben, daher müssen Java-Entwickler sich nicht um die Zerstörung der erzeugten Objekte sorgen.

Die Java-Laufzeitumgebung löscht Objekte wenn diese nicht mehr erreichbar sind. Diesen Prozess bezeichnet man als „Garbage Collection„.

Ein Objekt wird von dem Garbage Collector zerstört, wenn keine Referenz mehr auf dieses Objekt verweist.

Dies geschieht auf zwei Arten, entweder indem die Erreichbarkeit des Objekts endet (Methodenvariablen sind nur in den Methoden erreichbar) oder wenn die Referenzvariable manuell auf null gesetzt wird.

Da in einem Programm mehrere Referenzvariablen auf das gleiche Objekt verweisen können, müssen alle diese Referenzen entfernt werden, bevor das Objekt vom Garbage Collector zerstört werden kann.

Die Java-Laufzeitumgebung sorgt dafür, dass der Garbage Collector in periodischen Abständen den nicht benötigten Speicher (Speicher der von Objekten belegt wird, die nicht mehr referenzierbar sind) freigibt. Der Garbage Collector erfüllt diese Aufgabe automatisch, wenn er meint, dass die Zeit dafür reif ist.


Falls es vor dem Zerstören des Objekts notwendig ist noch einige bestimmte Aktionen auszuführen, dann müssen die Anweisungen dafür in der finalize-Methode der Objektklasse angegeben werden. Dies könnte erforderlich sein, falls ein Objekt auf eine nicht Java-Ressource zugegriffen hat und man diesen Zugriff beim Zerstören des Objekts wieder lösen möchte.

Der Garbage Collector in Aktion

In der folgenden Beispielanwendung werden 10000 Instanzen der Klasse Schaf angelegt. Die Variable x enthält die Referenz auf das gerade erzeugte Objekt. Sobald das nächste Objekt erzeugt wurde, ist die Referenz auf das vorherige Objekt nicht mehr vorhanden, da die Referenzvariable x jetzt auf das neue Objekt verweist.

In der main-Methode der Klasse DestruktorTest werden die Systemmethoden System.runFinalization() und System.gc() nach der Erzeugung der Schaf-Objekte aufgerufen. Dadurch wird der Garbage Collector manuell aktiviert und nicht mehr benötigter Speicherplatz, also nicht mehr referenzierbare Objekte, wieder freigegeben.

Der Garbage Collector arbeitet selbständig, wie in den Kommandozeilenausgaben weiter unten zu sehen ist.

Beispielanwendung für die Methode finalize() von Destruktoren

/*
* Beispielanwendung für die Methode finalize() von Destruktoren
*/ 

class DestruktorTest {

  public static void main(String[] args) 
  {
     Runtime laufzeit = Runtime.getRuntime();
     System.out.println("Freier Speicher: " + laufzeit.freeMemory());

     for(int counter=0; counter<10000; counter++) 
     {
       Schaf x = new Schaf (counter);
     }

     speicherAusgeben("vor GarbageCollector-Aufruf", laufzeit.freeMemory());

     System.runFinalization();
     System.gc();


     speicherAusgeben("nach GarbageCollector-Aufruf", laufzeit.freeMemory());
  }


  public static void speicherAusgeben(String zeitpunkt, long speicher)
  {
     System.out.println("Freier Speicher " + zeitpunkt + ": " + speicher);
  }

}


class Schaf 
{

  String info;
  int id;

  Schaf(int i) 
  {
     this.info = new String("Die ID des Schafs ist: " + i);
     this.id = i;
  }

  protected void finalize() 
  {
     System.out.println("Schaf-Objekt mit ID: " + id + " wurde zerstoert.");
  }
}

In der folgenden Abbildung ist die Kommandozeilenausgabe der oben vorgestellten Beispielanwendung “Destruktoren in Java – Die Methode finalize()” dargestellt:

Java Destruktor Objekte zerstören Beispiel

Java Destruktor Objekte zerstören – Ausgabe der Beispielanwendung

In der oberen Abbildung ist zu erkennen, dass zwei Schaf-Objekte vom Garbage Collector zerstört wurden. Dieser Prozess läuft parallel in einem nebenläufigen Thread ab, daher erhält man die Textmeldung zu einem undefinierten Zeitpunkt.

In der unteren Abbildung sind weitere Resultate der Beispielanwendung dargestellt. Es ist leicht zu erkennen wie unterschiedlich die jeweiligen Ergebnisse sind. Manchmal wird kein Objekt zerstört und manchmal gleich mehrere.

Java Destruktor Objekte zerstören Beispiel 2

Java Destruktor Objekte zerstören – Weitere Ausgabe der Beispielanwendung


Comments 4

  1. Pingback: Vererbung und Polymorphismus in Java

  2. Pingback: Klassen in Java: Die Grundlagen der objektorientierten Programmierung

  3. Pingback: Polymorphismus und Konstruktoren - Polymorphe Methodenaufrufe

  4. Pingback: Java-Grundlagen: Vererbung von Konstruktoren / Destruktoren in Java

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht.