Objektorientierte Programmierung und Java
von Laura Lemay
Heute erhalten Sie einen Einblick in die Konzepte der objektorientierten Programmierung von Java und die Art, wie sie sich auf die Struktur Ihrer Programme auswirken:
Wenn Sie mit objektorientierter Programmierung vertraut sind, ist der Großteil der heutigen Lektion für Sie ein alter Hut. Vielleicht gehen Sie statt dessen lieber in's Kino. Morgen gibt es wieder etwas Interessantes für Sie.
Betrachten wir einmal als Vergleich Legos. Für diejenigen, die nicht viel mit Kindern zu tun haben, sei erwähnt, daß Legos kleine Bausteine aus Kunststoff in verschiedenen Formen und Farben sind. Jedes Bausteinchen hat auf einer Seite kleine Noppen, die in runde Löcher anderer Bausteinchen passen, so daß aus mehreren Legos größere Formen zusammengesteckt werden können. Aus verschiedenen Lego-Teilen (Lego-Rädern, Lego-Motoren, Lego-Anhängerkupplungen) können wiederum größere Teile, z. B. Schlösser, Autos oder Riesenroboter, zusammengesetzt werden. Die Kombinationsmöglichkeiten sind schier endlos. Lego-Steinchen sind kleine Objekte, die auf vorgegebene Weise zusammengesetzt werden können, um größere Objekte zu bilden.
Hier ein anderes Beispiel. Sie gehen in einen Computerladen und stellen sich aus verschiedenen Teilen ohne viel Ahnung mit ein bißchen Hilfe des Verkäufers einen kompletten PC zusammen: eine Mutterplatine, einen CPU-Chip, eine Videokarte, eine Festplatte, eine Tastatur usw. Im Idealfall erhalten Sie ein System, dessen Einheiten zusammen ein größeres System bilden, mit dem Sie die Probleme lösen, für die Sie den Rechner eigentlich gekauft haben.
Intern können diese Teile komplex sein und von verschiedenen Herstellern stammen, die sie nach verschiedenen Konstruktionsmethoden gebaut haben. Sie braucht das aber nicht zu interessieren, was die einzelnen Chips auf der Karte alles machen oder wie ein «A» am Bildschirm erscheint, wenn Sie die A-Taste auf der Tastatur drücken. Alle Komponenten sind unabhängige Einheiten, die Sie nur insoweit interessieren, als sie (hoffentlich) nahtlos als Gesamtsystem zusammenarbeiten. Paßt diese Videokarte in den Steckplatz der Mutterplatine und funktioniert sie mit diesem Monitor? Sprechen die einzelnen Komponenten die richtige Sprache, um einander verstehen zu können? Wenn Sie wissen, welche Interaktionen zwischen den Komponenten ablaufen, können Sie mühelos als Laie ein Komplettsystem zusammenstellen.
Was hat das mit Programmierung zu tun? Alles. Objektorientierte Programmierung funktioniert genau auf diese Weise. Durch objektorientierte Programmierung besteht ein Gesamtprogramm aus vielen unabhängigen Komponenten (Objekten), die je eine bestimmte Rolle im Programm spielen und die miteinander auf vorgegebene Weise sprechen.
Objektorientierte Programmierung ist das Modellieren von Objekten, die wie im echten Leben aus vielen kleineren Objekten bestehen. Diese Möglichkeit des Kombinierens von Objekten ist aber nur ein sehr kleiner Aspekt der objektorientierten Programmierung. Objektorientierte Programmierung bietet viele andere Konzepte und Merkmale, um Objekte einfacher und flexibler zu gestalten und zu benutzen. Das wichtigste Merkmal ist die Klasse.
Eine
Klasse ist eine Sammlung aller Eigenschaften
und Methoden der Objekte dieser Klasse und legt fest, wie diese
zu erzeugen sind.
Wenn Sie ein Programm in einer objektorientierten Sprache schreiben, definieren Sie keine Objekte an sich. Sie definieren vielmehr Objektklassen.
Nehmen wir beispielsweise an, Sie haben eine Klasse namens Baum, die die Merkmale aller Bäume (haben Blätter und Wurzeln, wachsen, produzieren Chlorophyll) beschreibt. Die Baum-Klasse dient als abstraktes Modell für das Konzept eines Baums - die Hand ausstrecken und ihn anfassen, also mit ihm interagieren, oder ihn fällen - bedarf einer konkreten Instanz eines Baumes. Selbstverständlich können Sie viele Instanzen eines Baumes schaffen und jede Bauminstanz kann andere Merkmale haben (klein, groß, buschig, winterhart oder abfallend), ein Baum bleibt aber immer ein Baum (siehe Abb. 2.1).
Eine
Instanz einer Klasse ist ein anderes Wort
für ein tatsächliches Objekt. Während Klassen eine
abstrakte Darstellung eines Objekts sind, ist eine Instanz dessen
konkrete Darstellung.
Was genau ist also der Unterschied zwischen einer Instanz und einem Objekt? Eigentlich nichts. Objekt ist ein allgemeinerer Begriff, doch sind sowohl Instanzen als auch Objekte die konkrete Darstellung einer Klasse. In der objektorientierten Programmierung werden die Begriffe Instanz und Objekt meist gleichbedeutend verwendet. Eine Instanz eines Baumes und ein Baumobjekt sind das gleiche.
Als Beispiel, das dem Thema Java-Programmierung näher
kommt, könnten Sie eine Klasse für ein Element der Benutzeroberfläche,
etwa eine Schaltfläche (Button) erstellen. Die Button-Klasse
definiert die Merkmale eines Buttons (Beschriftung, Größe,
Aussehen) und wie er sich verhält (muß er einfach oder
doppelt angeklickt werden, ändert er beim Anklicken seine
Farbe, was wird durch ihn bewirkt?). Haben Sie die Button-Klasse
definiert, können Sie mühelos Instanzen dieses Buttons,
d. h. Button-Objekte, erstellen, die alle die Grundzüge des
ursprünglichen Buttons aufweisen, aber anders aussehen und
sich anders verhalten, je nach dem, wofür sie dienen. Dank
der Button-Klasse brauchen Sie nicht für jeden einzelnen
Button, den Sie in Ihrem Programm benötigen, einen eigenen
Code zu schreiben. Außerdem können Sie die Button-Klasse
wiederverwenden, um andere Button-Arten für das gleiche oder
andere Programme zu erstellen.
Abbildung 2.1: Baumklasse mit Bauminstanzen
Falls
Sie aus der C-Welt kommen, können Sie sich eine Klasse als
einen neuen, mittels struct und typedef zusammengesetzten Datentyp
vorstellen. Klassen bieten aber mehr als nur eine Sammlung von
Daten, wie sie im weiteren Verlauf dieser Lektion noch feststellen
werden.
Beim Schreiben eines Java-Programms werden Klassen entworfen und zusammengestellt. Läuft das Programm, werden Instanzen dieser Klassen nach Bedarf erstellt oder verworfen. Ihre Aufgabe als Java-Programmierer ist es, die richtigen Klassen anzulegen, um das zu erreichen, was Ihr Programm bewältigen soll.
Zum Glück müssen Sie nicht von Null beginnen. Die Java-Umgebung beinhaltet eine Bibliothek mit Klassen, die viele Grundzüge implementieren, nicht nur für grundlegende Programmieraufgaben (Klassen mit mathematischen Grundfunktionen, Arrays, Zeichenketten usw.), sondern auch für Grafiken und Vernetzung. In vielen Fällen reicht die Java-Klassenbibliothek aus, so daß Sie für ein Java-Programm nur eine Klasse erstellen müssen, die auf der Standard-Klassenbibliothek basiert, und die gewünschten Änderungen einbringen. Für komplizierte Java-Programme können Sie mehrere Klassen mit definierten Interaktionen erstellen.
Eine
Klassenbibliothek ist eine Sammlung von Klassen.
Jede Klasse, die Sie in Java schreiben, besteht im allgemeinen aus zwei Komponenten: Attribute und Eigenschaften. In diesem Abschnitt lernen Sie, wie diese Komponenten auf eine theoretische Klasse namens Motorcycle angewandt werden. Am Schluß dieser Lektion erstellen Sie den Java-Code zur Implementierung einer Präsentation eines Motorrades.
Attribute sind die einzelnen Dinge, durch die sich ein Objekt von anderen unterscheidet. Sie bestimmen Aussehen, Zustand und andere Qualitäten eines Objekts. Wir erstellen jetzt eine theoretische Klasse namens Motorcycle. Ein Motorrad kann folgende Attribute haben:
Die Attribute eines Objekts können auch Informationen über seinen Zustand enthalten, z. B. Merkmale des Motorzustands (aus oder ein) oder den eingelegten Gang.
Attribute werden durch Variablen definiert. Sie können Sie sich als globale Variablen für das gesamte Objekt vorstellen. Da jede Instanz einer Klasse verschiedene Werte für ihre Variablen haben kann, nennt man jede Variable eine Instanzvariable.
Jedes Attribut hat eine einzelne entsprechende Instanzvariable. Durch Änderung des Wertes einer Variablen ändert sich das Attribut des Objekts. Instanzvariablen können gesetzt werden, wenn ein Objekt erstellt wird. Sie bleiben durch die gesamte Lebensdauer des Objekts konstant oder können sich nach Belieben bei der Ausführung des Programms ändern.
Zusätzlich zu Instanzvariablen gibt es Klassenvariablen, die die Klasse selbst und alle ihre Instanzen betreffen. Im Gegensatz zu Instanzvariablen, deren Werte in der Instanz gespeichert werden, werden die Werte von Klassenvariablen direkt in der Klasse gespeichert. Sie lernen in dieser Woche mehr über Klassenvariablen. Instanzvariablen werden in der morgigen Lektion behandelt.
Um das Verhalten eines Objekts zu definieren, erstellen Sie Methoden, die mit Funktionen in anderen Sprachen vergleichbar sind, jedoch innerhalb einer Klasse definiert werden. In Java werden keine Funktionen außerhalb von Klassen (wie etwa bei C++) definiert.
Methoden
sind Funktionen, die innerhalb von Klassen definiert
werden und auf Klasseninstanzen angewandt werden.
Methoden wirken sich aber nicht immer nur auf ein Objekt aus. Objekte kommunizieren auch miteinander durch Methoden. Eine Klasse oder ein Objekt kann Methoden einer anderen Klasse oder eines anderen Objekts aufrufen, um Änderungen in der Umgebung mitzuteilen oder das Objekt aufzufordern, seinen Zustand zu ändern.
Ebenso wie es Instanz- und Klassenvariablen gibt, gibt es auch Instanz- und Klassenmethoden. Instanzmethoden (die so üblich sind, daß Sie normalerweise nur «Methoden» genannt werden) werden auf eine Instanz angewandt. Klassenmethoden werden auf eine Klasse (oder auf andere Objekte) angewandt. Sie lernen noch in dieser Woche mehr über Klassenmethoden.
Diese Lektion war bisher eher theoretisch. In diesem Abschnitt erstellen Sie ein funktionierendes Beispiel der Motorcycle-Klasse, so daß Sie sehen können, wie Instanzvariablen und Methoden in einer Klasse definiert werden. Sie erstellen außerdem eine Java-Anwendung, die eine neue Instanz der Klasse Motorcycle erzeugt und deren Instanzvariablen anzeigt.
Ich gehe
bei diesem Beispiel nicht in's Detail über die Syntax. Machen
Sie sich keine Sorgen, falls Sie nicht alles verstehen. Später
in dieser Woche erhalten Sie mehr Klarheit. Wichtig ist bei diesem
Beispiel nur, daß Sie die grundlegenden Teile dieser Klassendefinition
verstehen.
Sind Sie bereit? Dann fangen wir mit der grundlegenden Klassendefinition an. Öffnen Sie den Editor und geben Sie folgendes ein:
class Motorcycle { }
Herzlichen Glückwunsch! Sie haben soeben eine Klasse angelegt. Selbstverständlich passiert da nicht viel, aber Sie haben eine Java-Klasse in ihrer einfachsten Form.
Jetzt erstellen wir einige Instanzvariablen für diese Klasse - drei, um genau zu sein. Geben Sie nach der ersten Zeile folgende drei Zeilen ein:
String make; String color; boolean engineState;
Damit haben Sie drei Instanzvariablen erstellt: make und color können String-Objekte (String bedeutet «Zeichenkette» und ist Teil der vorher erwähnten Standard-Klassenbibliothek) enthalten. engineState ist ein boolescher Ausdruck, der bestimmt, ob der Motor läuft oder ausgeschaltet ist.
In Java
ist ein boolescher Ausdruck ein echter Datentyp, der den Wert
true (wahr) oder false (falsch) haben kann. Im Gegensatz zu C
sind boolesche Ausdrücke hier keine Zahlen. Sie erfahren
morgen mehr darüber.
Jetzt werden bestimmte Eigenschaften (Methoden) in die Klasse eingefügt. Ein Motorrad kann sich auf unterschiedliche Weise verhalten. Wir halten uns in diesem Beispiel kurz und fügen nur eine Methode ein, die den Motor anläßt. Geben Sie unter den Instanzvariablen in Ihrer Klassendefinition folgende Zeilen ein:
void startEngine() { if (engineState == true) System.out.println("The engine is already on."); else { engineState = true; System.out.println("The engine is now on."); } }
Die startEngine-Methode prüft, ob der Motor bereits läuft (in der Zeile engineState == true). Trifft das zu, gibt sie eine diesbezügliche Meldung aus. Falls der Motor noch nicht läuft, ändert sich der Zustand des Motors auf true, dann erfolgt eine Meldung.
Speichern Sie das Programm als Datei namens Motorcycle.java ab (denken Sie daran, daß Sie Ihre Java-Dateien immer wie die Klasse, die sie definieren, benennen sollten). Ihr bisheriges Programm sollte so aussehen:
class Motorcycle { String make; String color; boolean engineState; void startEngine() { if (engineState == true) System.out.println("The engine is already on."); else { engineState = true; System.out.println("The engine is now on."); } } }
Die Einrückungen
sind nicht Teil des Programms und haben für
den Java-Compiler keine Bedeutung. Sie sichern lediglich eine
bessere Übersichtlichkeit. In diesem Buch werden Instanzvariablen
und Methoden unter der Klassendefinition eingerückt. In den
Java-Klassenbibliotheken wird diese Einrückungsart ebenfalls
angewandt. Sie können aber eine andere Einrückungsart
anwenden.
Bevor Sie diese Klasse kompilieren, fügen wir noch eine Methode ein. Die showAtts-Methode gibt die aktuellen Werte der Instanzvariablen in einer Instanz der Motorcycle-Klasse aus. Das sieht so aus:
void showAtts() { System.out.println("This motorcycle is a " + color + " " + make); if (engineState == true) System.out.println("The engine is on."); else System.out.println("the engine is off."); }
Die showAtts-Methode gibt zwei Zeilen am Bildschirm aus: Das Modell (make) und die Farbe (color) des Objekts Motorrad, und ob der Motor ein oder aus ist.
Speichern Sie die Datei und kompilieren Sie sie mit javac:
javac Motorcycle.java
Ab diesem
Punkt gehe ich davon aus, daß Sie wissen, wie man Java-Programme
kompiliert und ausführt, so daß hierzu keine Anleitungen
mehr folgen.
Was passiert nun, wenn Sie den Java-Interpreter benutzen, um diese kompilierte Klasse auszuführen? Versuchen Sie's. Java nimmt an, daß diese Klasse eine Anwendung ist und sucht nach einer main-Methode. Da es sich aber nur um eine Klasse handelt, ist keine main-Methode vorhanden. Der Java-Interpreter (java) gibt eine Fehlermeldung aus, die in etwa so aussieht:
In class Motorcycle: void main(String argv[]) is not defined
Um etwas mit der Motorcycle-Klasse anfangen zu können, z. B. Instanzen dieser Klasse erstellen und damit spielen, müssen Sie eine Java-Anwendung schreiben, die diese Klasse benutzt, oder in diese Klasse eine main-Methode einfügen. Der Einfachheit halber entscheiden wir uns für letzteres. In Listing 2.1 sehen Sie die main()-Methode, die Sie in die Motorcycle-Klasse einfügen. Was dadurch bewirkt wird, folgt in Kürze.
Listing 2.1: main()-Methode für Motorcycle.java
1. public static void main (String args[]) { 2: Motorcycle m = new Motorcycle(); 3: m.make = "Yamaha RZ350"; 4: m.color = "yellow"; 5: System.out.println("Calling showAtts..."); 6: m.showAtts(); 7: System.out.println("......."); 8: System.out.println("Starting engine..."); 9: m.startEngine(); 10: System.out.println("......."); 11: System.out.println("Calling showAtts..."); 12: m.showAtts(); 13: System.out.println("......."); 14: System.out.println("Starting engine..."); 15: m.startEngine(); 16: }
Mit der main()-Methode ist die Motorcycle-Klasse jetzt eine Anwendung.
Wenn Sie sie erneut kompilieren, läuft sie diesmal. Die Ausgabe
sollte so aussehen:
Calling showAtts... This motorcycle is a yellow Yamaha RZ350 The engine is off. ........ Starting engine... The engine is now on. ........ Calling showAtts... This motorcycle is a yellow Yamaha RZ350 The engine is on. ........ Starting engine... The engine is already on.
Der Inhalt
der main()-Methode sieht für Sie ganz neu aus, deshalb gehen
wir ihn Zeile für Zeile durch, damit Sie verstehen, was passiert
(Einzelheiten hierüber morgen und übermorgen).
Sie haben einen Einblick in das Grundprinzip von Klassen, Objekten, Methoden und Variablen bekommen. Sie haben auch gesehen, wie alles in einem Java-Programm zusammengesetzt wird. Nun ist es an der Zeit, wieder für etwas Verwirrung zu sorgen. Vererbung, Schnittstellen und Pakete sind Mechanismen zum Organisieren von Klassen und ihren Eigenschaften. Die gesamte Java-Klassenbibliothek nutzt diese Konzepte. Auch die besten Klassenbibliotheken, die Sie für Ihre eigenen Programme schreiben, nutzen diese Konzepte.
Jede Klasse ist einer Superklasse untergeordnet (die Klasse über
der Hierarchie). Jede Klasse kann eine oder mehr Subklassen haben
(Klassen unter einer anderen Klasse in der Hierarchie). Klassen
weiter unten in der Hierarchie erben von den übergeordneten
Klassen.
Abbildung 2.2: Klassenhierarchie
Subklassen erben alle Methoden und Variablen von ihren Superklassen. Das bedeutet, daß Sie eine Klasse nicht neu definieren oder den Code von einer anderen Klasse kopieren müssen, wenn die Superklasse das Verhalten aufweist, das Sie für die neue Klasse brauchen. Ihre Klasse erbt automatisch das Verhalten von ihrer Superklasse. Diese Superklasse erbt ihr Verhalten wiederum von ihrer Superklasse usw. in der Hierarchie nach oben.
Ganz oben in der Java-Klassenhierarchie befindet sich die Klasse Object. Alle Klassen erben von dieser Superklasse. Object ist eine allgemeine Klasse. Sie definiert Verhaltensmuster, die alle Objekte in der Java-Klassenhierarchie erhalten. Weiter unten in der Hierarchie haben die Klassen zusätzliche Informationen und sind für einen spezifischeren Zweck ausgelegt. Auf diese Weise kann man sich eine Klassenhierarchie als Definition sehr abstrakter Konzepte vorstellen, die von oben nach unten stufenweise konkreter werden.
In der Regel muß man eine Klasse erstellen, die alle Informationen einer anderen Klasse sowie zusätzliche Informationen hat. Eventuell benötigen Sie eine Version einer Schaltfläche mit einer anderen Beschriftung. Sie können alle Button-Informationen in einem Schritt zusammentragen, indem Sie lediglich festlegen, daß Ihre Klasse von Button erben soll. Ihre Klasse hat dann automatisch alle in Button definierten Eigenschaften (Button ist damit die Superklasse der neuen Klasse), so daß Sie sich nur noch um die Unterschiede zwischen der Superklasse und der neuen Klasse zu kümmern brauchen. Dieser Mechanismus zur Definition neuer Klassen, bei dem nur Unterschiede festgelegt werden, heißt Subclassing.
Subclassing
bedeutet das Erstellen einer neuen Klasse - einer
sogenannten Subklasse, die von einer anderen Klasse in
der Klassenhierarchie erbt. Anhand dieser Methode müssen
nur die Unterschiede zwischen der neuen Klasse und ihrer Superklasse
definiert werden. Alles übrige erhält die Klasse durch
Vererbung.
Was nun, wenn Sie eine Klasse definieren müssen, die ein
völlig anderes Verhalten aufweisen muß, das in keiner
anderen Klasse vorkommt? In diesem Fall kann Ihre Klasse trotzdem
alles von Object erben und sauber in die Java-Klassenhierarchie
eingepaßt werden. Wenn Sie eine Klasse definieren, die in
der ersten Zeile auf keine Superklasse verweist, geht Java automatisch
davon aus, daß die Vererbung von Object stammt. Die Motorcycle-Klasse,
die Sie im vorherigen Abschnitt geschrieben haben, hat von Object
geerbt.
Müssen Sie eine größere Anzahl von Klassen erstellen, ist es sinnvoll, daß die Klassen nicht nur von der vorhandenen Klassenhierarchie erben, sondern daß die Hierarchie erweitert wird. Das muß meist im voraus geplant werden, um den Java-Code richtig organisieren zu können. Dieser Aufwand bietet aber entscheidende Vorteile:
Betrachten wir als Beispiel die Motorcycle-Klasse und nehmen wir an, Sie haben ein Java-Programm geschrieben, um alle Merkmale eines Motorrads zu implementieren. Sie haben die Arbeit beendet, das Programm funktioniert und alle sind zufrieden. Die nächste Aufgabe ist nun, eine Java-Klasse namens Car (Auto) zu schreiben.
Autos und Motorräder haben viele gemeinsame Merkmale. Beide sind Kraftfahrzeuge, die von Motoren angetrieben werden. Beide haben Scheinwerfer und Tachometer. Im ersten Anlauf sind Sie vielleicht geneigt, die Datei mit der Motorcycle-Klasse zu öffnen und die brauchbaren Informationen in die neue Klasse Car zu kopieren.
Sie tun das aber nicht, weil es eine bessere Vorgehensweise gibt: Sie fassen die gemeinsamen Informationen für Car und Motorcycle zu einer allgemeineren Klassenhierarchie zusammen. Das hört sich zunächst wegen nur zwei Klassen nach relativ viel Aufwand an. Bedenken Sie aber, daß Sie dadurch irgendwann Klassen für Fahrräder, Lastwagen usw. definieren können, die alle gemeinsame Eigenschaften haben. Diese Eigenschaften können Sie aus einer wiederverwendbaren Superklasse entnehmen, so daß sich insgesamt langfristig der Arbeitsaufwand erheblich reduziert.
Wir entwerfen nun eine Klassenhierarchie, die diesem Zweck dient. Ganz oben befindet sich die Klasse Object, die Wurzel aller Java-Klassen. Die gröbste Klasse, zu der Motorräder und Autos gehören können, wäre z. B. Fahrzeug. Ein Fahrzeug wird im allgemeinen als Gegenstand definiert, der jemanden mit oder ohne körperliche Einwirkung von einer Stelle zu einer anderen befördert. Sie definieren also die Klasse Vehicle (Fahrzeug) lediglich mit den Eigenschaften, die ermöglichen, daß der Gegenstand jemanden von A nach B befördert, sonst nichts.
Unterhalb von Vehicle? Wie wär's mit diesen Klassen: PersonPoweredVehicle
und EnginePoweredVehicle? Damit definieren Sie zwei der Klasse
Vehicle untergeordnete Klassen, wobei eine Fahrzeuge betrifft,
die durch die körperliche Kraft des Menschen und eine, die
durch die Kraft von Motoren betrieben werden. EnginePoweredVehicle
unterscheidet sich von PersonPoweredVehicle dadurch, daß
es einen Motor, Eigenschaften wie Anlassen und Ausschalten gibt,
Benzin verbraucht wird und verschiedene Geschwindigkeiten möglich
sind. Von Menschen angetriebene Fahrzeuge haben andere Mechanismen,
um jemanden von A nach B zu befördern, z. B. Pedale. Diese
Hierarchie ist in Abb. 2.3 dargestellt.
Abbildung 2.3: Die Basishierarchie Vehicle
Um spezifischer zu werden, können Sie für EnginePoweredVehicle mehrere Klassen für Motorräder, Autos, Lastwagen usw. anlegen. Sie können aber weitere Verhaltenmuster zu einer anderen Superklasse zusammenfassen, so daß Zwischenklassen für zwei- und vierrädrige Fahrzeuge entstehen (siehe Abb. 2.4).
Mit einer Subklasse für die zweirädrigen Kraftfahrzeuge können Sie schließlich eine Klasse für Motorräder definieren. Alternativ können Sie zusätzlich Kleinkrafträder und Mopeds definieren. Beide sind zweirädrige Kraftfahrzeuge, unterscheiden sich aber von Motorrädern.
Wo stehen nun Qualitäten wie Modell oder Farbe? Wo Sie wollen;
normalerweise an der Stelle, an der sie innerhalb der Klassenhierarchie
am besten passen und am sinnvollsten angeordnet sind. Sie können
Modell und Farbe in Vehicle definieren, dann erben alle Subklassen
von Vehicle auch diese Variablen. Wichtig ist dabei, sich vor
Augen zu halten, daß ein Merkmal oder Verhalten nur einmal
in der Hierarchie definiert werden muß und automatisch in
jeder Subklasse wiederverwendet wird.
Abbildung 2.4: Klassen für zwei- und vierrädrige Kraftfahrzeuge
Wie kann es sein, daß Instanzen einer Klasse automatisch Variablen und Methoden von übergeordneten Klassen innerhalb der Hierarchie erben?
Erstellen Sie eine neue Instanz einer Klasse, erhalten Sie einen «Ausschnitt» jeder Variablen, die in der aktuellen Klasse definiert ist und für jede Variable, die in allen ihren Superklassen definiert ist. Auf diese Weise bilden alle Klassen zusammen eine Maske für das aktuelle Objekt. Jedes Objekt übernimmt dann die jeweils brauchbaren Informationen.
Methoden funktionieren auf ähnliche Weise: Neue Objekt haben Zugang zu allen Methodennamen ihrer Klasse und deren Superklassen. Jedoch werden Methodendefinitionen dynamisch beim Aufrufen einer Methode gewählt. Das bedeutet, daß Java beim Aufrufen einer Methode für ein bestimmtes Objekt zuerst die Klasse des Objekts auf Definition der betreffenden Methode prüft. Ist sie nicht in der Klasse des Objekts definiert, sucht Java in der Superklasse dieser Klasse usw. aufwärts in der Hierarchie, bis die Definition der Methode gefunden wird (siehe Abb. 2.5).
Die Dinge sind etwas komplizierter, wenn eine Subklasse eine Methode
definiert, die die gleiche Unterschrift (Name, Anzahl und Typ
der Argumente) wie eine in einer Superklasse definierte Methode
hat. In diesem Fall wird die Methodendefinition, die zuerst (von
unten nach oben in der Hierarchie) gefunden wird, ausgeführt.
Aus diesem Grund kann es sinnvoll sein, eine Methode in einer
Subklasse zu definieren, die die gleiche Unterschrift hat wie
eine Methode in der Superklasse, so daß die Methode der
Superklasse «verborgen» wird. Diesen Vorgang nennt man
Overriding bzw. Überschreiben einer Methode.
Sie lernen am 7. Tag alles über Methoden.
Abbildung 2.5: Vorgang des Auffindens von Methoden
Overriding
ist ein Vorgang, bei dem eine Methode in einer Subklasse
erstellt wird, die die gleiche Unterschrift (Name, Zahl und Typ
der Argumente) hat wie eine Methode in einer Superklasse. Durch
diese neue Methode wird die Methode der Superklasse
überschrieben (siehe Abb. 2.6).
Javas Vererbungssystem basiert auf der Einzelvererbung. Das bedeutet, daß jede Java-Klasse nur eine Superklasse haben kann (während jede Superklasse mehrere Subklassen haben kann).
In anderen objektorientierten Programmiersprachen, z. B. C++ und
Smalltalk, können Klassen mehr als eine Superklasse haben
und verschiedene Variablen und Methoden aus mehreren Klassen erben.
Das ist die Mehrfachvererbung. Dieses System ist
dahingehend leistungsstark, daß man Klassen erstellen kann,
die alle nur erdenklichen Verhaltensmuster auf sich vereinen.
Andererseits wird die Klassendefinition dadurch wesentlich komplizierter
und der daraus entstehende Code ist verhältnismäßig
komplex.
Abbildung 2.6: Klassen für zwei- und vierrädrige Kraftfahrzeuge
Java hat zwei weitere Konzepte, die hier diskutiert werden: Pakete und Schnittstellen. Beides sind wichtige Themen zur Implementierung und zum Design von Klassengruppen und Klassenverhalten. Sie lernen über Schnittstellen und Pakete am 16. Tag. Hier folgt eine kurze Einführung.
Sie erinnern sich, daß Java-Klassen nur eine Superklasse haben und Variablen und Methoden von dieser Superklasse und allen Superklassen der Superklasse erben. Die Einfachvererbung vereinfacht zwar die Beziehung zwischen Klassen und die Funktionalität der Klassen, kann aber einschränkende Wirkungen haben, insbesondere, wenn ein bestimmtes Verhalten auf mehrere «Zweige» der Klassenhierarchie dupliziert werden muß. Java löst dieses Problem durch Anwendung des Konzeptes von Schnittstellen.
Eine
Schnittstelle ist in der Java-Sprache eine Sammlung
von Methodennamen ohne Definitionen, die andeuten, daß eine
Klasse außer dem von der Superklasse geerbten Verhalten
zusätzliche Eigenschaften hat.
Obwohl eine einzelne Java-Klasse (aufgrund der Einfachvererbung) nur eine Superklasse haben kann, können in einer Klasse mehrere Schnittstellen implementiert werden. Durch Implementieren einer Schnittstelle bietet eine Klasse Methodenimplementierungen (Definitionen) für die durch die Schnittstelle definierten Methodennamen. Implementieren zwei verschiedene Klassen die gleiche Schnittstelle, können beide auf die gleichen Methodenaufrufe (die durch diese Schnittstelle definiert werden) reagieren, obwohl die Reaktion der jeweiligen Klasse unterschiedlich sein kann.
Sie müssen an diesem Punkt keine Einzelheiten über Schnittstellen lernen. Dieser Lehrstoff folgt später; er würde hier nur unnötig verwirren.
Das letzte heute diskutierte Java-Konzept sind Pakete.
Sie lernen in Woche 3 alles über Pakete, unter anderem auch, wie sie erstellt und verwendet werden. Vorläufig genügt es, folgende Punkte zu verstehen:
Die Klassenbibliothek des Java Developer's Kits befindet sich in einem Paket namens java. Die im java-Paket enthaltenen Klassen sind garantiert in jeder Java-Implementierung verfügbar. Das sind die einzigen Klassen, deren Verfügbarkeit in allen Implementierungen garantiert ist. Das java-Paket selbst enthält andere Pakete für Klassen, die die Sprache an sich sowie die Ein- und Ausgabeklassen, einige Vernetzungsfunktionen und die Fensterfunktionen beinhalten. Klassen in anderen Paketen (z. B. Klassen in den Sun- oder Netscape-Paketen) sind eventuell nur in bestimmten Implementierungen verfügbar.
Standardmäßig haben Java-Klassen Zugang zu den in java.lang befindlichen Klassen (dem im java-Paket enthaltenen Sprachpaket). Um Klassen eines anderen Pakets benutzen zu können, müssen Sie sie entweder explizit nach Paketnamen einbeziehen oder in Ihre Quelldatei importieren.
Um auf eine Klasse eines Pakets zu verweisen, listen Sie alle Pakete, in der sich die Klasse befindet, und den Klassennamen durch Punkte (.) getrennt auf. Um beispielsweise die Color-Klasse zu nehmen, die sich im awt-Paket («awt» ist die Abkürzung von «Abstract Windowing Toolkit»), das sich im java-Paket befindet, setzen Sie in Ihr Programm eine Referenz auf die Color-Klasse im Format java.awt.Color.
Zum Schluß der heutigen Lektion erstellen wir eine Klasse, die eine Subklasse einer anderen Klasse ist, und wenden einige Overriding-Methoden an. Außerdem erhalten Sie in diesem Beispiel einen ersten Eindruck darüber, wie Pakete funktionieren.
Der häufigste Grund, eine Subklasse zu erstellen, ist das Schreiben eines Applets, zumindest beim Beginn der Java-Programmierung. Alle Applets sind Subklassen der Klasse Applet (die Teil des java.applet-Pakets ist). Durch Erstellen einer Subklasse von Applet erhalten Sie automatisch alle Eigenschaften aus dem Window-Toolkit und den Layoutklassen. Dadurch kann Ihr Applet an der richtigen Stelle auf der Seite ausgegeben werden und mit Systemoperationen, z. B. Tastenanschlägen und Mausklicks, interagieren.
In diesem Beispiel erstellen Sie ein Applet, das dem HelloWorld-Applet
von gestern ähnlich ist, jedoch die Hello-Zeichenkette in
einer größeren Schrift und in einer anderen Farbe ausgibt.
Zu Beginn dieses Beispiels erstellen wir zunächst die Klassendefinition.
Erinnern Sie sich an die HTML- und Klassenverzeichnisse von gestern?
Diese verwenden Sie wieder. Öffnen Sie Ihren Texteditor und
geben Sie folgende Klassendefinition ein:
public class HelloAgainApplet extends java.applet.Applet { }
Hier erstellen Sie die Klasse HelloAgainApplet. Beachten Sie, daß der Teil extends java.applet.Applet bestimmt, daß Ihre Applet-Klasse eine Subklasse der Applet-Klasse ist.
Die Applet-Klasse ist automatisch im java.applet-Paket enthalten, deshalb brauchen Sie nicht auf diese Klasse zugreifen. Statt dessen beziehen Sie sich explizit auf das Paket und den Klassennamen.
Der andere Teil dieser Klassendefinition ist das Schlüsselwort public. Das bedeutet, daß Ihre Klasse nach dem Laden für das gesamte Java-System verfügbar ist. Normalerweise muß eine Klasse nur public sein, wenn sie für alle anderen Klassen Ihres Java-Programms sichtbar sein soll. Applets müssen public deklariert werden. Sie lernen in Woche 3 mehr über public-Klassen.
Eine Klassendefinition ohne Inhalt ist nicht sinnvoll. Ohne Hinzufügen oder Überschreiben von Variablen oder Methoden der Superklasse ist das Erstellen einer Subklasse sinnlos. Deshalb fügen wir in diese Klasse einige Informationen ein, um sie von ihrer Superklasse zu unterscheiden.
Zuerst geben wir eine Instanzvariable für ein Font-Objekt ein:
Font f = new Font("TimesRoman", Font.BOLD,36);
Die Instanzvariable f enthält jetzt eine neue Instanz der Klasse Font, die Teil des java.awt-Pakets ist. Dieses Font-Objekt ist die Schriftart Times Roman, Fett, 36 Punkt. Im vorherigen HelloWorld-Applet wurde für den Text der Standardfont benutzt: Times Roman 12 Punkt. Sie können die Schrift eines Font-Objekts für Text in Ihrem Applet jederzeit ändern.
Durch Erstellen einer Instanzvariablen, die dieses Font-Objekt enthält, stellen Sie es allen Methoden der Klasse zur Verfügung. Nun erstellen wir eine Methode, die das Objekt nutzt.
Für Applets gibt es mehrere «Standardmethoden»,
die in Applet-Superklassen definiert sind, mit denen eine Applet-Klasse
überschrieben wird (Overriding). Dazu zählen Methoden
zum Initialisieren des Applets, zum Handhaben von Operationen,
wie Mausbewegungen oder Mausklicks, oder zum «Aufräumen»,
wenn das Applet beendet wird. Eine dieser Standardmethoden ist
paint(), durch die das Applet am Bildschirm angezeigt wird. Die
Standarddefinition von paint() macht überhaupt nichts - sie
ist eine leere Methode. Durch Überschreiben von paint() weisen
Sie das Applet an, was am Bildschirm auszugeben ist. Hier eine
Definition von paint():
public void paint(Graphics g) { g.setFont(f); g.setColor(Color.red); g.drawString("Hello again!", 5, 25); }
Diese Methode muß wie das Applet selbst public deklariert werden. Das Überschreiben der paint()-Methode ist ebenfalls public. Versuchen Sie, eine Methode in Ihrer Klasse zu überschreiben, die in einer Superklasse public ist, erhalten Sie einen Kompilierfehler. Das heißt, daß diese Methode immer public sein muß.
Innerhalb der paint()-Methode haben Sie im obigen Beispiel drei Dinge erreicht:
Sie haben das Grafikobjekt angewiesen, daß die Standardschrift in der Instanzvariablen f enthalten ist.
Sie haben das Grafikobjekt angewiesen, daß die Standardfarbe eine Instanz der Color-Farbe für die Farbe Rot ist.
Sie haben die Ausgabe Ihrer Zeichenkette "Hello again!" am Bildschirm in x- und y-Position 5 und 25 angegeben. Die Zeichenkette wird in der Standardschrift und -farbe angezeigt.
Für ein derart einfaches Applet genügt das. Ihr Applet sieht bisher so aus:
public class HelloAgainApplet extends java.applet.Applet { Font f = new Font("TimesRoman",Font.BOLD,36); public void paint(Graphics g) { g.setFont(f); g.setColor(Color.red); g.drawString("Hello again!", 5, 25); } }
Vielleicht haben Sie bemerkt, daß mit diesem Beispiel etwas nicht stimmt. Wenn Sie nicht genau wissen, was, versuchen Sie, diese Datei abzuspeichern (unter dem gleichen Namen wie die Klasse, also HelloAgainApplet.java) und sie mit dem Java-Compiler zu kompilieren. Sie erhalten mehrere Fehlermeldungen, z. B. diese:
HelloAgainApplet.java:7: Class Graphics not found in type declaration.
Warum erscheinen diese Fehlermeldungen? Weil die Klassen, auf die Sie verweisen, Teil eines Pakets sind. Wie Sie wissen, ist java.lang das einzige Paket, auf das automatisch zugegriffen wird. Sie haben in der ersten Zeile der Klassendefinition durch Angabe des vollen Paketnamens (java.applet.Applet) auf die Applet-Klasse Bezug genommen. Weiter unten im Programm haben Sie jedoch auf alle Arten anderer Klassen so Bezug genommen, als wären sie bereits verfügbar.
Dieses Problem kann auf zwei Arten gelöst werden: Beziehen Sie sich auf alle externen Klassen mit dem vollen Paketnamen oder importieren Sie die betreffende Klasse bzw. das Paket in Ihre Klassendatei an den Dateianfang. Welche Lösung Sie wählen, steht Ihnen frei. Falls Sie auf eine Klasse, die in einem anderen Paket enthalten ist, häufig verweisen, ist das Importieren weniger zeitaufwendig als die manuelle Eingabe.
In diesem Beispiel importieren wir die benötigten Klassen. Insgesamt sind das drei: Graphics, Font und Color. Alle drei befinden sich im Paket java.awt. Nachfolgend die Zeilen zum Importieren dieser Klassen. Fügen Sie sie an den Anfang vor der eigentlichen Klassendefinition ein:
import java.awt.Graphics; import java.awt.Font; import java.awt.Color;
Sie können
auch ein ganzes Paket von (public) Klassen importieren, indem
Sie anstelle eines spezifischen Klassennamens ein Sternchen (*)
tippen. Um beispielsweise alle Klassen des awt-Pakets zu importieren,
können Sie folgendes eingeben:
import java.awt.*;
Jetzt sollte sich HelloAgainApplet richtig in eine Klassendatei kompilieren lassen. Um das zu prüfen, erstellen Sie eine HTML-Datei mit dem Tag <APPLET> genau wie gestern. Hier die zu verwendende HTML-Datei:
<HTML> <HEAD> <TITLE>Another Applet</TITLE> </HEAD> <BODY> <P>My second Java Applet says:<BR> <APPLET CODE="HelloAgainApplet.class" WIDTH=200 HEIGHT=75> </APPLET> </BODY> </HTML>
Für dieses HTML-Beispiel ist Ihre Java-Klassendatei im gleichen
Verzeichnis wie die HTML-Datei. Speichern Sie die Datei unter
HelloAgainApplet.html und starten Sie Ihren Java-Browser oder
den Java-Appletviewer. Abb. 2.7 zeigt das Ergebnis, das Sie nun
erhalten sollten (die Zeichenkette Hello Again ist Rot):
Abbildung 2.7: Das HelloAgainApplet
Wenn das Ihre erste Begegnung mit objektorientierter Programmierung war, erscheint Ihnen eine Menge in diesem Kapitel theoretisch und kompliziert. Keine Sorge - je mehr Java-Anwendungen Sie im weiteren Verlauf dieses Buches schreiben, um so besser werden Sie das alles verstehen.
Eine der größten Hürden der objektorientierten Programmierung sind nicht unbedingt die Konzepte, sondern die Namen. In der objektorientierten Programmierung werden viele Fachbegriffe verwendet. Insgesamt haben Sie heute folgende Fachbegriffe gelernt:
F: Methoden sind effektiv Funktionen, die innerhalb von Klassen definiert werden. Warum nennt man sie nicht Funktionen, obwohl sie wie Funktionen aussehen und sich so verhalten?
A: In einigen objektorientierten Programmiersprachen werden sie Funktionen (bzw. in C++ Member-Funktionen) genannt. Andere objektorientierte Sprachen unterscheiden zwischen Funktionen innerhalb und außerhalb einer Klasse oder eines Objekts. Getrennte Begriffe sind wichtig, um die Funktionsweise zu verdeutlichen. Da der Unterschied in anderen Sprachen relevant ist und der Begriff «Methode» jetzt in der objektorientierten Technologie üblich ist, wird er auch in Java verwendet.
F: Ich verstehe Instanzvariablen und -methoden, aber nicht Klassenvariablen und -methoden.
A: Fast alles, was man in einem Java-Programm hat, sind Objekte. Einige Eigenschaften und Attribute sind jedoch sinnvoller, wenn sie in der Klasse, nicht im Objekt gespeichert werden. Um eine neue Instanz einer Klasse zu erstellen, brauchen Sie z. B. eine Methode, die für die Klasse, nicht für das Objekt definiert ist. (Andernfalls können Sie keine Instanz der Klasse erstellen. Sie brauchen ein Objekt, um die neue Methode aufzurufen, haben aber noch kein Objekt.) Demgegenüber werden Klassenvariablen häufig benutzt, wenn ein Attribut bzw. dessen Wert auch in der Instanz einer Klasse benutzt werden soll.
Vorwiegend werden Instanzvariablen und -methoden verwendet. Sie lernen hierüber noch in dieser Woche mehr.