Advanced Java Services | Vererbung 2 |
Eine reichhaltige Konstruktorausstattung ist für den Benutzer bequem, aber für den Entwickler der
Klasse ein wenig lästig. Der Rumpf der Konstruktoren ist meist sehr ähnlich ist, so daß in
den Konstruktoren "fast" das gleiche steht. Die Klasse Person braucht etwa neben dem Defaultkonstruktor
eigentlich nur einen Konstruktor der Form Person(String v, String n) . Der Anwender muß dann halt
statt p = new Person("Kandinsky") den Aufruf p = new Person("", "Kandinsky") verwenden.
Es gibt jedoch die Möglichkeit, sich innerhalb eines Konstruktors auf einen anderen Konstruktor
der gleichen Klasse zu beziehen. Dazu dient das Schlüsselwort this , das völlig analog
zu super verwendet wird. Wir demonstrieren dies zunächst mit einer weiteren Variante der
Klasse Person.
public class Person { // Membervariablen private String vor ; private String nach; // Konstruktoren public Person() { this("","") ; } public Person(String n) { this("", n) ; } public Person(String v, String n) { vor = v ; nach = n ; } // .... }
Hier wird mit this( "" , "" ) aus dem Defaultkonstruktor der Konstruktor
Person( String v , String n ) aufgerufen. Analog wird aus dem Konstruktor
Person(String n) mit Hilfe von this( "" , n ) ebenfalls der dritte Konstruktor
aufgerufen.
Zu beachten ist, daß this analog wie super ebenfalls als erstes Statement in einem
Konstruktor stehen muß. Daraus ergibt sich zwingend, daß this und super nicht zusammen
eingesetzt werden können, sondern nur alternativ.
Mit this() bezieht man sich auf einen Konstruktor der gleichen Klasse. Man macht dies hauptsächlich deswegen um Initialisierungsarbeiten, die für jedes Objekt notwendig sind, nur einmal zu codieren. Man erledigt diese Arbeit nur einmal in einem Konstruktor und verweist dann mit this() auf diesen Konstruktor. Seit der Einführung innerer und anonymer Klassen gibt es für dieses Vorgehen eine elegante Variante, den anonymen Konstruktor. Ein anonymer Konstruktor ist ein namenloser Block, der auf der obersten Ebene der Klasse angesiedelt ist. Tieferliegende Blöcke (also Blöcke in Methoden oder Konstruktoren) haben diese Eigenschaft (natürlich) nicht. Betrachten wir das folgende Beispiel:
class Anonymous { // Membervariablen // ... // anonymer Konstruktor { System.out.println("anonymous initializer"); } // Konstruktoren public Anonymous() { System.out.println("Anonymous()"); } public Anonymous(int a) { System.out.println("Anonymous(int a)"); } // Methoden // ... } // end class Anonymous public class AnonymousDemo { public static void main(String[] args) { Anonymous an1 = new Anonymous(); System.out.println("---------------------"); Anonymous an2 = new Anonymous(5); } }
Das kleine Demoprogramm macht folgende Ausgabe:
anonymous initializer Anonymous() --------------------- anonymous initializer Anonymous(int a)
Man sieht, daß der anonyme Konstruktor objektbezogen wirkt und noch vor dem eigentlichen Konstruktor abgearbeitet wird und daß dies für jeden Konstruktor gilt. Damit kann man Initialisierungsarbeiten, die für jedes Objekt anfallen, in diesen namenlosen Initialisierer packen und spart sich damit die Überlegung, auf welchen Konstruktor man sich mit this() beziehen will.
Versucht man am Anfang eines anonymen Konstruktors einen this() oder super()-Aufruf, so erhält man die Fehlermeldung: call to this must be first statement in constructor. Daraus kann man den folgenden Schluß ziehen: Existiert ein namenloser Block, so ergänzt der Compiler diesen Block unmittelbar nach einem super() oder this()- Aufruf, d.h. die folgende Codierung
// anonymer Konstruktor { System.out.println("anonymous initializer"); } // Konstruktoren public Anonymous() { System.out.println("Anonymous()"); } public Anonymous(int a) { System.out.println("Anonymous(int a)"); }
wird vom Compiler so übersetzt:
// Konstruktoren public Anonymous() { // impliziter oder expliziter Aufruf von super() oder this() // anonymer Block wird eingefügt // weitere statements System.out.println("Anonymous()"); } public Anonymous(int a) { // impliziter oder expliziter Aufruf von super() oder this() // anonymer Block wird eingefügt // weitere statements System.out.println("Anonymous(int a)"); }
Dadurch wird sichergestellt, daß für jeden Konstruktor der anonyme Initialisierer nach dem super()-Aufruf und vor allen anderen Statements kommt.
In unseren bisherigen Beispielen war der Datenteil eine Klasse immer objektbezogen. Jedes Objekt, das mit Hilfe eines Konstruktors angelegt wird, enthält dann diesen Datenteil als seine Eigenschaften. Manchmal sind jedoch auch Daten nützlich, die nicht objektbezogen sind, sondern klassenbezogen, also Daten, die nur ein einziges Mal pro Klasse angelegt werden. Solche Daten nennen wir in Zukunft statische Daten. Jedes Objekt der Klasse darf auf sie zugreifen, aber sie gehören zu keinem Objekt, sie gehören eben zur Klasse. Die Deklaration von statischen Daten sieht folgendermaßen aus:
public class StaticDemo { // statischer Datenteil private static int counter ; // nichtstatischer Datenteil private String vor, nach ; // Konstruktoren // ... // Methoden // ... }
Und so kann man sich das im Speicher vorstellen:
Es gibt verschiedene Begriffe, um diesen Unterschied klarzumachen. Statt Datenteil einer Klasse spricht
man auch von Membervariablen und unterscheidet dann zwischen Instanzvariablen und Klassenvariablen. Die
folgende Graphik macht also zweimal dieselbe Untercheidung mit anderen Begriffen.
Nichtstatische Daten werden pro Objekt durch einen Konstruktor initialisiert. Es liegt nahe, etwas ähnliches für statische Daten zu erwarten. Tatsächlich gibt es einen statischen Konstruktor bzw. einen statischen Initialisierer. Wie ein anonymer Block liegt er auf der obersten Ebene der Klasse, wird jedoch durch das Schlüsselwort static eingeleitet:
public class StaticDemo { // statischer Datenteil // nichtstatischer Datenteil //statischer Konstruktor static { // statische Initialisierungen } // Konstruktoren // Methoden }
Dazu betrachten wir zunächst das folgende Beispiel:
public class StaticDemo { public static void main(String[] args) { Static s1 = new Static(); System.out.println("---------------------"); Static s2 = new Static(5); } } class Static { // Membervariablen // ... // ... // statischer Konstruktor static { System.out.println("static initializer"); } // anonymer Konstruktor { System.out.println("anonymous initializer"); } // Konstruktoren public Static() { System.out.println("Static()"); } public Static(int a) { System.out.println("Static(int a)"); } // Methoden // ... // ... }
Es macht die folgende Ausgabe
static initializer anonymous initializer Static() --------------------- anonymous initializer Static(int a)
Der statische Konstruktor wird also einmal vor dem Anlegen des ersten Objekts aufgerufen. Danach nie mehr. Es gibt aber noch eine zweite Situation, in der die statischen Daten angelegt werden, ohne daß überhaupt ein Objekt existiert.
Jetzt, wo wir statische Daten kennengelernt haben, erscheinen die statischen Methoden in einem klareren Licht. Statische Methoden arbeiten mit statischen Daten zusammen. Statische Methoden haben Zugriff auf statische Daten, aber keinen Zugriff auf nichtstatische Daten, da diese objektbezogen sind. Daraus ergibt sich aber auch, daß statische Daten beim ersten Aufruf einer statischen Methode vorhanden sein müssen, also muß der statische Initialisierer vor dem Aufruf abgearbeitet sein. Das folgende Beispiel demonstriert dies:
public class StaticDemo2 { public static void main(String[] args) { Static2 s1; // Hier wird nur eine Referenz auf ein Objekt angelegt Static2.staticMethod(); } } class Static2 { // Membervariablen // ... // ... // statischer Konstruktor static { System.out.println("static initializer"); } // anonymer Konstruktor { System.out.println("anonymous initializer"); } // static method public static void staticMethod() { System.out.println("staticMethod()"); } } // end class Static2
Das Programm macht die folgende Ausgabe
static initializer staticMethod()
Beachten Sie, daß der anonyme Initialisierer nicht abgearbeitet wird ! Ein Standardbeispiel für einen statischen Datenteil ist ein Zähler, der die Anzahl der Objekte einer Klasse zählt. Wir werden das als Übung erledigen.
Ein Counter für die Klasse Person
Reihenfolge der Initialisierung bei Vererbung