vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Woche 1

Tag 7


Mehr über Methoden

Jede Klasse in Java agiert mit Methoden. Methoden sind wohl der wichtigste Teil einer objektorientierten Sprache, da sie jede Aktion, die ein Objekt ausführt, definieren.

Klassen und Objekte stellen ein Gerüst dar. Klassen- und Instanzvariablen bieten eine Möglichkeit zu beschreiben, was diese Klassen und Objekte sind. Allerdings können nur Methoden die Verhaltensweisen eines Objekts beschreiben - sprich die Dinge, die ein Objekt in der Lage ist zu tun, und die Art, mit der es mit anderen Klassen und Objekten kommuniziert.

Gestern haben Sie ein wenig über das Definieren von Methoden gelernt. Mit diesem Grundwissen können Sie bereits verschiedene Java-Programme schreiben, jedoch würden Ihnen einige Merkmale von Methoden fehlen, durch die Java-Programme erst richtig leistungsstark werden. Durch sie werden Ihre Objekte und Klassen effizienter und übersichtlicher. Heute lernen Sie alles über diese zusätzlichen Merkmale, darunter:

Methoden mit dem gleichen Namen, aber anderen
Argumenten erstellen

Wenn Sie mit der Klassenbibliothek von Java arbeiten, werden Sie oft auf Klassen stoßen, die diverse Methoden mit demselben Namen besitzen. Die Klasse java.lang.String z.B. verfügt über einige verschiedene valueOf()-Methoden.

Methoden mit demselben Namen werden durch zwei Dinge voneinander unterschieden:

Diese beiden Dinge bilden die Signatur einer Methode. Mehrere Methoden zu verwenden, die alle denselben Namen, aber unterschiedliche Signaturen haben, wird als überladen (engl. overloading) bezeichnet.

In dem Beispiel der Klasse String von oben verarbeiten die verschiedenen überladenen Versionen der valueOf()-Methode unterschiedliche Datentypen als Parameter.

Durch die Überladung von Methoden besteht kein Bedarf mehr für völlig verschiedene Methoden, die im wesentlichen dasselbe tun. Über die Überladung wird es auch möglich, daß sich Methoden in Abhängigkeit von den erhaltenen Argumenten unterschiedlich verhalten.

Die überladene Methode valueOf() in der Klasse String kann zur Konvertierung einer ganzen Reihe unterschiedlicher Datentypen und Objekte in String-Werte verwendet werden. Wenn Sie eine Methode in einem Objekt aufrufen, überprüft Java den Methodennamen und die Argumente, um zu ermitteln, welche Methodendefinition auszuführen ist.

Um eine Methode zu überladen, legen Sie lediglich mehrere unterschiedliche Methodendefinitionen in einer Klasse an, die alle den gleichen Namen, jedoch unterschiedliche Parameter (entweder in bezug auf die Zahl oder den Typ der Argumente) und unterschiedliche Rümpfe haben. Java erkennt überladene Methoden daran, daß die einzelnen Parameterlisten für jeden Methodennamen eindeutig sind.

Java unterscheidet überladene Methoden mit dem gleichen Namen anhand der Zahl und des Typs der Parameter für die jeweilige Methode, nicht anhand des Rückgabetyps. Das heißt, wenn Sie zwei Methoden mit dem gleichen Namen, der gleichen Parameterliste, jedoch unterschiedlichen Rückgabetypen erstellen, erhalten Sie einen Compilerfehler. Die für jeden Parameter einer Methode gewählten Variablennamen sind nicht relevant, nur die Zahl und der Typ zählen.

Im folgenden Beispiel wird eine überladene Methode erstellt. Listing 7.1 zeigt eine einfache Klassendefinition für die Klasse MyRect, die ein Rechteck definiert. Die Klasse MyRect hat vier Instanzvariablen, die die obere linke und untere rechte Ecke des Rechtecks definieren: x1, y1, x2 und y2.


Warum habe ich die Klasse MyRect genannt? Im awt-Paket von Java ist eine Klasse namens Rectangle enthalten, die einen Großteil des gleichen Verhaltens implementiert. Um zu vermeiden, daß die zwei Klassen verwechselt werden, habe ich diese Klasse MyRect genannt.

Listing 7.1: Die Anfänge der Klasse MyRect

1: class MyRect {
2:     int x1 = 0;
3:     int y1 = 0;
4:     int x2 = 0;
5:     int y2 = 0;
6: }


Versuchen Sie nicht, dieses Beispiel an dieser Stelle bereits zu kompilieren. Es wird zwar kompiliert werden, ohne Fehler zu erzeugen, allerdings wird es nicht laufen, da es (noch) keine main()-Methode besitzt. Wenn die Klassendefinition fertig ist, wird die endgültige Version sich sowohl kompilieren als auch ausführen lassen.

Wird eine neue Instanz von der Klasse MyRect erstellt, werden alle ihre Instanzvariablen mit 0 initialisiert. Wir definieren nun die Methode buildRect(), die die Größe des Rechtecks anhand von vier Integer-Argumenten auf die entsprechenden Werte abändert, so daß das Reckteckobjekt richtig ausgegeben wird (da die Argumente den gleichen Namen als Instanzvariablen haben, müssen Sie sicherstellen, daß auf sie mit this verwiesen wird):

MyRect buildRect (int x1, int y1, int x2, int y2) {
this.x1 = x1;
this.y1 = y1;
this.x2 = x2;
this.y2 = y2;
return this;
}

Was, wenn man nun die Dimensionen des Rechtecks auf andere Weise definieren will, z.B. durch Verwendung von Point-Objekten anstelle der einzelnen Koordinaten? In diesem Fall überladen Sie buildRect(), so daß dessen Parameterliste zwei Point-Objekte erhält:

MyRect buildRect (Point topLeft, Point bottomRight) {
x1 = topLeft.x;
y1 = topLeft.y;
x2 = bottomRight.x;
y2 = bottomRight.y;
return this;
}

Damit die obige Methode funktioniert, müssen Sie die Point-Klasse an den Anfang Ihrer Quelldatei importieren, so daß Java sie finden kann.

Eventuell möchten Sie das Rechteck mit einer oberen Ecke sowie einer bestimmten Breite und Höhe definieren. Hierfür schreiben Sie einfach eine andere Definition für buildRect():

MyRect buildRect (Point topLeft, int w, int h) {
x1 = topLeft.x;
y1 = topLeft.y;
x2 = (x1 + w);
y2 = (y1 + h);
return this;
}

Um dieses Beispiel zu beenden, erstellen wir eine Methode printRect() zum Ausgeben der Koordinaten des Recktecks und eine main()-Methode zum Testen aller Werte (einfach um zu beweisen, daß das tatsächlich funktioniert). Listing 7.2 zeigt die fertige Klassendefinition mit allen Methoden: drei buildRect()-Methoden, eine printRect() -Methode und eine main()-Methode.

Listing 7.2: Der komplette Quelltext von MyRect.java

 1: import java.awt.Point;
 2:
 3: class MyRect {
 4:     int x1 = 0;
 5:     int y1 = 0;
 6:     int x2 = 0;
 7:     int y2 = 0;
 8:
 9:     MyRect buildRect(int x1, int y1, int x2, int y2) {
10:         this.x1 = x1;
11:         this.y1 = y1;
12:         this.x2 = x2;
13:         this.y2 = y2;
14:         return this;
15:     }
16:
17:     MyRect buildRect(Point topLeft, Point bottomRight) {
18:         x1 = topLeft.x;
19:         y1 = topLeft.y;
20:         x2 = bottomRight.x;
21:         y2 = bottomRight.y;
22:         return this;
23:     }
24:
25:     MyRect buildRect(Point topLeft, int w, int h) {
26:         x1 = topLeft.x;
27:         y1 = topLeft.y;
28:         x2 = (x1 + w);
29:         y2 = (y1 + h);
30:         return this;
31:     }
32:
33:     void printRect(){
34:         System.out.print("MyRect: <" + x1 + ", " + y1);
35:         System.out.println(", " + x2 + ", " + y2 + ">");
36:     }
37:
38:     public static void main(String arguments[]) {
39:         MyRect rect = new MyRect();
40:
41:         System.out.println("Calling buildRect with coordinates 25,25,
            å 50,50:");
42:         rect.buildRect(25, 25, 50, 50);
43:         rect.printRect();
44:         System.out.println("***");
45:
46:         System.out.println("Calling buildRect with points (10,10), (20,20):");
47:         rect.buildRect(new Point(10,10), new Point(20,20));
48:         rect.printRect();
49:         System.out.println("***");
50:
51:         System.out.print("Calling buildRect with 1 point (10,10),");
52:         System.out.println(" width (50) and height (50):");
53:
54:         rect.buildRect(new Point(10,10), 50, 50);
55:         rect.printRect();
56:         System.out.println("***");
57:    }
58: }

Die Ausgabe dieses Java-Programms ist wie folgt:

Calling buildRect with coordinates 25,25, 50,50:
MyRect: <25, 25, 50, 50>
***
Calling buildRect with points (10,10), (20,20):
MyRect: <10, 10, 20, 20>
***
Calling buildRect with 1 point (10,10), width (50) and height (50):
MyRect: <10, 10, 60, 60>
***

Wie Sie an diesem Beispiel sehen, funktionieren alle buildRect()-Methoden auf der Grundlage der Argumente, mit denen sie aufgerufen werden. Sie können in Ihren Klassen beliebig viele Versionen einer Methode definieren, um das für die jeweilige Klasse benötigte Verhalten zu implementieren.

Konstruktor-Methoden

Zusätzlich zu den üblichen Methoden können Sie in einer Klassendefinition auch Konstruktor-Methoden definieren.


Eine Konstruktor-Methode oder auch nur Konstruktor ist eine besondere Methodenart, die beim Erstellen eines Objekts aufgerufen wird - mit anderen Worten, wenn ein Objekt konstruiert wird.

Im Gegensatz zu den üblichen Methoden können Sie einen Konstruktor nicht direkt aufrufen. Konstruktoren werden statt dessen von Java automatisch aufgerufen.

Wenn mit new eine neue Klasseninstanz erstellt wird, führt Java drei Aktionen aus:

Auch wenn für eine Klasse keine speziellen Konstruktoren definiert wurden, erhalten Sie trotzdem ein Objekt, müssen aber seine Instanzvariablen setzen oder andere Methoden aufrufen, die das Objekt zur Initialisierung braucht. Alle Beispiele, die Sie bisher geschrieben haben, verhalten sich so.

Durch Definieren von Konstruktoren in Ihren Klassen können Sie Anfangswerte von Instanzvariablen setzen, Methoden anhand dieser Variablen oder Methoden für andere Objekte aufrufen und die anfänglichen Eigenschaften Ihres Objekts bestimmen. Ferner können Sie Konstruktoren überladen wie übliche Methoden, um ein Objekt zu erstellen, das die spezifischen Merkmale entsprechend den in new festgelegten Argumenten aufweist.

Basis-Konstruktoren

Konstruktoren sehen wie übliche Methoden aus, unterscheiden sich von diesen aber durch zwei Merkmale:

Listing 7.3 ist ein Beispiel mit einer einfachen Klasse namens Person. Diese Klasse verfügt über einen Konstruktor, der die Instanzvariablen der Klasse anhand der Argumente von new initialisiert. Ferner beinhaltet die Klasse eine Methode für das Objekt, damit es sich selbst vorstellen kann.

Listing 7.3: Die Klasse Person

 1: class Person {
 2:     String name;
 3:     int age;
 4:
 5:     Person(String n, int a) {
 6:         name = n;
 7:         age = a;
 8:     }
 9:
10:     void printPerson() {
11:         System.out.print("Hi, my name is " + name);
12:         System.out.println(". I am " + age + " years old.");
13:     }
14:
15:     public static void main (String args[]) {
16:         Person p;
17:         p = new Person("Luke", 50);
18:         p.printPerson();
19:         System.out.println("----");
20:         p = new Person("Laura", 35);
21:         p.printPerson();
22:         System.out.println("----");
23:     }
24: }

Die Ausgabe dieses Beispiels ist wie folgt:

Hi, my name is Luke. I am 50 years old.
----
Hi, my name is Laura. I am 35 years old.
----

Die Klasse Person besitzt drei Methoden: Die erste ist der Konstruktor, der in den Zeilen 5 bis 8 definiert wird. Dieser initialisiert die beiden Instanzvariablen der Klasse mit den Werten der Argumente, die beim Erstellen einer Instanz der Klasse mit new übergeben werden. Die Klasse Person verfügt außerdem über die Methode printPerson() , so daß das Objekt sich selbst vorstellen kann. Schließlich gibt es noch eine main()-Methode, um die einzelnen Teile zu testen.

Aufrufen eines anderen Konstruktors

In manchen Fällen werden Sie eine Konstruktor-Methode definieren, die das Verhalten einer bereits vorhandenen Konstruktor-Methode dupliziert und gleichzeitig um bestimmte neue Verhaltensweisen erweitert. Anstatt nun die identischen Verhaltensweisen in diverse Konstruktor-Methoden in Ihrer Klasse zu kopieren, können Sie die erste Konstruktor-Methode aus dem Rumpf der anderen aufrufen. Java stellt hierfür eine spezielle Syntax zur Verfügung. Sie verwenden folgende Form, um einen Konstruktor, der in der aktuellen Klasse definiert ist, aufzurufen:

this(arg1, arg2, arg3...);

Die Verwendung von this in bezug auf eine Konstruktor-Methode ist ganz ähnlich wie beim Zugriff auf die Variablen des aktuellen Objekts mit this. Die für this() verwendeten Argumente sind selbstverständlich die Argumente des Konstruktors. Der Konstruktor der Klasse Person aus Listing 7.2 könnte z.B. wie folgt aus einem anderen Konstruktor heraus aufgerufen werden:

this(n, a);

n ist dabei der String, der den Namen des Person-Objekts enthält, und a der Integer, der das Alter angibt.

Konstruktoren überladen

Wie die üblichen Methoden können auch Konstruktoren überladen werden, also verschiedene Anzahl und Typen von Parametern annehmen. Dies gibt Ihnen die Möglichkeit, Objekte mit genau den gewünschten Eigenschaften zu erstellen, und Sie sind in der Lage, Eigenschaften aus verschiedenen Eingaben zu berechnen.

Beispielsweise sind die Methoden buildRect(), die Sie heute in der MyRect-Klasse definiert haben, ausgezeichnete Konstruktoren, weil sie die Instanzvariablen eines Objekts auf die entsprechenden Werte initialisieren. Das bedeutet, daß Sie anstelle der ursprünglich definierten Methode buildRect() (die vier Parameter für die Eckkoordinaten heranzieht) einen Konstruktor erstellen können. In Listing 7.4 wird die neue Klasse MyRect2 aufgeführt, die die gleiche Funktionalität aufweist wie die ursprüngliche Klasse MyRect. Jedoch wurde sie anstelle der Methode buildRect() mit überladenen Konstruktor-Methoden angelegt.

Listing 7.4: Der gesamte Quelltext von MyRect2.java

 1: import java.awt.Point;
 2:
 3: class MyRect2 {
 4:     int x1 = 0;
 5:     int y1 = 0;
 6:     int x2 = 0;
 7:     int y2 = 0;
 8:
 9:     MyRect2(int x1, int y1, int x2, int y2) {
10:         this.x1 = x1;
11:         this.y1 = y1;
12:         this.x2 = x2;
13:         this.y2 = y2;
14:     }
15:
16:     MyRect2(Point topLeft, Point bottomRight) {
17:         x1 = topLeft.x;
18:         y1 = topLeft.y;
19:         x2 = bottomRight.x;
20:         y2 = bottomRight.y;
21:     }
22:
23:     MyRect2(Point topLeft, int w, int h) {
24:         x1 = topLeft.x;
25:         y1 = topLeft.y;
26:         x2 = (x1 + w);
27:         y2 = (y1 + h);
28:     }
29:
30:     void printRect() {
31:         System.out.print("MyRect: <" + x1 + ", " + y1);
32:         System.out.println(", " + x2 + ", " + y2 + ">");
33:     }
34:
35:     public static void main(String arguments[]) {
36:         MyRect2 rect;
37:
38:         System.out.println("Calling MyRect2 with coordinates 25,25 50,50:");
39:         rect = new MyRect2(25, 25, 50,50);
40:         rect.printRect();
41:         System.out.println("***");
42:
43:         System.out.println("Calling MyRect2 with points (10,10), (20,20):");
44:         rect= new MyRect2(new Point(10,10), new Point(20,20));
45:         rect.printRect();
46:         System.out.println("***");
47:
48:         System.out.print("Calling MyRect2 with 1 point (10,10)");
49:         System.out.println(" width (50) and height (50):");
50:         rect = new MyRect2(new Point(10,10), 50, 50);
51:         rect.printRect();
52:         System.out.println("***");
53:
54:     }
55: }

Nachfolgend sehen Sie die von diesem Beispielprogramm produzierte Ausgabe (sie ist mit der des vorherigen Beispiels identisch, nur der dafür nötige Code wurde geändert):

Calling MyRect2 with coordinates 25,25 50,50:
MyRect: <25, 25, 50, 50>
***
Calling MyRect2 with points (10,10), (20,20):
MyRect: <10, 10, 20, 20>
***
Calling MyRect2 with 1 point (10,10) width (50) and height (50):
MyRect: <10, 10, 60, 60>
***

Methoden überschreiben

Wenn Sie eine Methode in einem Objekt aufrufen, sucht Java nach der Methodendefinition in der Klasse dieses Objekts. Falls es keine übereinstimmende Signatur findet, wird der Methodenaufruf in der Klassenhierarchie nach oben weitergereicht, bis eine passende Methodendefinition gefunden wird. Durch die in Java implementierte Methodenvererbung können Sie Methoden wiederholt in Subklassen definieren und verwenden, ohne den Code an sich duplizieren zu müssen.

Zuweilen soll ein Objekt aber auf die gleichen Methoden reagieren, jedoch beim Aufrufen der jeweiligen Methode ein anderes Verhalten aufweisen. In diesem Fall können Sie die Methode überschreiben. Durch Überschreiben von Methoden definieren Sie eine Methode in einer Subklasse, die die gleiche Signatur hat wie eine Methode in einer Superklasse. Dann wird zum Zeitpunkt des Aufrufs nicht die Methode in der Superklasse, sondern die in der Subklasse ermittelt und ausgeführt.

Erstellen von Methoden, die andere überschreiben

Um eine Methode zu überschreiben, erstellen Sie eine Methode in der Subklasse, die die gleiche Signatur (Name, Rückgabetyp und Parameterliste) hat wie eine Methode, die in einer Superklasse der betreffenden Klasse definiert wurde. Da Java die erste Methodendefinition ausführt, die es findet und die mit der Signatur übereinstimmt, wird die ursprüngliche Methodendefinition dadurch »verborgen«. Wir betrachten im folgenden ein einfaches Beispiel. Listing 7.5 zeigt eine einfache Klasse mit der Methode printMe(), die den Namen der Klasse und die Werte ihrer Instanzvariablen ausgibt.

Listing 7.5: Der Quelltext von PrintClass.java

 1: class PrintClass {
 2:     int x = 0;
 3:     int y = 1;
 4:
 5:     void printMe() {
 6:         System.out.println("x is " + x + ", y is " + y);
 7:         System.out.println("I am an instance of the class " +
 8:         this.getClass().getName());
 9:     }
10: }

Listing 7.6 umfaßt eine Klasse namens PrintSubClass, die eine Subklasse von PrintClass ist. Der einzige Unterschied zwischen PrintClass und PrintSubClass ist, daß letztere die Instanzvariable z hat.

Listing 7.6: Der Quelltext von PrintSubClass.java

 1: class PrintSubClass extends PrintClass {
 2:     int z = 3;
 3:
 4:     public static void main(String args[]) {
 5:         PrintSubClass obj = new PrintSubClass();
 6:         obj.printMe();
 7:     }
 8: }

Im folgenden die Ausgabe von PrintSubClass:

X is 0, Y is 1
I am an instance of the class PrintSubClass

In der Methode main() von PrintSubClass erstellen Sie ein PrintSubClass-Objekt und rufen die Methode printMe() auf. Beachten Sie, daß PrintSubClass diese Methode nicht definiert; deshalb sucht Java in allen Superklassen von PrintSubClass danach und findet sie in diesem Fall in PrintClass. Leider wird die Instanzvariable z nicht ausgegeben, wie Sie in der Ausgabe oben sehen können.


Die Klasse PrintClass besitzt ein wichtiges Feature, auf das hier eingegangen werden soll: Sie hat keine main()-Methode. Sie braucht auch keine, da sie keine Applikation ist. PrintClass ist lediglich eine Hilfsklasse für die Klasse PrintSubClass, die eine Applikation ist und deshalb auch über eine main()-Methode verfügt. Nur die Klasse, die Sie mit dem Java-Interpreter ausführen, benötigt eine main()-Methode.

Wir erstellen nun eine dritte Klasse. PrintSubClass2 ist fast mit PrintSubClass identisch, jedoch wird die Methode printMe() überschrieben, um die Variable z einzubinden. Listing 7.7 zeigt diese Klasse.

Listing 7.7: Die PrintSubClass2-Klasse

 1: class PrintSubClass2 extends PrintClass {
 2:     int z = 3;
 3:
 4:     void printMe() {
 5:         System.out.println("x is " + x + ", y is " + y +
 6:                ", z is " + z);
 7:         System.out.println("I am an instance of the class " +
 8:                this.getClass().getName());
 9:     }
10:
11:     public static void main(String args[]) {
12:         PrintSubClass2 obj = new PrintSubClass2();
13:         obj.printMe();
14:     }
15: }

Wenn Sie diese Klasse instanzieren und die Methode printMe() aufrufen, wird diesmal die Version von printMe(), die Sie für diese Klasse definiert haben, und nicht diejenige, die sich in der Superklasse PrintClass befindet, aufgerufen.

Das sehen Sie an folgender Ausgabe:

x is 0, y is 1, z is 3
I am an instance of the class PrintSubClass2

Aufrufen der Originalmethode

Normalerweise gibt es zwei Gründe dafür, warum man eine Methode, die in einer Superklasse bereits implementiert ist, überschreiben will:

Den ersten Grund haben Sie bereits kennengelernt. Durch Überladen einer Methode und Schreiben einer neuen Definition für diese Methode haben Sie die ursprüngliche Definition der Methode verborgen. Zuweilen will man aber auch die ursprüngliche Definition nicht ganz ersetzen, sondern vielmehr um zusätzliche Eigenschaften erweitern. Das ist besonders nützlich, wenn es darauf hinausläuft, Verhaltensweisen der Originalmethode in der neuen Definition zu duplizieren. Sie können die Originalmethode im Rumpf der überschriebenen Methode aufrufen und alles hinzufügen, was Sie benötigen.

Um die Originalmethode innerhalb einer Methodendefinition aufzurufen, benutzen Sie das Schlüsselwort super, um den Methodenaufruf in der Hierarchie nach oben weiterzugeben:

void myMethod (String a, String b) {
// Hier irgendwas ausführen
super.myMethod(a, b);
// Eventuell weitere Anweisungen
}

Wie this ist auch das Schlüsselwort super ein Platzhalter - in diesem Fall für die Superklasse dieser Klasse. Sie können es überall dort verwenden, wo Sie auch this verwenden. super verweist allerdings auf die Superklasse und nicht auf die aktuelle Klasse.

In Listing 7.8 sehen Sie die printMe()-Methoden aus dem vorherigen Beispiel.

Listing 7.8: Die printMe-Methoden

1: // aus PrintClass
2: void printMe() {
3:         System.out.println("x is " + x + ", y is " + y);
4:         System.out.println("I am an instance of the class" +
5:                this.getClass().getName());
6:     }
7: }
8:
9: // aus PrintSubClass2
10:     void printMe() {
11:         System.out.println("x is " + x + ", y is " + y + ", z is " + z);
12:         System.out.println("I am an instance of the class " +
13:                this.getClass().getName());
14:     }

Anstatt den Großteil des Verhaltens von der Methode der Superklasse in die Subklasse zu duplizieren, können Sie die Methode der Superklasse so umstellen, daß zusätzliche Eigenschaften mühelos hinzugefügt werden können:

// von PrintClass
void printMe() {
System.out.println("I am an instance of the class" +
this.getClass().getName());
System.out.println("X is " + x);
System.out.println("Y is " + y);
}

Beim Überschreiben von printMe() können Sie dann in der Superklasse die Originalmethode aufrufen und alles hinzufügen, was Sie benötigen:

// von PrintSubClass2
void printMe() {
super.printMe();
System.out.println("Z is " + z);
}

Die Ausgabe des Aufrufs von printMe() in einer Instanz der Superklasse sieht so aus:

I am an instance of the class PrintSubClass2
X is 0
Y is 1
Z is 3

Konstruktoren überschreiben

Konstruktoren können technisch nicht überschrieben werden. Da sie immer den gleichen Namen haben wie die aktuelle Klasse, werden Konstruktoren nicht vererbt, sondern immer neu erstellt. Das ist in den meisten Fällen sinnvoll, denn wenn ein Konstruktor Ihrer Klasse aufgerufen wird, wird gleichzeitig auch der Konstruktor mit der gleichen Signatur aller Superklassen aktiviert, so daß eventuell alle Teile einer Klasse initialisiert werden.

Andererseits möchten Sie eventuell beim Definieren von Konstruktoren für eine Klasse einiges ändern, z.B. wie ein Objekt initialisiert wird. Eventuell soll es nicht durch Initialisieren der Informationen, die Ihre Klasse zusätzlich einbringt, sondern durch Änderung der bereits vorhandenen Informationen initialisiert werden. Sie erreichen das, indem Sie die Konstruktoren Ihrer Superklasse explizit aufrufen.

Um eine normale Methode in einer Superklasse aufzurufen, benutzen Sie super.methodname(argumente) . Da Sie bei Konstruktoren keinen Methodennamen aufrufen müssen, wenden Sie hier eine andere Form an:

super(arg1, arg2, ...);

Beachten Sie allerdings, daß es in Java eine ganz spezielle Regel für die Verwendung von super() gibt: Es muß die allererste Anweisung in der Definition Ihres Konstruktors sein. Wenn Sie super() nicht explizit in Ihrem Konstruktor aufrufen, dann erledigt Java dies für Sie und verwendet dafür super() ohne Argumente.

Ebenso wie bei der Verwendung von this(...) bewirkt super(...) in Konstruktor- Methoden den Aufruf der Konstruktor-Methode für die unmittelbare Superklasse (die ihrerseits den Konstruktor ihrer Superklasse aufrufen kann usw.). Beachten Sie bitte, daß in der Superklasse ein Konstruktor mit der entsprechenden Signatur vorhanden sein muß, damit der Aufruf von super() funktioniert. Der Java-Compiler überprüft dies, wenn er versucht, die Quelldatei zu kompilieren.

Den Konstruktor in der Superklasse Ihrer Klasse mit derselben Signatur müssen Sie nicht extra aufrufen. Sie müssen lediglich den Konstruktor für die Werte aufrufen, die initialisiert werden müssen. Sie können sogar eine Klasse erstellen, die über Konstruktoren mit total anderen Signaturen als die Konstruktoren in einer der Superklassen verfügt.

Das Beispiel in Listing 7.9 zeigt die Klasse NamedPoint, die sich von der Klasse Point aus dem awt-Paket von Java ableitet. Die Point-Klasse hat nur einen Konstruktor, der die Argumente x und y entgegennimmt und ein Point-Objekt zurückgibt. NamedPoint hat eine zusätzliche Instanzvariable (einen String für den Namen) und definiert einen Konstruktor, um x, y und den Namen zu initialisieren.

Listing 7.9: Die NamedPoint-Klasse

 1: import java.awt.Point;
 2: class NamedPoint extends Point {
 3:     String name;
 4:
 5:     NamedPoint(int x, int y, String name) {
 6:        super(x,y);
 7:        this.name = name;
 8:     }
 9:     public static void main (String arg[]) {
10:      NamedPoint np = new NamedPoint(5, 5, "SmallPoint");
11:      System.out.println("x is " + np.x);
12:      System.out.println("y is " + np.y);
13:      System.out.println("Name is " + np.name);
14:    }
15: }

Das Programm liefert die folgende Ausgabe:

x is 5
y is 5
Name is SmallPoint

Der hier für NamedPoint definierte Konstruktor ruft die Konstruktor-Methode von Point auf, um die Instanzvariablen (x und y) von Point zu initialisieren. Obwohl Sie x und y ebensogut selbst initialisieren könnten, wissen Sie eventuell nicht, ob Point noch andere Operationen ausführt, um sich zu initialisieren. Deshalb ist es immer ratsam, Konstruktor-Methoden nach oben in der Hierarchie weiterzugeben, um sicherzustellen, daß alles richtig gesetzt wird.

Finalizer-Methoden

Finalizer-Methoden sind in gewissem Sinn das Gegenstück zu Konstruktor-Methoden. Während eine Konstruktor-Methode benutzt wird, um ein Objekt zu initialisieren, werden Finalizer-Methoden aufgerufen, kurz bevor das Objekt im Papierkorb landet und sein Speicher zurückgefordert wird.

Die Finalizer-Methode hat den Namen finalize(). Die Klasse Object definiert eine Standard-Finalizer-Methode, die keine Funktionalität hat. Um eine Finalizer-Methode zu erstellen, tragen Sie eine Methode mit der folgenden Signatur in Ihre Klassendefinition ein:

protected void finalize() throws Throwable{
super.finalize
}


Der Teil throws Throwable dieser Methodendefinition bezieht sich auf die Fehler, die nach dem Aufruf dieser Methode eventuell auftreten. Fehler werden in Java Exceptions oder zu deutsch Ausnahmen genannt. Am Tag 17 lernen Sie mehr darüber. Vorerst reicht es, daß Sie diese Schlüsselwörter in die Methodendefinition aufnehmen.

Im Körper dieser finalize()-Methode können Sie alle möglichen »Aufräumprozeduren« einbinden, die das Objekt ausführen soll. Sie können super.finalize() aufrufen, um die Aufräumarbeiten, wenn nötig, an die Superklasse Ihrer Klasse zu delegieren. (Dieses ist im allgemeinen recht sinnvoll, um es allen Beteiligten zu ermöglichen, das Objekt zu bearbeiten, wenn dies notwendig sein sollte.)

Sie können die Methode finalize() jederzeit auch selbst aufrufen. Es handelt sich um eine einfache Methode wie alle anderen. Allerdings sorgt der Aufruf von finalize() nicht dafür, daß der Garbage Collector für dieses Objekt aktiv wird. Nur durch Entfernen aller Referenzen auf das Objekt wird das Objekt zum Löschen markiert.

Finalizer-Methoden eignen sich am besten zur Optimierung des Entfernens von Objekten, z.B. durch Löschen aller Referenzen auf andere Objekte. In den meisten Fällen benötigen Sie finalize() überhaupt nicht.

Zusammenfassung

Heute haben Sie verschiedene Techniken zum Benutzen, Wiederverwenden, Definieren und Neudefinieren von Methoden kennengelernt. Sie haben gelernt, wie man durch Überladen eines Methodennamens die gleiche Methode mit einem anderen Verhalten auf der Grundlage der Argumente, durch die sie aufgerufen wird, definiert. Sie haben viel über Konstruktor-Methoden gelernt, die benutzt werden, um ein neues Objekt beim Erstellen zu initialisieren. Sie haben mehr über Methodenvererbung erfahren und gelernt, wie Methoden in Subklassen einer Klasse durch Überschreiben definiert werden können. Außerdem haben Sie gelernt, daß Sie mit Finalizer-Methoden Ihr Programm »aufräumen« können.

Nachdem Sie sich nun einen Tag lang mit Javas Art, mit Methoden umzugehen, beschäftigt haben, sollten Sie bereit sein, Ihre eigenen Programme zu schreiben.

Beginnend mit der nächsten Woche werden Sie ausgefeiltere Programme schreiben, wobei Sie die Techniken von Java 1.02 für Applets und die von Java 1.2 für Applikationen verwenden. Sie werden mit Grafik, grafischen Benutzeroberflächen, Maus- und Tastaturereignissen und Fenstern arbeiten. Dies kann der Einstieg in eine große Zeit sein.

Fragen und Antworten

Frage:
Ich habe zwei Methoden mit folgenden Signaturen geschrieben:

int total(int arg1, int arg2, int arg3) { }
float total(int arg1, int arg2, int arg3) {...}

Der Java-Compiler meckert beim Kompilieren der Klasse mit diesen Methodendefinitionen. Die Signaturen sind doch unterschiedlich. Wo liegt der Fehler?

Antwort:
Das Überladen von Methoden funktioniert in Java nur, wenn sich die Parameterlisten unterscheiden, entweder in der Zahl oder im Typ der Argumente. Die Rückgabetypen sind beim Überladen von Methoden nicht relevant. Wie soll Java bei zwei Methoden mit genau der gleichen Parameterliste wissen, welche aufzurufen ist?

Frage:
Kann ich auch Methoden, die bereits überschrieben wurden, überladen (d.h. kann ich Methoden erstellen, die den gleichen Namen wie eine geerbte Methode, jedoch eine andere Parameterliste haben)?

Antwort:
Sicher! Solange die Parameterliste unterschiedlich ist, spielt es keine Rolle, ob Sie einen neuen Methodennamen oder einen, den Sie aus einer Superklasse geerbt haben, definieren.



vorheriges KapitelInhaltsverzeichnisStichwortverzeichnisFeedbacknächstes Kapitel


Ein Imprint des Markt&Technik Buch- und Software-Verlag GmbH.
Elektronische Fassung des Titels: Java 2 in 21 Tagen, ISBN: 3-8272-5578-3