2
von
Laura Lemay
Objektorientierte Programmierung (OOP) ist einer der Begriffe, der in den letzten Jahren in der Programmierwelt große Verwirrung stiftet. Man könnte Jahre damit verbringen, alles über die Methoden der objektorientierten Programmierung und ihre Möglichkeiten, die »altmodische« Programmierung zu vereinfachen, zu lernen. Im Prinzip handelt es sich um das Organisieren von Programmen auf eine Art, die Dinge wie in der wirklichen Welt zusammenstellt.
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 Legosteine. Für diejenigen, die nicht viel mit Kindern zu tun haben, sei erwähnt, daß Legosteine 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 Legosteinen 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 Mainboard, eine CPU, eine Grafikkarte, eine Festplatte, eine Tastatur usw. Im Idealfall erhalten Sie ein System, dessen Einzelteile 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 Grafikkarte in den Steckplatz des Mainboard 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.
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 Baumes 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).
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äherkommt, 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 Button (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 Button, 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:
|
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 elementare Dinge 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 komplexere Java-Programme können Sie mehrere Klassen mit definierten Interaktionen erstellen.
Jede Klasse, die Sie in Java schreiben, besteht im allgemeinen aus zwei Komponenten: Eigenschaften und Methoden. 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 Eigenschaften haben:
Die Eigenschaften eines Objekts können auch Informationen über seinen Zustand enthalten, z.B. Merkmale des Motorzustands (aus oder ein) oder den eingelegten Gang.
Eigenschaften 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.
Jede Eigenschaft 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.
Die Methoden einer Klasse bestimmen, was die Instanzen der Klasse tun, wenn sich ihr interner Zustand ändert oder wenn die Instanz von einer anderen Klasse oder einem Objekt aufgefordert wird, etwas zu tun. Die Methoden bestimmen, was Objekte alles machen können und was man mit Objekten machen kann. Um die theoretische Klasse Motorcycle wieder aufzugreifen, gibt es einige Methoden, die diese Klasse haben könnte:
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 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 (diese werden normalerweise nur »Methoden« genannt) 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 beschreibe bei diesem Beispiel die Syntax nicht im Detail. 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 Methoden in die Klasse eingefügt. Ein Motorrad kann sich auf unterschiedliche Weise verhalten. Wir fassen 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). Bisher sollte Ihr Programm 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 sorgen lediglich für 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 verwenden.
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 Hersteller (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.
Eines der wichtigsten Konzepte in der objektorientierten Programmierung ist die Vererbung. Sie hat keinen direkten Einfluß darauf, wie Java-Klassen entworfen und geschrieben werden. Vererbung ist ein mächtiger Mechanismus, der bewirkt, daß Sie, um eine Klasse zu schreiben, nur bestimmen müssen, wie sich die Klasse von einer anderen Klasse unterscheidet. Gleichzeitig haben Sie dynamischen Zugang zu den Informationen dieser anderen Klasse.
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:
|
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 autmatisch 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.
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 sollten dies allerdings nicht tun, da 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.
Und 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 besitzen andere Antriebsvarianten, um jemanden von A nach B zu befördern, z.B. Pedale. Diese Hierarchie ist in Abb. 2.3 dargestellt.
Abbildung 2.3:
|
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 Eigenchaften 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:
|
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 Signatur (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 Signatur 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:
|
Abbildung 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. Die Stärke dieses System ist, 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.
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.
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 Kit 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 über den Paketnamen ansprechen 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 verwenden, 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 überschreiben einige Methoden. 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 Applet, 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 Windows-Toolkit und den Layoutklassen. Dadurch kann Ihr Applet an der richtigen Stelle auf der Seite ausgegeben werden und auf Systemoperationen, z.B. Tastenanschlägen und Mausklicks, ragieren.
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. Dazu zählen Methoden zum Initialisieren des Applets, zur Behandlunng von Ereignissen, 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ß.
Die paint()-Methode hat nur ein Argument: eine Instanz der Graphics-Klasse. Die Graphics-Klasse bietet ein plattformunabhängiges Verhalten für Fonts, Farben und einfache Zeichnungen. Sie lernen in Woche 2 mehr über die Graphics-Klasse, wenn Sie anspruchsvollere Applets erstellen.
Innerhalb der paint()-Methode haben Sie im obigen Beispiel drei Dinge erreicht:
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 erscheint diese Fehlermeldung? 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 andere 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-Applet-Viewer. Abb. 2.7 zeigt das Ergebnis, das Sie nun erhalten sollten (die Zeichenkette Hello Again ist rot):
Abbildung 2.7:
|
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 Terminologie üblich ist, wird er auch in Java verwendet.
F Ich verstehe die Idee hinter Instanzvariablen und -methoden, aber nicht die hinter Klassenvariablen und -methoden.
A Fast alles, was man in einem Java-Programm hat, sind Objekte. Für einige Eigenschaften und Methoden ist es 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.) Klassenvariablen werden verwendet, wenn eine Eigenschaft bzw. deren Wert allen Instanzen einer Klasse zur Verfügung stehen soll.
Vorwiegend werden Instanzvariablen und -methoden verwendet. Sie lernen hierüber noch in dieser Woche mehr.
(c) 1997 SAMS