Die objektorientierte Programmierung (OOP) ist einer der größten Fortschritte auf dem Gebiet der Programmierung, den es in den letzten Jahren gab. Sie denken vielleicht, es dauere Jahre, alles über das OOP-Paradigma zu lernen, und wie es Ihnen Ihr Leben leichter macht als herkömmliche Programmiertechniken. Aber eigentlich ist das Ganze nicht schwer zu verstehen. Es läuft alles darauf hinaus, Ihre Programme so zu organisieren, daß sie das Modell aus der realen Welt nachbilden.
Heute erhalten Sie einen Überblick über die objektorientierten Programmierkonzepte in Java und wie sie helfen, Ihre Java-Programme zu strukturieren. Wenn Sie bereits vertraut sind mit der objektorientierten Programmierung, wird Ihnen vieles der heutigen Lektion bekannt vorkommen. In diesem Fall können Sie die Lektion schnell durcharbeiten und nur die Java-Beispiele nachvollziehen.
Weil Java eine objektorientierte Sprache ist, werden Sie es mit zahlreichen Objekten zu tun bekommen. Sie erzeugen sie, modifizieren sie, verschieben sie, ändern ihre Variablen, rufen ihre Methoden auf, kombinieren sie mit anderen Objekten - und entwickeln natürlich Klassen und verwenden Ihre eigenen Objekt e. Heute lernen Sie den Umgang mit Java-Objekten kennen und erfahren, wie Sie eigene Klassen erzeugen.
Unter anderem geht es heute um die folgenden Themen:
Sie können in einen Computerladen gehen und sich ein ganzes PC-System aus verschiedenen Komponenten zusammensetzen: ein Motherboard, eine CPU, eine Grafikkarte, eine Festplatte, eine Tastatur usw. Im Idealfall haben Sie nach dem Zusammensetzen aller eigenständigen Einheiten ein System, in dem alle Einheiten zusammenarbeiten, um so zu einem größeren System zu werden, mit dem Sie die Aufgaben ausführen können, für die Sie den Computer gekauft haben.
Intern können die einzelnen Komponenten kompliziert sein und von verschiedenen Herstellern stammen, die unterschiedliche Entwicklungsansätze haben. Aber Sie müssen nicht wissen, wie die einzelnen Komponenten funktionieren, was die Chips auf dem Motherboard machen, oder wie ein »A« an Ihren Bildschirm gesendet wird, wenn Sie die A-Taste drücken. Jede Komponente, die Sie verwenden, ist eine eigenständige Einheit, und wenn Sie das System zusammensetzen, müssen Sie nur darauf achten, wie die Einheiten zusammenarbeiten. Nachdem Sie wissen, wie die Komponenten interagierenst es ganz einfach, das System zusammenzusetzen.
Die objektorientierte Programmierung (OOP) arbeitet auf genau dieselbe Weise. Wenn Sie OOP einsetzen, besteht Ihr Programm aus vielen eigenständigen Komponenten (Objekten), die jeweils eine ganz spezifische Rolle in dem Programm spielen, und die untereinander in vordefinierter Weise kommunizieren.
OOP verhält sich so wie die reale Welt, wo Objekte aus vielen kleineren Objekten zusammengesetzt sind. Diese Möglichkeit, Objekte zu kombinieren, ist jedoch nur ein sehr allgemeiner Aspekt der objektorientierten Programmierung. Die OOP bietet noch viele andere Konzepte und Funktionen, die es einfacher und flexibler machen, Objekte zu erzeugen und zu verwenden. Das wichtigste dieser Konzepte ist die Klasse.
Abbildung 3.1:
Eine Tree-Klasse und Tree-Instanzen
Jede Klasse, die Sie in Java entwickeln, besteht grundsätzlich aus zwei Teilen: Attribute und Verhalten. In diesem Abschnitt lernen Sie diese Konzepte anhand einer hypothetischen Klasse namens Motorcycle kennen.
Attribute sind die einzelnen Aspekte, die ein Objekt von einem anderen unterscheiden und sein Erscheinungsbild, seinen Status oder andere Eigenschaften festlegen. Wir wollen hier die hypothetische Klasse Motorcycle anlegen. Die Attribute eines Motorrads könnten unter anderem sein:
Die Attribute eines Objekts können auch Informationen über seinen Status beinhalten. Sie könnten beispielsweise Funktionen für den Motorzustand (an oder aus) oder den aktuell eingelegten Gang einführen.
Attribute werden durch Variablen definiert. Weil jede Instanz einer Klasse unterschiedliche Werte für ihre Variablen haben kann, werden diese auch als Instanzvariablen bezeichnet.
Neben den Instanzvariablen gibt es Klassenvariablen. Klassenvariablen entsprechen den globalen Variablen, die in allen Instanzen und in der Klasse selbst zur Verfügung stehen. Anders als Instanzvariablen, deren Werte in der Instanz abgelegt sind, werden die Werte der Klassenvariablen in der Klasse gespeichert.
Das Verhalten einer Klasse legt fest, was die Instanzen dieser Klasse tun, um ihren internen Status zu ändern, oder wie diese Instanz auf Nachrichten anderer Klassen oder Objekte reagiert. Verhalten definieren, wie eine Klasse oder ein Objekt mit dem restlichen Programm interagieren kann. Einige Verhalten, die unsere hypothetische Motorcycle-Klasse haben könnten, sind etwa »Maschine starten«, »Maschine abstellen«, Gas geben«, »Gang wechseln« oder »Abwürgen«.
Um das Verhalten eines Objekts zu definieren, erzeugen Sie Methoden, die aussehen wie Funktionen und Prozeduren in anderen Programmiersprachen, und die sich auch ebenso benehmen, die jedoch innerhalb der Klasse definiert sind. Java unterstützt keine Unterprogramme, die außerhalb von Klassen definiert sind, wie es in einigen anderen Sprachen möglich ist.
Wie Instanzvariablen und Klassenvariablen gibt es auch Instanzmethoden und Klassenmethoden. Instanzmethoden (die in der Regel einfach nur als Methoden bezeichnet werden) beziehen sich auf eine Instanz einer Klasse. Klassenmethoden beziehen sich auf die Klasse selbst.
Die Definition von Klassen ist relativ einfach. Sie haben es in den vorigen Kapiteln bereits mehrfach gesehen. Um eine Klasse zu definieren, geben Sie das Schlüsselwort class sowie den Klassennamen an:
classMyClassName {
/* Rumpf der Klasse
}
Wenn diese Klasse eine Unterklasse einer anderen Klasse ist, verwenden Sie das Schlüsselwort extends sowie den Namen der Oberklasse:
class MyClassName extends mySuperClassName {
/* Rumpf der Klasse */
}
Wenn diese Klasse eine spezielle Schnittstelle definiert, verwenden Sie das Schlüsselwort implements und den Namen dieser Schnittstelle:
class MyClassName implements Runnable {
/* Rumpf der Klasse */
}
extends und implements sind optional. Das Schlüsselwort implements bezieht sich nur auf Schnittstellen, die Sie in der nächsten Lektion kennenlernen werden. Heute wollen wir uns mit allen anderen Dingen beschäftigen, aus denen sich eine Klassendefinition zusammensetzt.
Bisher war diese Lektion eher theoretisch. In diesem Beispiel erzeugen Sie ein funktionierendes Beispiel für die Klasse Motorcycle, so daß Sie sehen, wie Instanzvariablen und -methoden in einer Klasse definiert werden. Außerdem entwickeln Sie eine Java-Anwendung, die eine neue Instanz der Klasse Motorcycle anlegt und ihre Instanzvariablen anzeigt. Sie erstellen den Java-Quellcode, der Ihren Entwurf implementiert und führen die Anwendung Motorcycle dann aus.
Herzlichen Glückwunsch! Sie haben eben Ihre erste Java-Klasse erzeugt. Im Moment ist sie zwar noch nicht zu viel nütze, aber sie stellt eine ganz einfache Java-Klasse dar. Um ihr Funktionalität mitzugeben, müssen Sie Instanzvariablen (die Attribute definieren) und Methoden (die Verhalten definieren) definieren.
Um einige Instanzvariablen (Attribute) für diese Klasse zu erzeugen, fügen Sie unmittelbar hinter der ersten Zeile der Klassendefinition die drei folgenden Zeilen ein:
String make;
String color;
boolean engineState;
Hier haben Sie drei Instanzvariablen: zwei davon, make (Marke) und color (Farbe), können String-Objekte aufnehmen. (String ist Teil der zuvor erwähnten Standard-Klassenbibliothek). Die dritte, engineState (Status des Motors) ist ein boolean, der angibt, ob der Motor an oder aus ist.
Jetzt fügen Sie der Klasse ein paar Verhalten (Methoden) hinzu. Ein Motorrad kann alles mögliche, aber damit dieses Beispiel im Rahmen bleibt, wollen wir nur eine Methode einführen, die den Motor startet. Fügen Sie die folgenden Zeilen hinter den Instanzvariablen in Ihrer Klassendefinition ein:
void startEngine() {
if (engineState == true)
System.out.println("Der Motor läuft bereits.");
else {
engineState = true;
System.out.println("Der Motor läuft jetzt.");
}
}
Die Methode startEngine prüft, ob der Motor bereits läuft (engineState == true). Wenn das der Fall ist, gibt sie einfach eine entsprechende Meldung aus. Wenn der Motor noch nicht läuft, ändern sie den Status des Motors auf true und gibt dann eine Meldung aus. Jetzt erzeugen Sie eine Klasse mit spezifischen Verhalten und Attributen.
Bevor Sie diese Klasse in JBuilder implementieren, brauchen Sie noch eine weitere Methode. Die Methode showAtts gibt die Werte der Instanzvariablen der aktuellen Instanz Ihrer Motorcycle-Klasse aus:
void showAtts() {
System.out.println("Dieses Motorrad ist eine blaue "
+ color + " " + make);
if (engineState == true)
System.out.println("Der Motor läuft.");
else System.out.println("Der Motor läuft nicht.");
}
Die Methode showAtts gibt zwei Zeilen auf dem Bildschirm aus: die Marke und die Farbe des Motorrad-Objekts sowie den Status des Motors - an oder aus.
Bevor Sie fortfahren, wollen wir noch einmal auf einen der wichtigsten Unterschiede zwischen Java-Anwendungen und Java-Applets hinweisen. Ein Java-Applet braucht keine main()-Methode, weil es als Unterprogramm des Web-Browsers ausgeführt wird, in dem es angesehen wird. In einer Java-Anwendung dagegen ist der Rumpf des Programms in der main()-Methode enthalten, die bei der Programmausführung als erstes aufgerufen wird. Als nächstes fügen Sie der Klasse Motorcycle die main()-Methode hinzu.
Bevor Sie anfangen, Klassendateien zu erzeugen, legen wir ein Projekt an, in dem sie verwaltet werden. Wählen Sie Datei | Neues Projekt und ändern Sie das Datei-Feld so ab, daß es den Eintrag C:\JBUILDER\myprojects\JIntermediate.jpr enthält. Klicken Sie auf die Schaltfläche Fertigstellen.
Jetzt implementieren Sie die Klasse Motorcycle in JBuilder. Zunächst legen Sie die Quellcode-Datei an. Klicken Sie im AppBrowser auf das Icon Dem Projekt hinzufügen, geben Sie in das Datei-Feld Motorcycle.java ein und klicken Sie auf Öffnen. Klicken Sie im AppBrowser auf das Inhalts-Feld und geben Sie den Code aus Listing 3.1 ein.
1: class Motorcycle {
2:
3: String make;
4: String color;
5: boolean engineState;
6:
7: void startEngine() {
8: if (engineState == true)
9: System.out.println("Der Motor läuft bereits.");
10: else {
11: engineState = true;
12: System.out.println("Der Motor läuft jetzt.");
13: }
14: }
15:
16: void showAtts() {
17: System.out.println("Dieses Motorrad ist eine "
18: + color + " " + make + ".");
19: if (engineState == true)
20: System.out.println("Der Motor ist an.");
21: else System.out.println("Der Motor ist aus.");
22: }
23:
24: public static void main (String args[]) {
25: Motorcycle m = new Motorcycle();
26: m.make = "Yamaha RZ350";
27: m.color = "gelbe";
28: System.out.println("showAtts aufrufen...");
29: m.showAtts();
30: System.out.println("Motor starten...");
31: m.startEngine();
32: System.out.println("showAtts aufrufen...");
33: m.showAtts();
34: System.out.println("Motor starten...");
35: m.startEngine();
36: }
37: }
Wählen Sie Datei | Alles speichern, um das Projekt und den Quellcode zu speichern. Weil Sie ein Programm geschrieben haben, das etwas auf die Standardausgabe schreibt, wählen Sie im Hauptmenü Ansicht | Ausführungsprotokoll, damit Sie diese Ausgabe auch sehen können. Klicken Sie mit der rechten Maustaste auf den Knoten Motorcycle.java im Navigations-Feld, und wählen Sie dann Start im Popup-Menü.
showAtts aufrufen...
Dieses Motorrad ist eine gelbe Yamaha RZ350
Der Motor ist aus.
Motor starten...
Der Motor läuft jetzt.
showAtts aufrufen...
Dieses Motorrad ist eine gelbe Yamaha RZ350
Der Motor läuft.
Motor starten...
Der Motor läuft bereits.
In Zeile 25 erzeugt Motrocycle m = new Motorcycle(); eine neue Instanz der Klasse Motorcycle und legt einen Verweis darauf in der Variablen m ab. Sie wissen, daß Sie in der Regel nicht direkt mit den Klassen Ihrer Java-Programme arbeiten. Statt dessen erzeugen Sie Objekte aus diesen Klassen und rufen dann die Methoden in diesen Objekten auf.
Die Zeilen 26 und 27 setzen die Instanzvariablen für dieses Motorrad-Objekt: Die Marke ist hier Yamaha RZ350 (ein sehr cooles Motorrad aus Mitte der 80er Jahre), und die Farbe ist gelb.
Zeile 29 gibt die Instanzvariablen aus, indem sie die Methode showAtts() aufruft, die in Ihrem Motorrad-Objekt definiert ist, und somit die zweite und dritte Ausgabezeile erzeugt.
Zeile 31 ruft die Methode startEngine() auf, um den Motor zu starten und die fünfte Ausgabezeile zu erzeugen.
Zeile 33 gibt die Instanzvariablen erneut aus, indem sie die Methode showAtts() aufruft und damit die sechste und siebte Ausgabezeile produziert.
Zeile 35 ruft die Methode startEngine() auf, um erneut zu versuchen, den Motor zu starten. Weil dieser jedoch bereits lief, als die Methode aufgerufen wurde, wird die letzte Ausgabezeile erzeugt.
Nachdem Sie jetzt eine erste Vorstellung von Klassen, Objekten, Methoden und Instanzvariablen und ihrer Verwendung in einem Java-Programm haben, wollen wir die Vererbung betrachten. Die Vererbung ist eine der Funktionalitäten, die die objektorientierte Programmierung so leistungsfähig macht.
Durch die Vererbung werden alle Klassen - solche Sie entwickeln, solche aus anderen Klassenbibliotheken und die aus der Standard-Java-Klassenbibliothek - in einer strengen Hierarchie angeordnet (siehe Abbildung 3.2).
Abbildung 3.2:
Eine Klassenhierarchie
Jede Klasse hat eine Oberklasse (die Klasse, die in der Hierarchie über ihr liegt), und jede Klasse kann eine oder mehrere Unterklassen haben (Klassen, die in der Hierarchie unter ihr liegen).
Ganz oben in der Java-Klassenhierarchie befindet sich die Klasse Object, die allgemeinste Klasse der Hierarchie. Die Oberklasse Object definiert das Verhalten, das alle anderen Klassen in der Java-Klassenhierarchie erben. Jede Unterklasse weiter unten in der Hierarchie fügt weitere Informationen ein und wird für eine ganz spezifische Aufgabe zugeschnitten. Sie können sich eine Klassenhierarchie auch so vorstellen, daß ganz oben sehr abstrakte Konzepte definiert werden, die auf dem Weg nach unten zu den Unterklassen immer konkreter werden.
Wenn Sie neue Java-Klassen schreiben, wollen Sie größtenteils eine Klasse erzeugen, die alle Informationen aus einer anderen Klasse besitzt sowie einige zusätzliche Informationen. Beispielsweise könnten Sie eine Version von Button mit eigener, eingebauter Beschriftung erzeugen. Um alle Button-Informationen zu ermitteln, definieren Sie Ihre Klasse einfach so, daß sie von Button erbt. Ihre Klasse erhält automatisch alle Verhalten und Attribute, die in Button (und in ihren Oberklassen) definiert sind, Sie müssen also nur noch das definieren, was Ihre Klasse von Button unterscheidet. Diese Technik für die Definition einer neuen Klasse als Unterschied zwischen der Klasse und ihrer Oberklasse wird auch als Unterklassenbildung bezeichnet.
Wenn Sie sehr viele Klassen erzeugen, ist es sinnvoll, Ihre Klassen nicht nur von der existierenden Klassenhierarchie erben zu lassen, sondern sie auch selbst eine Hierarchie bilden zu lassen. Das macht eine gewisse Vorausplanung erforderlich, wenn Sie die Organisation Ihres Java-Codes entwerfen, aber die Vorteile sind wirklich wesentlich.
Betrachten wir noch einmal die Klasse Motorcycle und nehmen wir an, Sie haben ein Java-Programm geschrieben, das alle Funktionen eines Motorrads implementiert. Es ist fertig, es funktioniert und alles ist in Ordnung. Jetzt bekommen Sie einen neuen Auftrag - Sie sollen die Java-Klasse Car erzeugen. Car und Motorcycle haben viele gemeinsame Eigenschaften. Beide werden durch einen Motor bewegt. Beide haben ein Getriebe, Scheinwerfer und Tachometer. Im ersten Impuls könnten Sie also überlegen, Ihre Motorcycle-Klassendatei zu öffnen und die betreffenden Informationen in Ihre neue Car-Klasse zu kopieren.
Viel sinnvoller ist es, die gemeinsame Information für Car und Motorcycle in einer allgemeineren Klassenhierarchie anzulegen. Das scheint nur für die Klassen Motorcycle und Car sehr viel Aufwand darzustellen, aber wenn Sie erst Fahrräder, Roller, Mopeds, Trucks und anderes hinzufügen, reduziert die Bereitstellung des gemeinsamen Verhaltens in einer wiederverwendbaren Oberklasse den Aufwand ganz erheblich.
Jetzt wollen wir eine Klassenhierarchie anlegen, die diese Aufgabe erfüllen könnte. Ganz oben in der Hierarchie befindet sich die Klasse Object, die Oberklasse aller Java-Klassen. Die allgemeinste Klasse, der ein Motorrad und ein Auto angehören könnte, könnte etwa Vehicle, also »Fahrzeug« sein. Ein Fahrzeug ist allgemein definiert als etwas, was jemanden von einem Platz zu einem anderen transportiert. In der Klasse Vehicle definieren Sie nur das Verhalten, das es ermöglicht, jemanden von A nach B zu transportieren, sonst nichts. Diese Verhalten könnten auch die Geschwindigkeit sowie die Steuerung beinhalten.
Was kommt unter Vehicle? Wie wäre es mit zwei neuen Klassen: HumanPoweredVehicle und EnginePoweredVehicle? EnginePoweredVehicle weist einen mechanischen Motor auf, bietet Verhalten zum starten und anhalten des Motors und macht eine bestimmte Menge an Treibstoff und Öl erforderlich. HumanPoweredVehicle hat eine menschliche Antriebskraft und weist Verhalten wie etwa die Nutzung der Pedale auf. Abbildung 3.3 zeigt die Hierarchie, die Sie bisher aufgebaut haben.
Abbildung 3.3:
Die grundlegende Hierarchie für Vehicle
Jetzt wollen wir ins Detail gehen. In EnginePoweredVehicle könnten Sie Klassen wie Motorcycle, Car usw. anlegen. Oder Sie könnten noch mehr Verhalten herausziehen und Zwischenklassen für Fahrzeuge mit zwei oder vier Rädern erzeugen, die jeweils unterschiedliche Verhalten aufweisen (siehe Abbildung 3.4).
Abbildung 3.4:
Fahrzeuge mit zwei oder vier Rädern
Schließlich könnten Sie in der Unterklasse für TwoWheeledEnginePoweredVehicle eine Unterklasse für Motorcycle anlegen. Sie könnten jetzt auch Scooter oder Moped definieren, die ebenfalls zu den motorgetriebenen Fahrzeugen mit zwei Rädern gehören, die aber andere Eigenschaften haben als Motorräder. Darüber hinaus können Sie Car als Unterklasse von FourWheeledEnginePoweredVehicle anlegen.
Wie funktioniert die Vererbung? Wie können Instanzen einer Klasse automatisch Zugriff auf Variablen und Methoden in Klassen haben, die weiter oben in der Hierarchie angesiedelt sind?
Für Instanzvariablen wird beim Erzeugen einer neuen Klasseninstanz für alle in der aktuellen Klasse definierten Variablen sowie für alle in ihren Oberklassen definierten Variablen zur Laufzeit Speicher alloziert. Auf diese Weise bilden alle Klassen in ihrer Gesamtheit eine Schablone für das aktuelle Objekt und anschließend trägt jedes Objekt die Information ein, die für die jeweilige Situation angemessen ist.
Methoden arbeiten ähnlich. Neue Objekte haben Zugriff auf alle Methoden ihrer Klasse und ihrer Oberklassen, aber die Methodendefinitionen werden dynamisch gewählt, wenn die Methode zur Laufzeit aufgerufen wird. D.h. wenn Sie die Methode eines bestimmten Objekts aufrufen, prüft der Java-Interpreter zuerst, ob die Methodendefinition in der Klasse dieses Objekts enthalten ist. Wenn dies nicht der Fall ist, sucht er in der Oberklasse des Objekts nach usw. in der Hierarchiekette nach oben, bis die Methode schließlich gefunden ist (siehe Abbildung 3.5).
Abbildung 3.5:
Wie Methoden dynamisch gewählt werden
Woran erkennt Java, daß es die richtige Methode erreicht hat? Wenn es eine Methode findet, der en Signatur mit der Signatur der aufgerufenen Methode übereinstimmt.
Java implementiert die Einfachvererbung, d.h. eine Unterklasse kann nur eine Oberklasse haben (obwohl natürlich eine Oberklasse mehrere Unterklassen haben kann). Es unterstützt keine Mehrfachvererbung, wie es in einigen anderen objektorientierten Sprachen der Fall ist (wie etwa in C++).
Die Java-Klassenbibliothek stellt Klassen bereit, die garantiert in jeder kommerziellen Java-Umgebung zur Verfügung stehen (beispielsweise Netscape). Diese Klassen befinden sich im java-Paket und umfassen alle Klassen, die Sie bisher kennengelernt haben, sowie viele andere, die Sie im Verlaufe dieses Buchs noch kennenlernen werden.
Der JDK (Java Developer's Kit) beinhaltet eine Dokumentation für die Java-Klassenbibliothek, mit Beschreibungen der Instanzvariablen, Methoden, Schnittstellen usw. der einzelnen Klassen. Anhand der Java-Klassenbibliothek und ihrer Variablen und Methoden können Sie leicht erkennen, was in Java möglich ist, was nicht, und Sie finden einen Ansatzpunkt für Ihre eigenen Entwicklungen.
Hier die Klassenpakete, die Teil der Java-Klassenbibliothek sind:
Neben den Java-Klassen kann Ihre Entwicklungsumgebung auch weitere Klassen beinhalten, die andere Utilities oder Funktionalität bieten. Diese Klassen können praktisch sein, aber weil sie nicht Teil der Standard-Java-Bibliothek sind, stehen Sie anderen Leuten, die Ihre Java-Programme ausführen wollen, nicht zur Verfügung, es sei denn, Sie liefern eine lizenzierte Kopie bei. Das ist vor allem für Applets ein wichtiger Aspekt, weil Applets auf jeder Plattform ausgeführt werden sollen, wo ein beliebiger Java-fähiger Browser implementiert ist. Nur die Klassen aus dem java-Paket stehen garantiert allen Web-Browsern in allen Java-Umgebungen zur Verfügung, deshalb sollte man sich bei der Entwicklung von Applets auf die Verwendung der Klassen aus der Standardbibliothek beschränken.
Nachdem Sie alles über die Vererbung wissen, können Sie eine Unterklasse erzeugen und einige Methoden überschreiben. Das wohl typischste Beispiel für das Anlegen einer Unterklasse, wenigstens wenn Sie gerade mit der Programmierung unter Java anfangen, ist die Entwicklung eines Applets. Alle Applets sind Unterklassen der Klasse Applet, die Teil des Pakets java.applet ist. Durch das Anlagen einer Unterklasse von Applet erben Sie automatisch die gesamte Funktionalität des AWT und der Layout-Klassen, die Ihnen ermöglichen, Ihre Applets auf der Seite an der richtigen Stelle auszugeben und eine Interaktion mit Systemoperationen zu realisieren, etwa die Verarbeitung von Tastencodes oder Mausklicks.
Jetzt wollen wir das Projekt JIntermediate.jpr, das Sie heute angelegt haben, ergänzen. Zuerst brauchen Sie eine HTML-Seite, um Ihr neues Applet zu testen. Klikken Sie auf das Icon Dem Projekt hinzufügen, geben Sie HelloAgain.html ein und klicken Sie auf Öffnen. Klicken Sie auf die Inhalts-Seite des AppBrowsers, dann auf die Registerkarte Quelltext, und geben Sie den HTML-Code aus Listing 3.2 ein.
1: <HTML>
2: <TITLE>Hello to Everyone!</TITLE>
3: <BODY>
4: <APPLET CODE=HelloAgainApplet WIDTH = 250 HEIGHT = 100></APPLET>
5: </BODY>
6: </HTML>
Dieser Code ist ähnlich dem HTML-Code aus Lektion 1. Um die Quellcode-Datei für das Applet zu erzeugen, klicken Sie erneut auf das Icon Dem Projekt hinzufügen, geben HelloAgainApplet.java ein und klicken auf Öffnen. Wenn das AppBrowser-Fenster erscheint, klicken Sie auf den Inhalts-Bereich und geben den in Listing 3.3 gezeigten Code ein.
Listing 3.3: HelloAgainApplet.java
1: import java.awt.Graphics;
2: import java.awt.Font;
3: import java.awt.Color;
4:
5: public class HelloAgainApplet extends java.applet.Applet {
6:
7: Font f = new Font("TimesRoman", Font.BOLD, 36);
8:
9: public void paint(Graphics g) {
10: g.setFont(f);
11: g.setColor(Color.red);
12: g.drawString("Hello again!", 5, 50);
13: }
14: }
Nachdem Sie den Quellcode eingegeben haben, wählen Sie Datei | Alles speichern, um Ihre Arbeit zu speichern.
In Zeile 5 sehen Sie außerdem das Schlüsselwort public. Dies ist ein Zugriffs-Modifier, d.h. Ihre Klasse steht dem ganzen Java-System zur Verfügung, nachdem sie geladen ist. Größtenteils sollten Sie eine Klasse nur dann public deklarieren, wenn Sie wollen, daß alle anderen Klassen in Ihren Java-Programmen zur Verfügung stehen. Aber insbesondere Applets müssen als public deklariert werden. (Mehr über das Schlüsselwort public erfahren Sie in der nächsten Lektion.)
In Zeile 7 enthält die Instanzvariable f eine neue Instanz der Klasse Font, Teil des Pakets java.awt. Dieses Font-Objekt stellt eine Times Roman dar, fett und 36 Punkt hoch (1/2 Inch). Im vorigen HelloWorld-Applet wurde die Standardschrift für den Text verwendet: Times Roman, 12 Punkt. Mit Hilfe eines Font-Objekts können Sie die Schriftart des Texts, den Sie in Ihrem Applet ausgeben, ändern. Wenn Sie eine Instanzvariable anlegen, die dieses Font-Objekt aufnimmt, stellen Sie sie allen Methoden Ihrer Klasse zur Verfügung. Jetzt können Sie eine Methode erzeugen, die es verwendet.
Wenn Sie Applets schreiben, gibt es mehrere Standardmethoden, die in den Applet-Oberklassen definiert sind, und die Sie im allgemeinen in Ihrer Applet-Klasse überschreiben. Unter anderem handelt es sich dabei um Methoden zum Initialisieren des Appltes, zum Starten, zur Verarbeitung von Operationen wie etwa Mausbewegungen oder Mausklicks, oder für Aufräumarbeiten nach Beendigung des Applets. Eine dieser Standardmethoden ist paint(), die Ihr Applet auf der Web-Seite anzeigt. Die Standarddefinition von paint() macht gar nichts - es ist eine leere Methode. Durch Überschreiben (Neudefinieren) von paint() teilen Sie dem Applet einfach nur mit, was es bei der Ausführung auf dem Bildschirm anzeigen soll.
Es gibt zwei Dinge, die Sie sich zur paint()-Methode merken sollten. Erstens, diese Methode wird public deklariert, so wie das eigentliche Applet. Die Methode paint() ist jedoch aus einem anderen Grund public - weil es eine public-Methode überschreibt. Sie werden gleich sehen, warum die Methode der Oberklasse public ist. In der Zwischenzeit achten Sie einfach darauf, daß immer, wenn Sie eine public-Methode überschreiben, die neue Methodendeklaration ebenfalls public sein muß, sonst erhalten Sie einen Compiler-Fehler.
Zweitens, die paint()-Methode nimmt ein einziges Argument entgegen: eine Instanz der Graphics-Klasse. Die Graphics-Klasse bietet ein plattformunabhängiges Verhalten für die Darstellung von Schriften, Farben und elementaren Zeichenoperationen. (Mehr über die Klasse Graphics erfahren Sie in der 2. Woche, wo Sie komplexere Applets entwickeln.)
Innerhalb der paint()-Methode haben Sie drei Dinge erledigt:
Im AppBrowser können Sie Ihr neues Applet im Struktur-Bereich sehen, wie in Abbildung 3.6 gezeigt.
Abbildung 3.6:
Das Struktur-Feld im AppBrowser zeigt die Teile des Applets
Eine der praktischsten Eigenschaften des AppBrowsers ist, daß alle Importe sofort in dem Struktur-Feld angezeigt werden. Wenn Sie einen der unter dem Import-Knoten eingerückt dargestellten Knoten doppelt anklicken, erhalten Sie den Quellcode für dieses Paket. Wenn Sie sehen wollen, wie Java das Font-Objekt implementiert, machen Sie dazu einfach einen Doppelklick auf den Knoten java.awt.Font. (Mit dem Zurück zum ersten Eintrag des Browsers-Icon oben im Navigations-Feld kehren Sie zum Quellcode Ihres Applets zurück.) Abbildung 3.7 zeigt die Ausführung des Applets im appletviewer.
Abbildung 3.7:
HelloAgainApplet im appletviewer
Um das Applet auszuführen, müssen Sie erst auf die ihm zugeordnete HTML-Datei im Navigationsfeld klicken und dann den Befehl Start auswählen.
Wenn Sie eine Klasse erzeugen, dann wollen Sie in der Regel auch, daß diese Klasse irgend etwas macht, was sie von ihren Oberklassen unterscheidet. Innerhalb jeder Klassendefinition gibt es Deklarationen und Definitionen von Variablen, Methoden oder beidem - für die Klasse und jede Instanz der Klasse. In diesem Abschnitt lernen Sie alles über Instanz- und Klassenvariablen. Der folgende Abschnitt bietet Ihnen Informationen über Methoden.
Gestern haben Sie erfahren, wie man lokale Variablen deklariert und initialisiert - d.h. Variablen innerhalb von Methodendefinitionen. Instanzvariablen werden glücklicherweise fast ebenso wie lokale Variablen deklariert und definiert. Der wichtigste Unterschied ist ihre Position in der Klassendefinition. Variablen werden als Instanzvariablen betrachtet, wenn sie außerhalb der Methodendefinition deklariert werden. Im allgemeinen werden jedoch die meisten Instanzvariablen unmittelbar nach der ersten Zeile der Klassendefinition definiert. Hier folgt eine einfache Klassendefinition für die Klasse Bicycle, die von der Klasse PersonPoweredVehicle erbt:
class Bicycle extends PersonPoweredVehicle {
String bikeType;
int chainGear;
int rearCogs;
int currentGearFront;
int currentGearRear;
}
Diese Klassendefinition enthält vier Instanzvariablen.
Klassenvariablen sind global für eine Klasse und für alle Instanzen dieser Klasse. Man könnte sich Klassenvariablen als globaler als Instanzvariablen vorstellen. Klassenvariablen sind praktisch für die Kommunikation zwischen verschiedenen Objekten derselben Klasse oder für die Verwaltung globaler Statuszustände in einer Objektemenge. Klassenvariablen sind Variablen, die in der Klasse selbst definiert und gespeichert sind. Dadurch erhalten die Klasse sowie alle von dieser Klasse instantiierten Objekte Zugriff auf den Variablenwert.
Wenn Sie Instanzvariablen verwenden, erhält jede neue Instanz der Klasse eine neue Kopie der in der Klasse definierten Instanzvariablen. Jede Instanz kann dann den Wert dieser Instanzvariablen unabhängig ändern, ohne die Werte in anderen Instanzen zu beeinflussen. Bei Klassenvariablen gibt es nur jeweils eine Kopie der Variable, die nur einen Wert enthalten kann. Die Klasse und jede Instanz der Klasse haben Zugriff auf dieselbe Variable. Eine Änderung des Werts einer Klassenvariablen ändert den Wert für alle Instanzen dieser Klasse gleichzeitig.
Klassenvariablen werden definiert, indem der Variablendeklaration das Schlüsselwort static hinzugefügt wird. Betrachten Sie den folgenden Ausschnitt aus einer Klassendefinition:
class FamilyMember {
static String surname = "Matzenfrazzer";
String name;
int age;
...
}
Hier haben die Instanzen der Klasse FamilyMember alle ihre eigenen Werte für name und age, aber die Klassenvariable surname hat nur einen Wert für alle von der Klasse erzeugten Objekte. Ändern Sie surname, und alle Instanzen von FamilyMember werden geändert.
Um auf Klassenvariablen zuzugreifen, verwenden Sie dieselbe Notation wie für Instanzvariablen. Wenn Sie den Wert der Klassenvariablen ändern wollen, verwenden Sie entweder die Instanz oder den Namen der Klasse auf der linken Seite des Punkts sowie den Variablennamen auf der rechten Seite. Beide Ausgabezeilen dieses Beispiels geben dieselbe Information aus:
FamilyMember baby = new FamilyMember();
System.out.println("Family's surname is " + FamilyMember.surname);
System.out.println("Family's surname is " + baby.surname);
Weil Sie eine Instanz verwenden können, um den Wert einer Klassenvariablen zu ändern, entsteht leicht Verwirrung über die Klassenvariablen und wo ihre Werte herkommen (Sie wissen, daß der Wert einer Klassenvariablen alle Instanzen beeinflußt). Aus diesem Grund sollten Sie den Klassennamen angeben, wenn Sie auf eine Klassenvariable verweisen. Im obigen Beispiel ist also FamilyMember.surname besser als baby.surname. Damit wird das Debugging einfacher und der Code besser lesbar.
Konstanten werden verwendet, um gemeinsam genutzte Werte für alle Methoden eines Objekts zu definieren, und um objektübergreifenden Werten sinnvolle Namen zuzuweisen, die sich nie ändern. In Java können Konstanten nur für Instanz- oder Klassenvariablen erzeugt werden, nicht für lokale Variablen.
final float pi = 3.141592;
final boolean debug = false;
final int maxsize = 40000;
final int LEFT = 0;
final int RIGHT = 1;
final int CENTER = 2;
Die Variable alignment ist ebenfalls als int deklariert:
Im Rumpf einer Methodendefinition können Sie dann entweder die Ausrichtung (alignment) setzen:
oder Sie prüfen, welche Ausrichtung vorliegt:
switch (this.alignment) {
case LEFT: // Ausrichtung links
...
break;
case RIGHT: // Ausrichtung rechts
...
break;
}
Um den Wert einer Klassen- oder Instanzvariablen zu ändern, schreiben Sie einfach einen Zuweisungsoperator rechts von dem Ausdruck:
Die Klasse CheckPoint in Listing 3.4 ist ein Beispiel, das die Instanzvariablen in einem Point-Objekt prüft oder ändert. Point ist Teil des Pakets java.awt und bezieht sich auf eine Koordinate mit einem x- und einem y-Wert.
1: import java.awt.Point;
2: class CheckPoint {
3: public static void main(String args[]) {
4: Point aPoint = new Point(100,100);
5:
6: System.out.println("Original-Koordinate:");
7: System.out.println("X,Y ist " + aPoint.x + "," + aPoint.y);
8:
9: aPoint.x = 50;
10: aPoint.y = 150;
11:
12: System.out.println("Neue Koordinate:");
13: System.out.println("X,Y ist " + aPoint.x + "," + aPoint.y);
14: }
15: }
Original-Koordinate:
X,Y ist 100,100
Neue Koordinate:
X,Y ist 50,150
Wenn Sie ein Java-Programm schreiben, definieren Sie Klassen. Wie Sie heute bereits erfahren haben, sind Klassen Schablonen für Objekte. Größtenteils verwenden Sie die Klasse, um Objektinstanzen zu erzeugen, und arbeiten dann mit diesen Instanzen. In diesem Abschnitt lernen Sie, wie Objekte einer bestimmten Klasse erzeugt und eingesetzt werden. Das Erzeugen eines Objekts wird auch als Instantiierung des Objekts bezeichnet.
Gestern haben Sie erfahren, daß die Verwendung eines String-Literals - mehreren in doppelte Anführungszeichen eingeschlossene Zeichen - eine neue Instanz der Klasse String erzeugt, deren Wert mit dem String-Literal initialisiert wird. Die String-Klasse ist in dieser Hinsicht einzigartig. Obwohl es sich um eine Klasse handelt, gibt es eine einfache Möglichkeit, direkt Instanzen davon zu erzeugen, indem ein Literal angelegt wird. Die anderen Klassen bieten keine solche Abkürzung. Instanzen dieser Klassen müssen explizit erzeugt werden, wie Sie gleich sehen werden.
Ein Objekt wird mit Hilfe des Schlüsselworts new erzeugt, beispielsweise:
Dieses Beispiel zeigt, wie explizit ein String-Objekt erzeugt wird. Beachten Sie, daß das Schlüsselwort String zweimal verwendet wird. Beim ersten Mal deklariert es die Variable str vom Typ String (auf der linken Seite des Gleichheitszeichens). Beim zweiten Mal stellt das Schlüsselwort String einen Aufruf der Methode dar, die das Objekt erzeugt. Vergessen Sie nicht die Klammern am Ende. Sie zeigen an, daß Sie eine Methode aufrufen.
Wenn Sie das Schlüsselwort new verwenden, passieren mehrere Dinge. Zuerst wird ein Objekt der angegebenen Klasse erzeugt und Speicher wird dafür alloziert. Damit wird das Objekt instantiiert (eine Instanz wird erzeugt). Darüber hinaus wird, was noch viel wichtiger ist, beim Erzeugen des Objekts eine spezielle Klassenmethode aufgerufen, die in der betreffenden Klasse definiert ist. Diese Methode wird auch als Konstruktor bezeichnet.
1: import java.util.Date;
2: class CreateDates {
3: public static void main(String args[]) {
4: Date d1, d2, d3;
5:
6: d1 = new Date();
7: System.out.println("Date 1: " + d1);
8:
9: d2 = new Date(92, 4, 1, 7, 17);
10: System.out.println("Date 2: " + d2);
11:
12: d3 = new Date("July 4 1997 4:33 PM");
13: System.out.println("Date 3: " + d3);
14: }
15: }
Wählen Sie Datei | Speichern, um Ihre Änderungen zu speichern, und dann Ansicht | Ausführungsprotokoll, um das Protokollfenster anzuzeigen. Klicken Sie mit der rechten Maustaste auf den Knoten CreateDates.java im Navigations-Feld und wählen Sie im Popup-Menü Start.
Date 1: Thu May 29 05:33:20 PDT 1997
Date 2: Fri May 01 07:17:00 PDT 1992
Date 3: Fri Jul 04 16:33:00 PDT 1997
Das zweite Date-Objekt, das Sie in diesem Beispiel erzeugt haben (Zeile 9), hat fünf Integer-Argumente. Die Argumente stellen ein Datum dar: Jahr, Monat, Tag, Stunden und Minuten. Wie die Ausgabe zeigt, wird dadurch ein Date-Objekt erzeugt, das das Datum May 1, 1992, 7:17 a.m. erzeugt.
Um zu ermitteln, welche Argumente die Konstruktoren für Date akzeptieren, gehen Sie in das Struktur-Feld des AppBrowsers und doppelklicken auf den Knoten java.util.Date im Import-Abschnitt. Dadurch wird der Quellcode für die Klasse angezeigt. Suchen Sie jetzt den zweiten Knoten Date im Struktur-Bereich und klikken Sie auf das Plus-Zeichen (+), um den Knoten zu expandieren. Jetzt können Sie auf einen der angezeigten Date-Konstruktoren klicken, um den Quellcode für diesen Konstruktor anzuzeigen und mehr darüber zu erfahren.
Wenn Sie später mehr darüber erfahren, wie Sei eigene Klassen erzeugen, lernen Sie auch, daß Sie beliebig viele Konstruktoren definieren können, je nachdem, was Sie benötigen, um das Klassenverhalten zu implementieren.
Wenn Sie bereits in anderen Sprachen programmiert haben, fragen Sie sich vielleicht, wie in Java die Speicherverwaltung realisiert wird. In dem oben erzeugten Programm beispielsweise mußte Sie keinen Speicher explizit allozieren. Das hat das Schlüsselwort new für Sie übernommen. In Java ist die Speicherverwaltung dynamisch und automatisch. Wenn Sie mit new ein Objekt erzeugen, alloziert Java automatisch die richtige Menge Speicher für diesen Objekttyp auf dem Heap.
Wenn Sie mit Objekten arbeiten, passiert hinter den Kulissen etwas ganz wichtiges, nämlich der Verweis auf diese Objekte. Wenn Sie Variablen Objekte zuweisen oder sie Methoden als Argumente übergeben, dann übergeben Sie eigentlich Verweise (Referenzen) auf diese Objekt, nicht Kopien der Objekte oder die Objekte selbst. Betrachten Sie das Programm ReferencesTest in Listing 3.6, wo zwei Variablen des Typs Point deklariert werden, und das pt1 ein neues Point-Objekt und den Wert von pt1 dann pt2 zuweist.
Listing 3.6: ReferencesTest.java
1: import java.awt.Point;
2: class ReferencesTest {
3: public static void main (String args[]) {
4: Point pt1, pt2;
5: pt1 = new Point(100, 100);
6: pt2 = pt1;
7:
8: pt1.x = 200;
9: pt1.y = 200;
10: System.out.println("Point1: " + pt1.x + ", " + pt1.y);
11: System.out.println("Point2: " + pt2.x + ", " + pt2.y);
12: }
13: }
Wo ist hier der Trick? Wie sieht pt2 aus, nachdem Sie die x- und y-Instanzvariablen von pt1 geändert haben?
Point1: 200, 200
Point2: 200, 200
Es gibt Situationen in Ihren Java-Programmen, wo Sie einen Wert als bestimmten Typ abgespeichert haben, ihn aber zur Verarbeitung als ganz anderen Wert brauchen. Vielleicht handelt es sich um die Instanz einer Klasse, aber Sie brauchen eine Instanz einer anderen Klasse, vielleicht ist es ein Fließkommawert, aber Sie brauchen einen Integer-Wert. Um den Wert eines Typs in einen anderen umzuwandeln, verwenden Sie die sogenannte Typumwandlung (Casting).
In den folgenden Abschnitten wird der zu konvertierende Wert als Quelle, der Typ, in den konvertiert werden soll, als Ziel bezeichnet.
Elementare Datentypen können automatisch oder explizit umgewandelt werden. Bei der automatischen Typkonvertierung kann eine einfache Zuweisung des kleineren an den größeren Datentyp passieren, wenn der Zieltyp größer als der Quelltyp ist (d.h. wenn er eine größere Genauigkeit aufweist). Tabelle 5.1 zeigt, welche Typen in welche anderen Typen umgewandelt werden können.
Tabelle 3.1: Wertumwandlungen, für die kein Datenverlust entsteht
Die Zuweisung eines Wert eines größeren Typs an einen kleineren Typ dagegen führt in der Regel zu einem Datenverlust. Wenn der Wert des größeren Typs jedoch gerade klein genug ist (beispielsweise wenn der Wert eines long gleich 10 ist, und Sie ihn einem int zuweisen), bleibt der Wert erhalten.
Um einen größeren Wert in einen kleineren Typ umzuwandeln, sollten Sie eine explizite Typumwandlung verwenden, um das Risiko eines Datenverlusts zu vermeiden.
Die Umwandlung zwischen elementaren Typen ermöglicht Ihnen, den Wert eines elementaren Typs in einen anderen elementaren Typ umzuwandeln - beispielsweise, einem Integer einen Fließkommawert zuzuweisen. Die Umwandlung zwischen den elementaren Typen betrifft hauptsächlich numerische Typen und Zeichen. Boolesche Wert können nicht in andere elementare Typen umgewandelt werden. Explizite Typumwandlungen sehen wie folgt aus:
Dabei ist ziel-typname der Name des Typs, in den Sie konvertieren wollen (z.B. short, int, float), und quell-wert ist der Ausdruck, dessen Ergebnis Sie konvertieren wollen. Der folgende Ausdruck teilt den Wert von x durch den Wert von y und wandelt den resultierenden float in einen int um:
Beachten Sie, daß die Auswertungspriorität der Typumwandlung höher als die der Arithmetik ist, deshalb müssen Sie Klammern verwenden, so daß das Ergebnis der Division zuerst berechnet und dann in einen Integer-Wert umgewandelt wird.
Einige Objekte brauchen nicht explizit umgewandelt zu werden. Insbesondere, weil Unterklassen mindestens alle Informationen aus der Oberklasse enthalten, können Sie überall dort, wo die Oberklasse erwartet wird, eine Instanz einer Unterklasse einsetzen. Angenommen, Sie wollen eine Methode aufrufen, die zwei Argumente entgegennimmt: eines vom Typ Object, ein anderes vom Typ Number. Sie müssen dieser Methode keine Instanzen dieser jeweiligen Klassen übergeben. Für das Object-Argument können Sie eine beliebige Unterklasse von Object (mit anderen Worten, ein beliebiges Objekt) übergeben, und für das Number-Objekt eine Instanz einer beliebigen Unterklasse von Number (Integer, Double, Float usw.).
Die Umwandlung eines Objekts in eine der Oberklassen dieses Objekts bewirkt, daß die Information verlorengeht, die für die ursprüngliche Oberklasse spezifisch war, und macht eine explizite Umwandlung erforderlich. Instanzen von Klassen können in Instanzen anderer Klassen umgewandelt werden, mit einer Einschränkung: Die Klasse des Quell-Objekts und die Klasse des Ziel-Objekts müssen durch Vererbung verwandt sein. Das bedeutet, Sie können eine Objekt nur in eine Instanz der Unterklasse oder Oberklasse seiner Klasse umwandeln - nicht in eine beliebige Klasse. Um ein Objekt in eine andere Klasse umzuwandeln, verwenden Sie dieselbe Operation wie für elementare Typen:
(ziel-klassenname) quell-objekt
Hier ist ziel-klassenname der Name der Klasse, in die Sie umwandeln, und quell-objekt ist ein Verweis auf das Objekt, das Sie konvertieren wollen. Beachten Sie, daß die Umwandlung einen neuen Verweis auf das quell-objekt des Typs ziel-klassenname erzeugt. Das Original-Objekt existiert weiterhin.
Der folgende Codeabschnitt ist ein Beispiel für eine Umwandlung einer Instanz der Klasse GreenApple in eine Instanz der Klasse Apple (wobei GreenApple eine Unterklasse von Apple ist):
Apple anApple;
GreenApple aGreenApple;
aGreenApple = new GreenApple();
anApple = (Apple) aGreenApple;
Beachten Sie, daß die speziellen Attribute, die GreenApple grün gemacht haben, in anApple verloren sind. Das restliche Verhalten (die Methoden) und die Attribute (Variablen), die GreenApple von Apple geerbt hab, überleben die Konvertierung.
Neben der Umwandlung von Objekten in Klassen können Sie Objekte auch in Schnittstellen umwandeln - aber nur, wenn die Klasse des Objekts oder eine seiner Oberklassen diese Schnittstelle auch implementiert (mit anderen Worten, wenn sie demselben Vererbungsbaum angehört). Die Umwandlung eines Objekts in eine Schnittstelle ermöglicht Ihnen dann, eine der Methoden der Schnittstelle aufzurufen, auch wenn die Klasse dieses Objekts die Schnittstelle nicht direkt implementiert. Mehr über Schnittstellen erfahren Sie morgen.
Nachdem Sie wissen, wie ein elementarer Datentyp in einen anderen elementaren Datentyp umgewandelt wird, und wie ein Umwandlung zwischen Klassen erfolgt, fragen Sie sich vielleicht, wie Sie eine Umwandlung zwischen beidem vornehmen können. Wie wir bereits am Anfang dieses Abschnitts erwähnt haben, ist es in Java eigentlich nicht möglich, eine Umwandlung zwischen elementaren Datentypen und Objekten vorzunehmen. Allerdings bietet Java eine andere Möglichkeit, diese Konvertierung vorzunehmen.
Es gibt mehrere spezielle Klassen im Paket java.lang, die den elementaren Datentypen entsprechen: Integer für int, Float für float, Boolean für boolean usw. Unter Verwendung der Klassenmethoden, die in diesen Klassen definiert sind, können Sie mit new ein Objekt-Äquivalent für alle elementaren Datentypen erzeugen. Die folgende Codezeile beispielsweise erzeugt eine Instanz der Klasse Integer mit dem Wert 35:
Integer intObject = new Integer(35);
Nachdem Sie dieses Objekt angelegt haben, können Sie seinen Wert als Objekt behandeln. Es gibt auch Methoden, um den elementaren Typ wieder zurückzuverwandeln. In diesem Beispiel extrahiert die Methode intValue() den elementaren int-Wert von 35 aus dem Integer-Objekt und weist ihn theInt zu:
int the Int = intObject.intValue;
Details über die Verwendung dieser Methoden erfahren Sie, indem Sie Hilfe | Java-Referenz auswählen und nach dem Thema java.lang-Paket suchen.
Gestern haben Sie die Operatoren zum Vergleich von Werten kennengelernt: Gleich (==), Ungleich (!=), Kleiner (<), Größer (>) usw. Die meisten dieser Operatoren nehmen arithmetische Werte Operanden entgegen, und der Java-Compiler zeigt einen Fehler an, wenn Sie versuchen, einen anderen Wertetyp zu übergeben.
Die Ausnahme für diese Regel herrscht für die beiden Gleichheitsoperatoren: Gleich (==) und Ungleich (!=). Wenn Objekte als Operanden übergeben werden, vergleichen diese Operanden nicht zwei separate Objekte, sondern sie prüfen, ob diese beiden Objekte auf dasselbe Objekt verwiesen. In diesem Kontext nehmen die Operanden folgende Bedeutung an: bezieht-sich-auf-dasselbe-Objekt (==) und bezieht-sich-auf-ein-anderes-Objekt (!=). Wie können Sie aber zwei verschiedene Objektinstanzen auf Gleichheit überprüfen? Sie müssen spezielle Methoden in Ihren Klassen implementieren, und Sie müssen diese Methoden unter Verwendung ihrer Methodennamen aufrufen.
Die Klasse String definiert deshalb eine Methode equals(), die jedes Zeichen im String vergleicht und true zurückgibt, wenn die beiden Strings identische Werte enthalten. Die Klasse EqualString.java in Listing 3.7 zeigt diese Funktionalität.
1: class EqualString {
2: public static void main (String args[]) {
3: String str1, str2;
4: str1 = "Soll ich das wiederholen?";
5: str2 = str1;
6:
7: System.out.println();
8: System.out.println("String1: " + str1);
9: System.out.println("String2: " + str2);
10: System.out.println("Selber Wert?: " + str1.equals(str2));
11: System.out.println("Selbes Objekt? " + (str1 == str2));
12:
13: str2 = new String(str1);
14:
15: System.out.println();
16: System.out.println("String1: " + str1);
17: System.out.println("String2: " + str2);
18: System.out.println("Selber Wert? " + str1.equals(str2));
19: System.out.println("Selbes Objekt? " + (str1 == str2));
20: }
21: }
String1: Soll ich das wiederholen?
String2: Soll ich das wiederholen?
Selber Wert? true
Selbes Objekt? true
String1: Soll ich das wiederholen?
String2: Soll ich das wiederholen?
Selber Wert? true
Selbes Objekt? false
Im zweiten Teil erzeugt Zeile 13 ein neues String-Objekt mit dem Wert von str1. Zeile 18 zeigt, daß str1 und str2 identische Werte besitzen. Zeile 19 zeigt, daß es sich um separate und verschiedene String-Objekte handelt.
In Java müssen Sie sich an eine neue Art und Weise gewöhnen, Strings zu duplizieren. Aus diesem Grund verwendet das Programm EqualString in Zeile 13 new, statt nur str2 ein String-Literal zuzuweisen.
Wenn Sie die Klasse eines Objekts ermitteln wollen, führen Sie die folgende Codezeile aus:
String myObjName = myObj.getClass().getName();
Die Methode getClass() ist in der Klasse Object definiert und steht somit allen Objekten zur Verfügung. Das Ergebnis dieser Methode ist ein Class-Objekt (das wiederum selbst eine Klasse ist), das die Methode getName() definiert. Die Methode getName() gibt ein String-Objekt zurück, das den Namen der Klasse darstellt und Ihnen damit mitteilt, um die Instanz welcher Klasse es sich bei dem Objekt handelt. Schließlich wird dieser Rückgabewert myObjName als Ausgangswert zugeordnet.
Wenn Sie prüfen wollen, ob ein Objekt eine Instanz einer bestimmten Klasse ist, verwenden Sie den instanceof-Operator. Der instanceof-Operator nimmt zwei Operatoren entgegen, ein Objekt links und den Namen einer Klasse rechts. Dieser Boolesche Ausdruck ergibt true oder false, je nachdem, ob das Objekt eine Instanz der angegebenen Klasse oder einer ihrer Unterklassen ist, zum Beispiel:
"Muuh!" instanceof String; // ergibt true
Point pt = new Point(10,10);
pt instanceof String; // ergibt false
Der instanceof-Operator kann auch für Schnittstellen verwendet werden. Wenn ein Objekt eine Schnittstelle implementiert, ergibt der instanceof-Operator mit diesem Schnittstellenname als Operand auf der rechten Seite true. Mehr über Schnittstellen erfahren Sie morgen.
Methoden sind verständlicherweise der wichtigste Teil einer objektorientierten Sprache. Während Klassen und Objekte die Umgebung bieten, und die Klassen- und Instanzvariablen eine Möglichkeit, die Attribute und den Status dieser Klassen und Objekte zu speichern, realisieren Methoden das Verhalten eines Objekts und definieren, wie das Objekt mit anderen Objekten im System interagiert.
In diesem Abschnitt lernen Sie einige der komplexeren Eigenschaften von Methoden kennen, die diese wirklich leistungsfähig und Ihre Objekte und Klassen effizienter und verständlicher machen.
Das Format für den Aufruf einer Methode in Objekten ist ähnlich dem Zugriff auf seine Instanzvariablen: Auch beim Methodenaufruf wird die Punktnotation verwendet. Das Objekt, dessen Methode Sie aufrufen, befindet sich auf der linken Seite des Punkts, der Name der Methode und ihre Argumente auf der rechten Seite:
myObject.methodOne(arg1, arg2, arg3);
Beachten Sie, daß hinter einer Methode immer ein Klammernpaar angegeben werden muß, auch wenn sie keine Argumente entgegennimmt:
Wenn die Methode, die Sie aufgerufen haben, ein Objekt ergibt, das selbst wiederum Methoden hat, können Sie die Methoden wie Variablen verschachteln. In diesem Beispiel erzeugt die getClass()-Methode ein Objekt, das die getName()-Methode enthält:
myObject.getClass().getName();
Sie können Variablenverweise und Methodenaufrufe auch verschachteln. In diesem Beispiel enthält die Variable xVar eine Klasse, die die Methode methodTwo definiert:
myObject.xVar.methodTwo(arg1);
System.out.println(), die Methode, die Sie bisher verwendet haben, ist ein gutes Beispiel für die Verschachtelung von Variablen und Methoden. Die Klasse System (Teil des Pakets java.lang) beschreibt systemspezifisches Verhalten. System.out ist eine Klassenvariable, die eine Instanz der Klasse PrintStream enthält, die auf die Standardausgabe des Systems zeigt. PrintStream-Instanzen haben eine println()-Methode, die einen String auf diesen Ausgabestream ausgibt.
Der folgende Codeausschnitt zeigt ein Beispiel für den Aufruf einer Methode, die in der Klasse String definiert ist, length():
System.out.println("Länge des Strings: + str.length());
Strings beinhalten Methoden für Stringvergleiche und -änderungen, ähnlich dem, was Sie in einer String-Bibliothek in anderen Sprachen erwarten würden.
Klassenmethoden beziehen sich auf die Klasse als Ganzes und nicht auf ihre Instanzen. Klassenmethoden werden normalerweise für allgemeine Utility-Methoden verwendet, die nicht direkt für einer Instanz der Klasse ausgeführt, sondern der Klasse nur vom Konzept her zugeordnet werden können. Beispielsweise definiert die Klasse String die Klassenmethode valueOf(), die unterschiedliche Argumenttypen entgegennehmen kann (Integer, Boolesche Werte, andere Objekte usw.). Die Methode valueOf() gibt dann eine neue Instanz von String zurück, die den Stringwert des übergebenen Arguments enthält. Diese Methode wird nicht direkt für eine existierende Instanz von String ausgeführt, aber die Ermittlung eines Strings aus einem anderen Objekt oder Datentyp ist definitiv eine string-bezogene Operation, deshalb ist es sinnvoll, sie in der Klasse String zu definieren.
Klassenmethoden können auch praktisch für die Zusammenfassung allgemeiner Methoden an zentraler Stelle sein (der Klasse). Beispielsweise enthält die Klasse Math, die im Paket java.lang definiert ist, zahlreiche mathematische Operationen als Klassenmethoden - es gibt keine Instanzen der Klasse Math, aber Sie können ihre Methoden mit numerischen oder Booleschen Argumenten aufrufen.
Für den Aufruf einer Klassenmethode verwenden Sie die Punktnotation, so wie für Instanzmethoden. Wie bei Klassenvariablen können Sie entweder eine Instanz der Klasse oder die Klasse selbst auf der linken Seite des Punkts angeben. Aus denselben Gründen, die bei der Beschreibung von Klassenvariablen erwähnt wurden, sorgt die Angabe des Klassennamens für Klassenmethoden dafür, daß Ihr Code einfacher zu lesen wird. Die beiden letzten Zeilen des folgenden Codeausschnitts erzeugen dieselben Ergebnisse und demonstrieren, warum der Klassenname auf der linken Seite des Punkts angegeben werden sollte:
String s1, s2, s3;
s1 = "Mandy";
s2 = s1.valueOf(7);
s3 = String.valueOf(7);
Die Klassenmethode valueOf() gibt für einen Integer den Stringwert für diesen Integer zurück Im obigen Beispiel geben s1.valueOf(7) und String.valueOf(7) den Stringwert seven zurück. Die Codezeile mit dem Ausdruck s1.valueOf(7) verwendet jedoch nicht wirklich die Variable s1, sondern ruft dafür einfach die Klassenmethode auf. Die Verwendung des Instanznamens auf diese Weise wird den ungeübten Leser irreführen. Deshalb ist es sinnvoller, die Notation der letzten Zeile zu verwenden, String.valueOf(7), weil sie klar zeigt, daß es sich um eine Klassenmethode handelt.
Methoden definieren, wie Sie heute bereit erfahren haben, das Verhalten eines Objekts - was passiert, wenn dieses Objekt erzeugt ist, und welche Operationen das Objekt während seiner Lebensdauer ausführen kann. In diesem Abschnitt lernen Sie die Grundlagen der Methodendefinition und Sie erfahren, wie Methoden arbeiten. Später werden Sie detaillierte Informationen über die Funktionsweise von Methoden erhalten.
Methodendefinitionen bestehen aus vier Teilen: Name, Rückgabetyp, Argument und Rumpf. Die Methodensignatur ist eine Kombination aus Methodenname, dem Typ des Objekts oder des elementaren Datentyps, den dies Methode zurückgibt, sowie der Argumenteliste.
In anderen Sprachen ist der Name einer Methode (oder Funktion, Prozedur oder Unterroutine) ausrichend, um sie von anderen Methoden desselben Programms zu unterscheiden. In Java können Sie unterschiedliche Methoden haben, die denselben Namen tragen, aber eine unterschiedliche Argumenteliste aufweisen. Dieser Sachverhalt wird auch als Überladen von Methoden bezeichnet, was Sie später noch genauer kennenlernen werden.
Eine grundlegende Methodendefinition sieht wie folgt aus:
rückgabetyp methodenName(typ1 arg1, typ2 arg2, typ3 arg3...) {
/* Methodenrumpf */
}
Der rückgabetyp ist der Typ des Werts, den die Methode zurückgibt. Dabei kann es sich um einen der elementaren Datentypen handeln, um eine Klasse oder um void, falls die Methode keinen Wert zurückgibt, wie etwa die Methode main().
Wenn diese Methode ein Array-Objekt zurückgibt, können die Array-Klammern hinter dem rückgabetyp oder hinter der Argumenteliste angegeben werden. Weil die erste Form leichter zu lesen ist, wird sie in den heutigen Beispielen verwenden (und auch im restlichen Buch):
int[] makeRange(int lower, int upper) {...}
Die Argumenteliste der Methode besteht aus mehreren Variablendeklarationen, die durch Kommata getrennt und in Klammern eingeschlossen sind. Diese Argumente sind die lokalen Variablen im Rumpf der Methode, deren Werte die Objekte oder die elementaren Werte sind, die beim Aufruf der Methode übergeben werden.
Innerhalb des Methodenrumpfs können Sie Anweisungen, Ausdrücke, Methodenaufrufe anderer Objekte, Bedingungen, Schleifen usw. angeben - alles, was Sie gestern kennengelernt haben. Wenn Ihre Methode einen Rückgabetyp hat (d.h. wenn sie nicht als void deklariert wurde), müssen Sie irgendwo innerhalb des Methodenrumpfs einen Wert zurückgeben. Dazu verwenden Sie das Schlüsselwort return.
Beachten Sie die return-Anweisungen in Listing 3.8.
1: public class RangeClass {
2:
3: int[] makeRange(int lower, int upper) {
4: int arr[] = new int[ (upper - lower) + 1 ];
5: for (int i = 0; i < arr.length; i++) {
6: arr[i] = lower++;
7: }
8: return arr;
9: }
10:
11: static public void main(String[] args) {
12: int theArray[];
13: RangeClass theRange = new RangeClass();
14: theArray = theRange.makeRange(1, 10);
15: System.out.print("Das Array: [ ");
16: for (int i = 0; i < theArray.length; i++) {
17: System.out.print(theArray[i] + " ");
18: }
19: System.out.println("]");
20: }
21: }
Das Array: [ 1 2 3 4 5 6 7 8 9 10 ]
Im Rumpf einer Methodendefinition können Sie auf das aktuelle Objekt verweisen - das Objekt, durch das die Methode aufgerufen wurde -, um auf dessen Instanzvariablen zuzugreifen, oder um es einer andere Methode als Argument zu übergeben. Dazu verwenden Sie das Schlüsselwort this. Es verweist auf das aktuelle Objekt und Sie können es überall dort verwenden, wo das Objekt angegeben werden kann - in der Punktnotation, um auf seine Instanzvariablen zu verweisen, als Argument für eine Methode, als Rückgabewert für die aktuelle Methode usw. Hier einige Beispiele:
t = this.x; // die Instanzvariable x für das aktuelle Objekt
this.myMethod(this); // ruft die Methode myMethod auf, die in der
// Klasse des aktuellen Objekts definiert ist,
// und übergibt ihr das aktuelle Objekt
return this; // gibt das aktuelle Objekt zurück
In vielen Fällen können Sie das Schlüsselwort this weglassen. Sie können auf Instanzvariablen und Methodenaufrufe, die in der aktuellen Klasse definiert sind, einfach über den Namen zugreifen. Das Schlüsselwort this ist in diesen Verweisen implizit und kann weggelassen werden.
Beachten Sie, daß Sie this nur innerhalb des Rumpfs einer Instanzmethodendefinition verwenden sollten, weil es sich dabei um einen Verweis auf die aktuelle Instanz einer Klasse handelt. Klassenmethoden - d.h. Methoden, die mit dem Schlüsselwort static deklariert wurden - können this nicht verwenden. Klassenmethoden sind keiner bestimmten Instanz der Klasse zugeordnet, deshalb ist das Schlüsselwort this hier nicht relevant.
Wenn Sie innerhalb Ihrer Methodendefinitionen auf eine Variable zugreifen, sucht Java zunächst im aktuellen Gültigkeitsbereich (wobei es sich um einen Block handeln könnte) nach der Definition dieser Variablen, und dann in den äußeren Gültigkeitsbereichen bis hin zur aktuellen Methodendefinition. Wenn es sich bei dieser Variablen nicht um eine lokale Variable handelt, sucht Java nach einer Definition dieser Variablen als Instanz- oder Klassenvariable in der aktuellen Klasse. Schließlich durchsucht Java noch alle Oberklassen.
Unter Java ist es in bestimmten Situationen möglich, eine Variable in einem inneren Gültigkeitsbereich zu erzeugen, die eine Variable in einem äußeren Gültigkeitsbereich verdeckt. Wenn die Definition einer Variablen in einem inneren Gültigkeitsbereich genau der einer Variablen eines äußeren Gültigkeitsbereichs entspricht, wird der Wert für die äußere Variable verborgen. Das ist natürlich kein empfehlenswerter Programmierstil, weil es zu subtilen und verwirrenden Fehlern in Ihrem Code führen kann. Betrachten Sie beispielsweise das folgende kleine Java-Programm:
class ScopeTest {
int test = 10;
void printTest() {
int test - 20;
System.out.println("test = " + test);
}
}
In dieser Klasse haben Sie zwei Variablen mit demselben Namen und derselben Definition. Die erste, eine Instanzvariable, hat den Namen test und wird mit dem Wert 10 initialisiert. Die zweite ist eine lokale Variable, auch mit dem Namen test, aber mit dem Wert 20. Weil die lokale Variable in dem inneren Gültigkeitsbereich die Instanzvariable in dem äußeren Gültigkeitsbereich verdeckt, gibt die println()-Methode test = 20 aus. Sie können jedoch dieses Standardverhalten für den Gültigkeitsbereich umgehen, indem Sie this.test verwenden, um auf die Instanzvariable zuzugreifen, und test für die lokale Variable.
Ein tückischeres Beispiel für dieses Problem entsteht, wenn Sie eine Variable in einer Unterklasse definieren, die es in der Oberklasse bereits gibt. Das kann zu sehr subtilen Fehlern in Ihrem Code führen. Beispielsweise rufen Sie dann die Methoden auf, die den Wert einer bestimmten Instanzvariablen ändern sollen, aber statt dessen ändern sie den Wert einer Variablen aus dem inneren Gültigkeitsbereich, und der Wert der äußeren Variablen bleibt unverändert. Ein anderer Fehler könnte auftreten, wenn Sie ein Objekt von einer Klasse in das einer anderen konvertieren. Der Wert Ihrer Instanzvariablen könnte sich auf unerklärliche Weis ändern (weil er aus der Oberklasse, und nicht aus Ihrer Klasse ermittelt wurde). Die beste Möglichkeit, das zu vermeiden, ist, sicherzustellen, daß Sie bei der Definition von Variablen in einer Unterklasse darauf achten, keine bereits in der Oberklasse enthaltenen Variablendefinition zu verwenden.
Wenn Sie eine Methode mit Objekt-Argumenten aufrufen, werden die Variablen, die Sie in den Methodenrumpf eingeben, als Referenz übergeben, d.h., egal, was Sie mit diesen Objekten innerhalb der Methode machen, es betrifft auch die Original-Objekte. Das gilt auch für Arrays und alle Objekte, die in Arrays enthalten sein können. Wenn Sie einer Methode ein Array übergeben und dessen Inhalt ändern, wird auch das Orignial-Array verändert. (Beachten Sie, daß elementare Datentypen als Wert übergeben werden.)
Hier ein Beispiel, das demonstrieren soll, wie das Ganze funktioniert. Zuerst haben Sie eine einfache Klassendefinition mit einer einzigen Methode, oneToZero():
class PassByRef {
int oneToZero(int arg[]) {
int count = 0;
for (int i = 0; i < arg.length; i++) {
if (arg[i] == 1) {
count++;
arg[i] = 0;
}
}
return count;
}
}
Die Methode oneToZero() erledigt zwei Aufgaben:
In diesem Code-Ausschnitt erzeugen Sie die main()-Methode für die Klasse PassByRef und übergeben der Methode oneToZero() ein Array mit Integern:
public static void main (String arg[]) {
int arr[] = { 1, 3, 4, 5, 1, 1, 7 };
PassByRef test = new PassByRef();
int numOnes;
numOnes = test.oneToZero(arr);
}
Die ersten drei Zeilen setzen die Variablen. Die erste ist ein Array mit Integern. Die zweite ist eine Instanz der Klasse PassByRef, die in der Variablen test abgelegt wird. Die dritte ist ein Integer, der die Anzahl der im Array aufgetretenen Einsen aufnimmt. Die vierte Zeile ruft die im Objekt test definierte Methode oneToZero() auf und übergibt ihr das in arr gespeicherte Array. Diese Methode gibt die Anzahl der Einsen im Array zurück, die Sie dann der Variablen numOnes zuweisen. Die Methode oneToZero() gibt 3 zurück. Die alten Werte im Array, {1,3,4,5,1,1,7} werden durch den Methodenaufruf geändert in {0,3,4,5,0,0,7}.
So wie es Klassen- und Instanzvariablen gibt, gibt es auch Klassen- und Instanzmethoden. Der Unterschied ist analog. Klassenmethoden stehen allen Instanzen der Klasse zur Verfügung und können anderen Klassen bereitgestellt werden. Einige Klassenmethoden können deshalb überall verwendet werden, unabhängig davon, ob es eine Instanz dieser Klasse gibt oder nicht.
Die Java-Klassenbibliotheken beispielsweise enthalten die Klasse Math. Sie definiert mathematische Operationen, die in jedem Programm und mit den unterschiedlichsten numerischen Typen eingesetzt werden können:
float root = Math.sqrt(453.0);
System.out.print("Das größere Element von x und y ist " + Math.max(s, y));
Klassenmethoden werden mit dem Schlüsselwort static vor der Methodendefinition gekennzeichnet, ähnlich, wie eine Klassenvariable erzeugt wird. Hier die Methodensignatur der Klasse sqrt:
static float sqrt(float arg1) {...}
Java unterstützt außerdem »Hüllklassen« für die elementaren Datentypen - beispielsweise Klassen für Integer, Float und Boolean. Unter Verwendung der in diesen Klassen definierten Klassenmethoden können Sie Konvertierungen zwischen Objekten und elementaren Datentypen vornehmen. Die Klassenmethode parseInt() der Klasse Integer beispielsweise nimmt einen String und eine Basis entgegen und gibt den String als Integer zurück.
int count = Integer.parseInt("42", 10); // gibt den Integer 42 zurück
Sie haben heute bereits gelernt, wie Methoden mit einem Namen und einer Signatur erzeugt werden. Methoden können in Java auch überladen werden. Das bedeutet, Sie können Methoden erzeugen, die alle denselben Namen haben, aber unterschiedliche Signaturen und unterschiedliche Definitionen. Das Überladen von Methoden ermöglicht, daß Instanzen Ihrer Klasse eine einfacherer Schnittstelle für andere Objekte haben und sich je nach Eingabe unterschiedlich verhalten.
Wenn Sie eine Methode in einem Objekt aufrufen, betrachtet Java den Methodennamen, die Anzahl der Argumente sowie die Argumenttypen, um zu ermitteln, welche Methodendefinition ausgeführt werden soll. (In Abbildung 3.5 wurde dieses Konzept vorgestellt.)
Um eine überladene Methode zu erzeugen, brauchen Sie nur mehrere verschiedene Methodendefinitionen in Ihrer Klasse erzeugen, die alle denselben Namen haben, aber unterschiedlicher Argumentelisten. Die Argumenteliste kann sich hinsichtlich der Anzahl der Argumente, der Datentypen dieser Argumente oder beidem unterscheiden. Dabei spielt es keine Rolle, welche Variablennamen Sie für die einzelnen Methodenargumente verwenden; es kommt nur auf die Anzahl und den Typ der Argumente an.
Java erlaubt das Überladen von Methoden, wenn jede Methode desselben Namens eine eindeutige Argumenteliste hat. Überladene Methoden müssen aber identische Rückgabetypen haben. Das bedeutet, wenn Sie versuchen, zwei Methoden mit demselben Namen und denselben Argumenten aber mit unterschiedlichen Rückgabetypen zu erzeugen, erhalten Sie einen Compiler-Fehler.
Der folgende Code zeigt eine einfache Klassendefinition für die Klasse MyRect, die ein Rechteck definiert. Die Klasse MyRect hat vier Instanzvariablen, die die Koordinatenpaare für die linke obere und die rechte obere Ecke definieren: x1, y1, x2 und y2. Wenn eine neue Instanz von MyRect erzeugt wird, werden alle Instanzvariablen mit 0 initialisiert:
class MyRect {
int x1 = 0;
int y1 = 0;
int x2 = 0;
int y2 = 0;
}
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 passiert, wenn Sie dem Anwender erlauben wollen, die Maße des Rechtecks auf andere Weise zu spezifizieren - beispielsweise unter Verendung von Point-Objekten statt einzelner Koordinaten? Sie können buildRect() überladen, so daß seine Argumenteliste statt dessen zwei Point-Objekte aufnimmt. (Beachten Sie, daß Sie java.awt.Point importieren müssen, damit dies funktioniert.) Diese Version der Methode nimmt die Point-Objekte entgegen und weist ihre x- und y-Feldwerte den entsprechenden Instanzvariablen zu:
MyRect buildRect(Point topLeft, Point bottomRight) {
x1 = topLeft.x;
y1 = topLeft.y;
x2 = bottomRight.x;
y2 = bottomRight.y;
return this;
}
Vielleicht wollen Sie aber das Rechteck auch anhand einer oberen Ecke und einer Höhe und Breite definieren. Dann erzeugen Sie einfach noch eine 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;
}
Das Überladen von Methoden ermöglicht Ihnen, den Anwendern Ihrer Klassendefinition genau diese Flexibilität bereitzustellen. Beachten Sie, daß Sie beliebig viele Methoden in Ihren Klassen definieren können, um das für diese Klasse erforderliche Verhalten zu realisieren. Sie können jedoch nicht in einer Klasse zwei separate Methoden definieren, die denselben Namen und dieselben Argumente verwenden. Wenn Sie das versuchen, erhalten Sie den Compilerfehler »Duplicate Definition«.
Wenn Sie die Methode eines Objekts aufrufen, sucht Java nach der Definition dieser Methode in der Klasse des Objekts. Wenn es keine findet, übergibt es den Methodenaufruf in der Hierarchie nach oben, bis eine passende Methodensignatur gefunden wird. Die Methodenvererbung ermöglicht Ihnen, Methoden zu definieren und in Unterklassen zu verwenden, ohne den Code in jeder Unterklasse erneut angeben zu müssen.
Es gibt jedoch Situationen, wo Sie wollen, daß ein Objekt anders auf einen Methodenaufruf reagiert, als durch das Verhalten in der Oberklasse definiert. In diesem Fall können Sie die Methode überschreiben. Hier wird eine Methode in einer Unterklasse neu definiert, wobei dieselbe Signatur verwendet wird wie für die Methode in der Oberklasse. Wenn diese Methode aufgerufen wird, wird die Methode in der Unterklasse gefunden und ausgeführt, und nicht die in der Oberklasse.
Um eine Methode zu überschreiben, müssen Sie nur eine Methode in Ihrer Unterklasse erzeugen, die dieselbe Signatur (Name und Argumenteliste) wie eine von einer Oberklasse Ihrer Klasse definierte Methode hat. Weil Java die erste Methodendefinition ausführt, die es findet, und die mit der Signatur übereinstimmt, wird dadurch letztlich die Original-Methodendefinition aus dem Gültigkeitsbereich des aktuellen Objekts verborgen.
Klicken Sie jetzt auf das Icon Dem Projekt hinzufügen, geben Sie PrintClass.java ein und klicken Sie auf Öffnen. Klicken Sie auf den Inhalts-Bereich und geben Sie den Code aus Listing 3.9 ein.
1: class PrintClass {
2: int x = 0;
3: int y = 1;
4:
5: void printMe() {
6: System.out.println("X ist " + x + ", Y ist " + y);
7: System.out.println("Ich bin eine Instanz der Klasse "
8: + this.getClass().getName());
9: }
10: }
Als nächstes erzeugen Sie eine Unterklasse von PrintClass, mit dem einzigen Unterschied, daß diese eine z-Variable aufweist. Diese Unterklasse enthält auch die main()-Methode der Anwendung. Klicken Sie erneut auf das Icon Dem Projekt hinzufügen, um PrintSubClass.java einzufügen, und geben Sie dann den in Listing 3.10 gezeigten Code ein.
Listing 3.10: 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: }
X ist 0, Y ist 1
Ich bin eine Instanz der Klasse PrintSubClass
Jetzt fügen Sie dem Projekt das in Listing 3.11 gezeigte PrintSubClass2.java hinzu.
Listing 3.11: PrintSubClass2.java
1: class PrintSubClass2 extends PrintClass {
2: int z = 3;
3:
4: void printMe() {
5: System.out.println("X ist " + x + ", Y ist " + y
6: + ", Z ist " + z);
7: System.out.println("Ich bin eine Instanz der Klasse "
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 PrintSubClass2.java ausführen, instantiiert es die Unterklasse PrintSubClass2 und ruft die in dieser Unterklasse definierte printMe()-Methode auf.
X ist 0, Y ist 1, Z ist 3
Ich bin eine Instanz der Klasse PrintSubClass2
Normalerweise gibt es zwei Gründe, warum Sie eine Methode überschreiben wollen, die eine Oberklasse bereits implementiert hat:
Sie haben bereits erfahren, wie die erste Aufgabe realisiert wird. Durch Überschreiben einer Methode und Bereitstellung einer neuen Definition für diese Methode haben Sie die Original-Methodendefinition ersetzt, was die Unterklasse betrifft. Manchmal will man die Original-Definition einfach nur ergänzen, statt sie völlig zu ersetzen. Das ist insbesondere dann nützlich, wenn Sie ein Verhalten in der Original-Methode und in der überschreibenden Methode realisieren wollen, so wie im PrintClass-Beispiel gezeigt. Wenn es möglich ist, im Rumpf der überschreibenden Methode die Original-Methode aufzurufen, können Sie diese einfach ergänzen.
Um die Original-Methode aus einer Neudefinition einer Methode heraus aufzurufen, verwenden Sie das Schlüsselwort super, um den Methodenaufruf in der Hierarchie nach oben zu reichen:
void myMethod (String a, String b) {
// Hier irgend etwas machen
super.myMethod(a, b);
// und wieder irgend etwas machen
}
Das Schlüsselwort super ist ein Platzhalter für die Oberklasse dieser Klasse. Sie können es überall dort verwenden, wo Sie auch das Schlüsselwort this verwenden, aber es bezieht sich nicht auf die aktuelle Klasse, sondern auf die Oberklasse.
Statt Verhalten der Methode aus der Oberklasse in der Unterklasse neu anzulegen, können Sie die Methode der Oberklasse auch so definieren, daß ihr ganz einfach zusätzliches Verhalten hinzugefügt werden kann. Hier folgt eine überarbeitete Version des obigen Beispiels, das diesen Sachverhalt demonstriert. Fügen Sie dem Projekt PrintRevClass.java hinzu und geben Sie den in Listing 3.12 gezeigten Code ein.
Listing 3.12: PrintRevClass.java
1: class PrintRevClass {
2: int x = 0;
3: int y = 1;
4:
5: void printMe() {
6: System.out.println("Ich bin eine Instanz der Klasse "
7: + this.getClass().getName());
8: System.out.println("X ist " + x);
9: System.out.println("Y ist " + y);
10: }
11: }
Jetzt klicken Sie wieder auf das Icon Dem Projekt hinzufügen, geben die neue Klasse PrintRevSubClass.java ein und klicken auf OK. Jetzt klicken Sie auf das Inhalts-Feld und geben den in Listing 3.13 gezeigten Code ein.
Listing 3.13: PrintRevSubClass.java
1: class PrintRevSubClass extends PrintRevClass {
2: int z = 3;
3:
4: void printMe() {
5: super.printMe();
6: System.out.println("Z is " + z);
7: }
8:
9: public static void main(String args[]) {
10: PrintRevSubClass obj = new PrintRevSubClass();
11: obj.printMe();
12: }
13: }
Wählen Sie Datei | Alles speichern. Wenn Sie PrintRevSubClass.java ausführen, instantiiert es die Unterklasse PrintRevSubClass, ruft die Methode printMe() auf, wie in PrintRevClass (der Oberklasse) definiert, und führt dann wieder das zusätzliche Verhalten aus, das in der Methode dieser Unterklasse definiert ist.
Ich bin eine Instanz der Klasse PrintRevSubClass
X ist 0
Y ist 1
Z ist 3
Neben den normalen Methoden können Sie in Ihrer Klassendefinition auch Konstruktormethoden definieren.
Wenn für eine Klasse keine speziellen Konstruktormethoden definiert sind, erhalten Sie zwar ein Objekt, aber Sie müssen möglicherweise seine Instanzvariablen setzen oder andere Methoden aufrufen, die das Objekt zu seiner Initialisierung benötigt. Ein Großteil der Klassen, die Sie bisher erzeugt haben, sind auf diese Weise vorgegangen, wobei einfach nur Speicher für das Objekt erzeugt wurde.
Durch die Definition von Konstruktormethoden in Ihren eigenen Klassen können Sie Ausgangswerte für Instanzvariablen angeben, basierend auf diesen Instanzvariablen Methoden aufrufen, die Methoden anderer Objekte aufrufen oder die Ausgangseigenschaften für Ihr Objekt berechnen. Sie können Konstruktoren auch überladen, wie normale Methoden, um ein Objekt zu erzeugen, das bestimmte Eigenschaften hat, basierend auf den Argumenten, die bei der Instantiierung eines Objekts mit dem Schlüsselwort new übergeben wurden.
Konstruktoren sehen ganz ähnlich wie normale Methoden aus, mit zwei grundlegenden Unterschieden:
Listing 3.14 beispielsweise zeigt eine einfache Klasse namens Person, die einen Konstruktor definiert, welcher seine Instanzvariablen basierend auf den mit dem Schlüsselwort new übergebenen Argumente setzt. Die Klasse beinhaltet außerdem eine Methode für das Objekt, sich selbst »einzuführen«, sowie eine main()-Methode, mit der geprüft wird, wie alles funktioniert.
1: class Person {
2: String name;
3: int age;
4:
5: Person(String n, int a) { // Konstruktor
6: name = n;
7: age = a;
8: }
9:
10: void printPerson() {
11: System.out.print("Hi, mein Name ist " + name);
12: System.out.println(". Ich bin " + age + " Jahre alt.");
13: }
14:
15: public static void main(String args[]) {
16:
17: Person p;
18: System.out.println("----------");
19: p = new Person("Michelle", 44);
20: p.printPerson();
21: System.out.println("----------");
22: p = new Person("Phil", 49);
23: p.printPerson();
24: System.out.println("----------");
25: }
26: }
Einige Konstruktoren, die Sie schreiben, können eine Obermenge eines anderen in Ihrer Klasse definierten Konstruktors darstellen. Das bedeutet, sie können das grundlegende Verhalten eines anderen Konstruktors aufweisen, plus ein zusätzliches Verhalten. Statt ein identisches Verhalten in mehreren Konstruktormethoden in Ihrer Klasse zu kopieren, ist es sinnvoll, einfach nur den ersten Konstruktor aus dem Rumpf des zweiten Konstruktors heraus aufzurufen. Java stellt eine spezielle Syntax dafür bereit. Um einen Konstruktor aufzurufen, der in der aktuellen Klasse definiert ist, verwenden Sie die folgende allgemeine Form:
Die Argumente von this sind die Argumente für den Konstruktor. Unter Verwendung des Schlüsselworts this als Methodenaufruf wird der Konstruktor der aktuellen Klasse aufgerufen. Nachdem die Funktion this() zum Aufruf des Original-Konstruktors verwendet wurde, können Sie eine beliebige Funktionalität hinzufügen, die Ihr neuer Konstruktor braucht.
Wie normale Methoden können auch Konstruktoren eine unterschiedliche Anzahl so wie unterschiedliche Typen von Argumenten entgegennehmen, so daß Sie Ihr Objekt mit genau den Eigenschaften erzeugen können, die Sie brauchen, oder mit der Möglichkeit, Eigenschaften aus verschiedenen Eingaben zu brechnen.
Die buildRect()-Methoden beispielsweise, die Sie früher in diesem Kapitel erzeugt haben, sind geeignete Kandidaten für Konstruktoren, weil sie die Instanzvariablen eines Objekts mit den entsprechenden Werten initialisieren. Statt also die definierten buildRect()-Methoden aufzurufen, können Sie auch Konstruktoren erzeugen. Listing 3.15 zeigt die Klasse MyRect.java, die dieselbe Funktionalität aufweist, definiert als mehrere überladene MyRect()-Konstruktoren statt der ursprünglichen überladenen buildRect()-Methoden.
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(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: MyRect(Point topLeft, Point bottomRight) {
17: x1 = topLeft.x;
18: y1 = topLeft.y;
19: x2 = bottomRight.x;
20: y2 = bottomRight.y;
21: }
22:
23: MyRect(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 args[]) {
36: MyRect rect;
37:
38: System.out.println("----------");
39: System.out.println("Aufruf von MyRect() mit den "
40: + "Koordinaten 25, 25, 50, 50:");
41: rect = new MyRect(25, 25, 50, 50);
42: rect.printRect();
43: System.out.println("----------");
44:
45: System.out.println("Aufruf von MyRect() mit den "
46: + "Punkten (10,10) und (20,20):");
47: rect = new MyRect(new Point(10,10), new Point(20,20));
48: rect.printRect();
49: System.out.println("----------");
50:
51: System.out.println("Aufruf von myRect() mit "
52: + "Punkt (15,15), Breite 50 und "
53: + "Höhe 60:");
54: rect = new MyRect(new Point(15,15), 50, 60);
55: rect.printRect();
56: System.out.println("----------");
57: }
58: }
Wenn Sie Methoden in Konstruktoren umwandeln, sollten Sie folgendes beachten:
----------
Aufruf von MyRect() mit den Koordinaten 25, 25, 50, 50:
MyRect: <25,25 , 50,50>
----------
Aufruf von MyRect() mit den Punkten (10,10) und (20,20):
MyRect: <10,10 , 20,20>
----------
Aufruf von MyRect() mit dem Punkt (15,15), Breite 50 und Höhe 60:
MyRect: <15,15 , 65,75>
----------
Technisch gesehen können Konstruktoren nicht überschrieben werden. Weil sie immer denselben Namen wie die aktuelle Klasse haben, müssen Sie neue Konstruktoren erzeugen, statt sie von einer Oberklasse zu erben. Größtenteils ist das kein Problem, denn wenn der Konstruktor Ihrer Klasse aufgerufen wird, werden gleichzeitig alle Konstruktoren mit derselben Signatur in Ihren Oberklassen ebenfalls aufgerufen, um sicherzustellen, daß alle geerbten Teile des Objekts korrekt initialisiert werden.
Wenn Sie jedoch Konstruktoren für Ihre eigene Klasse definieren, wollen Sie möglicherweise beeinflussen, wie Ihr Objekt initialisiert wird - nicht nur, um neue Variablen zu initialisieren, die Ihre Klasse einfügt, sondern auch, um den Inhalt der geerbten Variablen zu ändern. Dazu rufen Sie explizit den Konstruktor Ihrer Oberklasse auf und nehmen dann die entsprechenden Veränderungen vor.
Um eine reguläre Methode in einer Oberklasse aufzurufen, verwenden Sie die folgende Form:
Bei Konstruktoren gibt es keinen Methodennamen, sie verwenden also statt dessen die folgende Form:
Ähnlich der Verwendung der Methode this() in einem Konstruktor ruft die Methode super() den Konstruktor für die unmittelbare Oberklasse auf. Der folgende Code zeigt die Klasse NamedPoint, die die Klasse java.awt.Point erweitert. Die Klasse Point hat nur einen Konstruktor, der ein x- und ein y-Argument entgegennimmt, und sie gibt ein Point-Objekt zurück. NamedPoint hat eine zusätzliche Instanzvariable (einen String, der den Namen des Punkts aufnimmt) und definiert einen Konstruktor, der x, y und name initialisiert:
import java.awt.Point;
class NamedPoint extends Point {
String name;
NamedPoint(int x, int y, String name) { // Konstruktor
super(x, y); // Aufruf des Konstruktors von Point
this.name = name;
}
}
Der Konstruktor ruft die Konstruktormethode von Point auf, wobei die super()-Methode verwendet wird, um die Instanzvariablen von Point zu initialisieren (x und y). Sie könnten x und y zwar auch selbst initialisieren, aber Sie wissen vielleicht nicht, welche anderen Dinge Point noch erledigt, deshalb ist es immer sinnvoller, die Konstruktoren in der Hierarchie nach oben durchzureichen, um sicherzustellen, daß alles korrekt eingerichtet ist.
Finalizer-Methoden sind das Gegenteil von Konstruktormethoden. Während eine Konstruktormethode verwendet wird, um ein Objekt zu initialisieren, wird eine Finalizer-Methode aufgerufen, unmittelbar bevor die Speicherbereinigung dafür ausgeführt und der von ihm belegte Speicher freigegeben wird.
protected void finalize() {...}
Innerhalb des Rumpfs Ihrer finalize()-Methode spezifizieren Sie alle Aufräumarbeiten, die für das Objekt ausgeführt werden sollen. Außerdem können Sie super.finalize() aufrufen, um die Finalizer-Methodendefinitionen der Oberklassen Ihrer Klasse zu nutzen, falls das erforderlich ist.
Sie können die finalize()-Methode jederzeit selbst aufrufen; es handelt sich dabei um eine Methode wie jede andere. Sie wird jedoch implizit aufgerufen, unmittelbar, bevor der vom Objekt belegte Speicher durch die Speicherbereinigung freigegeben wird. Darüber hinaus sollten Sie wissen, daß der Aufruf von finalize() nicht bewirkt, daß die Speicherbereinigung für ein Objekt ausgeführt wird. Es bewirkt nur, daß der Code in der Methode ausgeführt wird. Nur das Entfernen aller Verweise auf ein Objekt bewirkt, daß es zum Löschen markiert wird.
Finalizer-Methoden werden am besten zur Optimierung beim Entfernen eines Objekts genutzt - beispielsweise, indem Verweise auf andere Objekte entfernt werden, indem etwaig angeforderte externe Ressourcen freigegeben werden oder aus irgendeinem anderen Grund, der es einfacher machen könnte, ein Objekt zu entfernen. Größtenteils brauchen Sie finalize() jedoch überhaupt nicht.
Wenn dies Ihr erstes Zusammentreffen mit der objektorientierten Programmierung (OOP) war, dann erschienen Ihnen viele der heute vorgestellten Informationen möglicherweise sehr theoretisch. Lassen Sie sich jedoch nicht entmutigen -je weiter Sie dieses Buch durcharbeiten, und je mehr Java-Programme Sie erzeugen, desto besser werden Sie das Konzept verstehen.
Eine der größten Hürden ist jedoch nicht einmal das Verständnis für das Konzept, sondern die Terminologie. Hier folgt ein Überblick über die Begriffe und Konzepte, die Sie heute kennengelernt haben:
Heute haben Sie viel über Objekte gelernt: wie sie erzeugt werden, wie man die Werte ihrer Variablen ermittelt und ändert, und wie ihre Methoden aufgerufen werden. Darüber hinaus haben Sie erfahren, wie Objekte in andere Objekte oder elementare Datentypen umgewandelt werden, wie elementare Datentypen in Objekte umgewandelt werden, und wie Objekte verglichen werden. Alles, was Sie in Ihren Java-Programmen machen, führt früher oder später zu einem Objekt.
Sie können jetzt Methoden definieren, auch die Komponenten einer Methodensignatur. Sie wissen, wie Werte von einer Methode zurückgegeben werden, wie Argumente übergeben werden und wie das Schlüsselwort this genutzt wird, um auf das aktuelle Objekt zu verweisen. Und Sie haben alles über die Methode main() und ihre Arbeitsweise erfahren.
Darüber hinaus wurden andere Methodentechniken eingeführt, wie etwa das Überladen, das Überschreiben, Konstruktoren und Finalizer-Methoden. Sie haben gelernt, wie mit Hilfe des Schlüsselworts super die Funktionalität von Methoden aus einer Oberklasse wiederverwendet werden kann, wie mit this() ein Konstruktor aus dem Rumpf einer anderen Methodendefinition aufgerufen wird, und wie mit super() der Konstruktor einer Oberklasse aus dem Methodenrumpf einer Unterklasse aufgerufen wird.
Sie haben damit alle Grundlagen, um mit den meisten Aspekten von Java zurechtzukommen. Morgen lernen Sie die komplexeren Funktionen von Java kennen und Sie werden Programme entwickeln, die mit den Java-Klassenbibliotheken arbeiten.
F Ich habe das Konzept der Instanzvariablen und -methoden verstanden, aber nicht das der Klassenvariablen und -methoden. Könnten Sie mir das noch einmal erklären?
A Fast alles, was Sie in einem Java-Programm machen, wird durch Objekte realisiert. Einige Verhalten und Attribute sind jedoch sinnvoller, wenn sie in der Klasse selbst gespeichert werden, und nicht in dem Objekt. Um beispielsweise eine Instanz einer Klasse zu erzeugen, brauchen Sie eine Methode, die für die Klasse selbst definiert ist, und nicht für das Objekt (das ja noch nicht existiert). Sie müssen die Methode aufrufen, um das Objekt zu erzeugen, aber weil Sie noch kein Objekt haben, wie könnten Sie dann die Methode aufrufen? Diese Art Problem kann durch die Verwendung von Klassenmethoden gelöst werden. Klassenvariablen dagegen werden häufig eingesetzt, wenn Sie ein Attribut haben, dessen Wert Sie mit allen anderen Instanzen einer Klasse gemeinsam nutzen wollen. Größtenteils verwenden Sie jedoch Instanzvariablen und -methoden.
F Gibt es eine Obergrenze für die Anzahl der Unterklassen, die eine Klasse haben kann?
A Theoretisch nein. Praktisch gesehen gibt es jedoch eine Begrenzung, die das Dateisystem des Computers vorgibt. Weil die .class-Dateien auf der Festplatte abgelegt werden, stellt der verfügbare Plattenspeicher die Begrenzung dar. Außerdem werden die .class-Dateien, die zu einem Paket gehören, in einem Unterverzeichnis desselben Namens wie das Paket gespeichert, und verschachtelte Pakete werden in den Unterverzeichnisse ihrer Elternpakete gespeichert, so daß möglicherweise auch die Verzeichnisstruktur eine Grenze darstellt. Diese Einschränkungen werden morgen genauer beschrieben.
F Was ist der Vorteil, daß es in Java kein Überladen von Operatoren gibt?
A Das Argument gegen das Überladen von Operatoren ist, daß der Operator so definiert werden kann, daß er alles mögliche bedeuten kann, so daß manchmal nicht deutlich ist, was er in einem bestimmten Kontext bedeutet. Das macht den Code fehleranfälliger und schwieriger zu warten. Java sollte einfacher und robuster als die Sprachen sein, die das Überladen von Operatoren erlauben, deshalb wurde diese Funktion nicht implementiert.
F Ich habe versucht, in einer Methode eine konstante Variable anzulegen, habe aber einen Compilerfehler erhalten. Was habe ich falsch gemacht?
A Das Problem ist, daß Sie keine konstanten lokalen Variablen erzeugen können. Mit Hilfe des Schlüsselworts final können nur Klassen- oder Instanzvariablen als Konstanten angelegt werden.
F Ich habe zwei Methoden mit den folgenden Signaturen erzeugt:
Der Java-Compiler beschwert sich über eine »Duplicate Definition«, wenn ich versuche, die Klasse zu kompilieren, aber ihre Signaturen sind doch unterschiedlich! Was habe ich falsch gemacht?
A Beim Überladen erkennt Java den Rückgabetyp nicht als Teil der Methodensignatur, deshalb beschwert sich der Compiler darüber, daß die Signaturen gleich sind, weil sie sich nur im Rückgabetyp unterscheiden (was in diesem Fall kein Unterscheidungsmerkmal ist). Das Überladen funktioniert nur, wenn sich die Argumenteliste unterscheidet (anhand von Anzahl oder Typ der Argumente oder beidem). Weil der Rückgabetyp erst bekannt ist, wenn Java festgestellt hat, welche Methode zur Laufzeit ausgeführt werden soll, betrachtet es den Rückgabetyp als irrelevant, wenn es feststellt, ob die Methode überladen ist.
F Kann ich überschriebene Methoden überladen? Das heißt, kann ich Methoden erzeugen, die denselben Namen wie eine geerbte Methode haben, aber eine andere Parameterliste?
A Warum nicht? Solange es eine unterschiedliche Argumenteliste gibt, ist es egal, ob sich die Original-Definition der Methode in derselben Klasse oder in einer Oberklasse befindet - Sie können eine geerbte Klasse genauso überladen wie jede andere Methode.
Der Workshop bietet zwei Möglichkeiten, zu überprüfen, was Sie in diesem Kapitel gelernt haben. Der Quiz-Teil stellt Ihnen Fragen, die Ihnen helfen sollen, Ihr Verständnis für den vorgestellten Stoff zu vertiefen. Die Antworten auf die Fragen finden Sie in Anhang A. Der Übungen-Teil ermöglicht Ihnen, Erfahrungen in der Anwendung der Dinge zu sammeln, die Sie hier kennengelernt haben. Versuchen Sie, diese Dinge durchzuarbeiten, bevor Sie mit der nächsten Lektion weitermachen.
boolean aBoolean = true;
Boolean boolObject = new Boolean(aBoolean);
Point1: 200, 200 Poitn2: 100, 100
2. Stellen Sie fest, was im folgenden LunchTime-Konstruktor falsch ist, und versuchen Sie, es zu korrigieren.
void LunchTime(String n, String f, int a) { // Konstruktor name = n; food = f; age = a; return this; }