) einf.gen. Alles zwischen dem Start-Tag und dem Ende-Tag wird gekennzeichnet, damit der Anwender weiß, dass auf diese Weise markierte Texte und Grafiken angeklickt werden kGnnen. Das folgende Beispiel wird einen Hypertext-Link zur Hompage der Firma Sun im Beispieldokument einbetten:
182
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Erstellen von Applets mit dem JDK
<TITLE>Beispiel HTML-Dokument HTML Demo
Dieses Dokument ist ein HTML Beispiel. Das Java Development Kit finden Sie auf der JavaSoft Home Page. Das Absatz-Tag (
) verbessert die Lesbarkeit des Dokuments. Ein Webbrowser ignoriert .berz$hlige Leerzeichen und Zeilenumbr.che bei der Darstellung eines Dokuments. Wenn Sie Zeilenumbr.che oder Abs$tze in Ihr Dokument einf.gen wollen, dann m.ssen Sie entsprechend das
-Tag oder das
-Tag verwenden. Nun verf.gt das Beispieldokument .ber Text, Grafik und einen Link, wie in der folgenden Abbildung gezeigt wird:
Abb. 7.7 Ein HTML-Beispieldokument mit Text, Grafik und Hypertext-Link
Ein Java-Applet in ein HTML-Dokument einzuf.gen, ist recht einfach. Es gibt ein <APPLET>",4>-Tag, das die Stelle angibt, an der sich die .class-Datei befindet und welcher Platz im Dokument f.r das Applet bereitgestellt wird. Angenommen, Sie wollen ein Uhr-Applet in Ihr Dokument einf.gen, das die aktuelle Uhrzeit mit Stunden, Minuten und Sekunden anzeigt. Hier ein einfaches Beispiel eines <APPLET>-Tags, das ein Uhr-Applet (Clock.class) l$dt: 183
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Applets, Applikationen und das Java Development Kit
<APPLET CODE="Clock.class" WIDTH=200 HEIGHT=60> Wenn der Webbrowser das <APPLET>-Tag erreicht, wird die VM gestartet und Clock.class geladen. Der VM wird außerdem mitgeteilt, dass vom Applet eine Fl$che von 200x60 Pixel benutzt werden darf. Die Position des <APPLET>-Tags im Dokument bestimmt die Koordinaten der linken oberen Ecke des darstellenden Bereichs des Applets. F.gen Sie die Zeile nun der Beispieldatei hinzu:
<TITLE>Beispiel HTML-Dokument HTML Demo
Dieses Dokument ist ein HTML Beispiel. Das Java Development Kit finden Sie auf der JavaSoft Home Page. <APPLET CODE="Clock.class" WIDTH=200 HEIGHT=60> Dieses Dokument kGnnte jetzt zum Beispiel so dargestellt werden:
Abb. 7.8 Das Uhr-Applet eingebettet in ein HTML-Dokument
184
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Erstellen von Applets mit dem JDK
Wie Sie sehen kGnnen, ist das Einbinden von Java-Applets in HTML-Dokumente recht einfach. Java kann Plug-in-Komponenten erzeugen, die von Anf$ngern und Fortgeschrittenen gleichermaßen benutzt werden kGnnen. Damit dieses Komponentenmodell funktionieren kann, muss der HTML-Autor die Eigenschaften und das Verhalten des Applets .ber HTML anpassen kGnnen. Der Java-Programmierer entscheidet, welche Parameter f.r das Applet von Bedeutung sind und der HTMLAutor .bergibt mit dem -Tag die initialen Parameter an das Applet, zum Beispiel so: <APPLET CODE="FilledBox.class" WIDTH=50 HEIGHT=50> Das -Tag akzeptiert zwei Argumente: NAME und VALUE. NAME wird verwendet, um den Namen eines Parameters anzugeben, VALUE .bergibt den entsprechenden Wert. Soweit es Java betrifft, sind alle .bergebenen Parameter StringObjekte, also Zeichenketten, die jedoch leicht in jeden anderen Datentyp umgewandelt werden kGnnen. Die Ausgabe des FilledBox-Applets ist in der folgenden dargestellt. Beachten Sie bitte, dass nur die Ausgabe des Applets dargestellt wird:
Abb. 7.9 Der Applet Viewer bei der Ausf1hrung des FilledBox-Applets aus der Datei FilledBox.html
Enth$lt der Webbrowser eine Java-VM, wird er das Applet darstellen und mit Ausnahme des -Tags alles ignorieren, das sich zwischen den <APPLET>- und -Tags befindet. Webbrowser, die nicht dazu in der Lage sind, Java auszuf.hren, ignorieren die <APPLET>- und -Tags und stellen stattdessen alle g.ltigen HTML Anweisungen zwischen dem <APPLET>- und -Tag dar. Dies ermGglicht es Ihnen, Alternative Inhalte f.r Browser zu erstellen, die kein Java ausf.hren kGnnen. Dies ist besonders wichtig, da nicht alle Anwender Java-Applets ausf.hren kGnnen oder wollen. Wenn Sie Ihre Webseiten f.r jeden Anwender benutzbar machen wollen, dann m.ssen Sie auch solche Anwender ber.cksichtigen, die kein Java ausf.hren kGnnen.
Wie Webbrowser Applets ausfhren Ein Browser, der Java ausf.hren kann, befolgt eine bestimmte Reihenfolge von Schritten, die er ausf.hrt, wenn er im HTML-Dokument auf ein <APPLET>-Tag stGßt:
185
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Applets, Applikationen und das Java Development Kit
1
Der Browser reserviert zur Darstellung des Applets Platz im Dokument. Die Parameter WIDTH und HEIGHT des <APPLET>-Tags geben die Fl$che an, die vom Applet belegt wird.
2
Der Browser liest die Parameter des -Tags aus.
3
Die VM wird gestartet und dazu aufgefordert, das Applet zu laden und zu initialisieren. Das Applet hat Zugriff auf die Namen und Werte innerhalb der -Tags.
4
Die VM erzeugt eine Laufzeitkopie des Applets, basierend auf der .class-Datei.
5
Der Browser ruft die init-Methode auf, damit sich das Applet selbst initialisiert.
6
Die VM ruft die start-Methode des Applets auf, sobald sie dazu bereit ist, das Applet mit der Verarbeitung anfangen zu lassen. Die VM ruft außerdem die paint-Methode auf, um das Applet im Browser darzustellen.
7
Wann immer ein Applet erneut dargestellt werden muss (zum Beispiel wenn der Anwender das Applet in den sichtbaren Bereich des Browsers hineinbewegt), wird paint erneut aufgerufen.
8
Der Browser ruft die stop-Methode auf, wenn der Anwender das gegenw$rtig dargestellte HTML-Dokument durch ein anderes ersetzt.
9
Der Browser ruft die destroy-Methode auf, wenn das Applet aus dem Speicher entfernt wird.
Der Quellcode des Applets Der Quellcode eines Java-Applets wird genau so erstellt wie der Quellcode einer Java-Applikation: mit einem Texteditor. Der Unterschied besteht darin, dass JavaApplets keine main-Methode haben. Stattdessen verf.gen sie .ber einige andere Methoden, die von der VM aufgerufen werden, wenn der Browser diese anfordert. Hier der Quellcode f.r das FilledBox-Applet: import java.awt.*; import java.applet.Applet; /** FilledBox stellt einen farbig ausgefuellten Kasten im Browser / Fenster dar */ public class FilledBox extends Applet { // Diese Variable speichert die Farbe, die im HTML// Dokument als Color boxColor; angegeben wird. Color boxColor; 186
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Erstellen von Applets mit dem JDK
/** Hole die Farbe des Kastens aus der HTML-Datei */ public void init() { String s; s = getParameter("color"); // Die vorgegebene Farbe ist grau. boxColor = new Color ( 0,0,0); // Erwartet wird ein Parameter color, der die // Werte red, white oder blue haben kann. Fehlt // der Parameter, wird s null. if (s != null) { if (s.equals("red")) boxColor = Color.red; if (s.equals("white")) boxColor = Color.white; if (s.equals("blue")) boxColor = Color.blue; } } /** Stelle den Kasten in der dem Applet zugewiesenen * Region dar. Benutze die im HTML-Dokument angege* bene Farbe. */ public void paint(Graphics g) { g.setColor (boxColor); g.fillRect (0, 0, size().width, size().height); } } Dies ist ein wenig komplizierter als das Beispiel der Java-Applikation, aber das liegt daran, dass es mehr macht, als die Applikation. Sie werden sich daran erinnern, dass die main-Methode von allen Java-Applikationen benGtigt wird. Diese fehlt hier. Tats$chlich sind in Applets gar keine Methoden erforderlich. Es gibt jedoch f.nf Methoden, die von der VM aufgerufen werden kGnnen, wenn diese vom Webbrowser (oder Applet Viewer) angefordert werden: public void init() initialisiert das Applet. Wird einmal aufgerufen. public void start() wird aufgerufen, wenn der Browser bereit ist, das initialisierte Applet zu starten. Kann mehrfach aufgerufen werden, wenn der Anwender die Webseite mehrfach aufruft. Wird ebenfalls aufgerufen, wenn das Browserfenster aus dem Status iconified wiederhergestellt wird. public void stop() wird aufgerufen, wenn der Browser die Ausf.hrung des Applets beenden will. Wird aufgerufen, wenn der Anwender die Webseite verl$sst, die das Applet enth$lt. Wird ebenfalls aufgerufen, wenn das Browserfenster in den Status iconified wechselt. public void destroy() wird aufgerufen, wenn der Browser das Applet aus dem Speicher entfernt. public void paint(Graphics g) wird aufgerufen, wenn der Browser das Applet erneut darstellen muss.
187
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Applets, Applikationen und das Java Development Kit
Wenn in dem Applet keine dieser Methoden implementiert ist, wird es f.r die entsprechenden Methoden keine Funktionalit$t f.r die entsprechende Methode aufweisen. In dem Beispiel sind die Methoden init und paint implementiert. Die Methode init() bezieht die Farbe des Kastens aus einem Parameter des HTML-Dokuments, in das es eingebettet ist (Applet-Parameter sind bereits im Abschnitt „HTML fr Java-Applets“ erkl$rt worden.) Speichern Sie diesen Quellcode f.r das Java-Applet unter dem Namen FilledBox.java ab.
Verwenden von javac Der javac-Compiler arbeitet bei Applets genau so, wie Sie dies bereits von den JavaApplikationen her kennen: Javac FilledBox.java Hier einige Tipps, die Ihnen den Start erleichtern sollen: Als Erstes muss ein Applet immer als public deklariert werden oder es wird nicht kompiliert. Weiterhin sollten Sie beachten, dass f.r Java die Schreibweise von Bedeutung ist: filledbox.java ist etwas Anderes als FilledBox.java (und wird nicht kompiliert werden), es wird zwischen GROSSBUCHSTABEN und kleinbuchstaben unterschieden. Wenn der Java-Code f.r den Compiler akzeptabel ist, wird sich die einzige Meldung, die Sie sehen werden, auf ein veraltetes API beziehen: Note: FilledBox.java uses a deprecated API. Recompile with "-deprecation" for details. 1 warning. Im Augenblick kGnnen Sie diese Warnung ignorieren. Solange keine weiteren Fehlermeldungen erscheinen, wird die Datei FilledBox.class erzeugt. Falls weitere Fehlermeldungen ausgegeben wurden, m.ssen Sie Ihren Code .berarbeiten. Es gibt eine Vielzahl von Fehlermeldungen, die Ihr Compiler ausgeben kann, wenn er einen Quellcode bearbeitet. Am einfachsten sind Syntaxfehler zu beheben, wie zum Beispiel ein fehlendes Semikolon oder schließende Klammern. Andere Fehler werden die falsche Verwendung von Variablentypen oder ung.ltige Ausdr.cke oder VerstGße gegen Zugriffsbeschr$nkungen betreffen. Den Quellcode erfolgreich zu kompilieren, ist der erste Schritt des Prozesses der Fehlerbereinigung (so genanntes Debugging). Fehlerfreies Kompilieren garantiert nicht, dass Ihr Programm auch das tut, was Sie wollen. Machen Sie sich aber noch keine Sorgen um das Debugging. Das Beispiel sollte einfach genug sein, dass es ohne Probleme l$uft.
Hinweis
188
Zwischen Java 1.0 und Java 1.1 wurden einige Methoden umbenannt. Bis alle Anwender auf Java 1.1 umgestiegen sind, kann es erforderlich sein, die Java-1.0-Methoden zu verwenden. Sollten Sie einen Java-1.1-f$higen Browser verwenden, kGnnen Sie die size()-Aufrufe gegen getSize() austauschen, um den Warnhinweis zu umgehen.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Erstellen von Applets mit dem JDK
Bevor Sie Ihr Applet benutzen kGnnen, m.ssen Sie ein HTML-Dokument erstellen, das Ihr Applet ansteuert.
Erstellen einer HTML-Datei Da Sie jetzt bereits .ber grundlegende HTML-Kenntnisse verf.gen, kGnnen Sie sich sehr leicht eine HTML-Datei erstellen, in die Sie Ihr Applet einbinden:
<TITLE>Beispiel HTML-Dokument mit FilledBox.class FilledBox Demo
<APPLET CODE="FilledBox.class" WIDTH=50 HEIGHT= 50> Sie kGnnen diese Datei erzeugen, indem Sie das Listing einfach in einen Texteditor eingeben. Speichern Sie die Datei als FilledBox.html. HTML-Dateien kGnnen jeden beliebigen Namen erhalten. Es ist allerdings .blich, die HTML-Dokumente nach den Applets zu benennen, die sie beherbergen. Wenn Sie die HTML-Datei nach dem Applet benennen und dann javadoc auf den Quellcode anwenden, wird die HTML-Datei .berschrieben. Hinweis
Verwenden von appletviewer appletviewer wird verwendet, um das Applet so darzustellen, wie es in einem Browser dargestellt wird, ohne jedoch das HTML-Dokument anzuzeigen. Im Falle der FilledBox.html-Datei wird appletviewer nur das ausgef.llte K$stchen in einem eigenen Fenster darstellen: appletviewer FilledBox.html Sehen Sie unter Abbildung 7.9 nach, um einen Eindruck von der Ausgabe zu bekommen. Zum Vergleich zeigt die folgende Abbildung das HTML-Dokument, wie es zum Beispiel im Netscape Navigator aussehen w.rde. Hier werden der Text und das Applet dargestellt.
189
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Applets, Applikationen und das Java Development Kit
Abb. 7.10 Netscape Navigator stellt die Datei FilledBox.html dar
Wenn in einer HTML-Datei mehr als ein Applet enthalten ist, wird appletviewer f.r jedes Applet ein eigenes Fenster Gffnen. Ein Webbrowser wird alle in einem HTMLDokument enthaltenen Applets in einer Webseite darstellen. Eine angenehme F$higkeit von appletviewer ist die MGglichkeit, Klassen nicht nur aus lokalen Dateien laden zu kGnnen, sondern auch .ber Netzwerke hinweg.
Verwenden von javadoc und javah Die Werkzeuge javadoc und javah arbeiten auch auf der Grundlage des Quellcodes eines Applets. Die Kommandozeilen sind dieselben wie bei der Erstellung einer Applikation: javadoc FilledBox.java und 190
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Neu in Java 2
javah FilledBox.class Da das vorgestellte Beispiel sehr ausf.hrlich kommentiert wurde, wird javadoc brauchbare Ergebnisse liefern, die es Ihnen erleichtern, mitzuverfolgen, was der Code im Einzelnen macht. Obwohl dies f.r ein so einfaches Applet wie das hier vorgestellte nicht so sehr entscheidend ist, ist dies doch insgesamt eine n.tzliche Lektion f.r sp$tere Projekte, die deutlich komplizierter sein werden.
Tipp
Ausliefern von Applikationen zusammen mit der Java-Laufzeitumgebung JRE Die Java-Laufzeitumgebung JRE (Java Runtime Environment) wurde mit Java 1.1 eingef.hrt. Wenn Sie ein Programm verteilen wollen, m.ssen Sie den Anwendern eine Java-Plattform anbieten, um das Programm auszuf.hren. Das JRE ist ein solches Programm. Es bietet den Anwendern eine Java-VM an, mit der das Programm ausgef.hrt werden kann. Sie kGnnen die JRE entweder zusammen mit Ihrem Programm in einem Paket anbieten oder aber auch beides getrennt ausliefern. Grunds$tzlich sollte es ausreichen, wenn Sie sich das JRE einmal f.r alle Java-Anwendungen installieren, die es nutzen wollen. Indem Sie die JRE zusammen mit Ihrem Programm vertreiben, kGnnen Sie sicher sein, dass die Anwender eine kompatible Java-VM besitzen. Die Version 1.3 der JRE f.r Java 2 kann kostenlos von der Website der Firma Sun bezogen werden. F.r das Entwickeln der Software ist die JRE nicht notwendig, aber f.r den Vertrieb der Software ist sie von großer Bedeutung.
Hinweis
Neu in Java 2 Wenn Sie bereits mit der Entwicklung von Software mit dem JDK 1.1 vertraut sind, wird es im Allgemeinen f.r Sie kaum Neuerungen bei der Erstellung von Applets und Applikationen mit Java 2 geben. Wenn Sie aber bislang in einer Java-1.0-Umgebung gearbeitet haben, dann werden Ihnen einige Snderungen auffallen, die bereits mit der Version 1.1 eingef.hrt wurden. Hier wird eine detaillierte Liste der Snderungen vorgestellt. Diese Liste besteht aus Snderungen, die JDK-Werkzeuge betreffen und ist nicht als Liste aller neuen Funktionen von Java 2 zu verstehen:
(appletviewer) <APPLET>-Tag =nderungen Dieses Tag zum Laden von Java-Applets wurde ver$ndert. Sie kGnnen jetzt Ressourcen und andere Objekte angeben, die zusammen mit dem Applet geladen werden.
191
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Applets, Applikationen und das Java Development Kit
(javac) @missbiligtes Tag Der javac-Compiler wird Sie jetzt warnen, wenn Sie Methoden verwenden, die zwar in $lteren Versionen des JDK unterst.tzt werden, aber nicht den in der aktuellen Version bevorzugten Methoden entsprechen. (N$heres .ber alle missbilligten Methoden kGnnen Sie der Java-Plattform 1.3 API-Spezifikation unter http://java.sun. com/products/jdk/1.3/docs/api/index.html entnehmen.)
(jar) Java-Archive Java-Klassen und -Ressourcen, wie zum Beispiel Bilder und TGne, kGnnen nun in komprimierten Archiven zusammengefasst werden, den so genannten .jar-Dateien. Dabei werden digitale Signaturen unterst.tzt und die
(javah) Neue native Methodenschnittstelle Die Schnittstelle f.r den Aufruf nativer Methoden ist .berarbeitet und f.r alle Plattformen standardisiert worden. Die Werkzeuge zum Signieren des Codes haben sich zwischen Java 1.1 und 2 ge$ndert. Fr.her hat javakey alles verwaltet, stattdessen gibt es nun drei Werkzeuge:
(keytool) Java-Schlsselgenerator Programm zum Erstellen von Schl.sseln zur digitalen Signatur von Klassen.
(jarsigner) Java-Archivsignatur Programm zur digitalen Signatur von .class- und .jar-Dateien, damit diese authentifiziert werden kGnnen.
(policytool) Verwaltung von Sicherheitsrichtlinien Applikation, die bei der Konfiguration der Sicherheitsrichtlinien einer Installation hilft. Drei ganz neue Werkzeuge wurden mit Java 2 eingef.hrt, die sich speziell an fortgeschrittene Konzepte richten:
(tnameserv) IDL Transient Name Server Der Name Server muss vor COBRA COS (Common Object Services) gestartet werden, damit die entsprechenden Dienste genutzt werden kGnnen (weitere Informationen .ber dieses Werkzeug kGnnen Sie Kapitel 17: JavaBeans: Ein Jberblick entnehmen). 192
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ausblick
(rmid) System-Daemon zur RMI-Aktivierung Der Daemon muss gestartet werden, bevor aktivierbare RMI-Objekte verwendet werden (weitere Informationen zu RMI kGnnen Sie Kapitel 17: JavaBeans: Ein Jberblick entnehmen).
(servletrunner) Werkzeug zur Ausfhrung von Servlets Benutzen Sie dieses Werkzeug, um Testl$ufe Ihrer Servlets durchzuf.hren, bevor Sie diese in einem Webserver installieren (weitere Informationen .ber Servlets kGnnen Sie den Randnotizen dieses Kapitels entnehmen).
Ausblick In Kapitel 8: Arbeiten mit Java-Objekten werden Konzepte der objektorientierten Programmierung vorgestellt, inklusive polymorpher Funktionen, Konstruktor und Finalizer.
193
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
Eine Einfhrung in die OOP
196
Konstruktor und Finalizer
213
Ausblick
218
8
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
Das Paradigma der objektorientierten Programmierung (OOP) hat sich im Verlaufe des letzten Jahrzehnts in der Softwareindustrie verbreitet und dabei Vorteile in Bezug auf die Produktivit$t der Programmierer, der Wiederverwendbarkeit und der Wartungsf$higkeit von Software mit sich gebracht. OOP wird heute als „beste Praxis“ in der Softwareentwicklung angesehen. Als vollst$ndig objektorientierte Programmiersprache erfordert Java ein eingehendes Verst$ndnis dieser Objektorientierung, um effektiv sein zu kGnnen. Bezugnehmend darauf beginnt dieses Kapitel mit einer Einf.hrung in die OOP.
Eine Einf,hrung in die OOP Im Kern ist OOP eine Sichtweise auf Problemstellungen und deren LGsungsmGglichkeiten. Statt Programme in einem linearen Stil, dem so genannten top-downAnsatz zu erstellen (wie dies zum Beispiel bei den traditionellen Programmiersprachen C und Pascal der Fall ist), versucht das OOP ein Problem in seine Bestandteile aufzubrechen. Dieser LGsungsansatz konzentriert sich auf diese unabh$ngigen Objekte und deren Beziehungen zu anderen Objekten. Dieser Ansatz ist vielen Aufgaben besser angepasst, da die meisten Problemstellungen komplex und vielschichtig sind und deshalb nicht zu einem linearen Ansatz konform sind. Objektklassen sind den Strukturen und record-Typen von nicht-OOP-Sprachen sehr $hnlich, deshalb beginnt dieser Abschnitt mit der Betrachtung einfacher Datenstrukturen und den solchen Strukturen innewohnenden Problemen der Softwareentwicklung. Um die Kontinuit$t des Beispielcodes dieses Kapitels zu gew$hrleisten und ein illustrierendes Beispiel f.r OOP zu liefern, werden diese Konzepte auf das Design eines Steuerungssystems f.r die Luftfahrt angewandt.
Hinweis
Obwohl C++ eine OOP-Sprache ist, unterst.tzt sie auch solche Techniken, die nicht objektorientiert sind. Da sich die Syntaxen von C++ und Java sehr $hnlich sind, werden die Beispiele f.r nicht objektorientierten Code in C++ dargestellt.
Datenstrukturen In fast allen Programmiersprachen werden Daten in Variablen gespeichert, die einem bestimmten Datentyp entsprechen. Der Datentyp integer enth$lt zum Beispiel ganze Zahlen, der Datentyp character individuelle alphanummerische Zeichen und der Datentyp string enth$lt Gruppen von alphanummerischen Zeichen. Einige Sprachen erlauben es dem Programmierer, eigene Datentypen zu erstellen, indem einige der eben aufgez$hlten Datentypen zusammengefasst werden. In C++ sind diese Verbunddatentypen so genannte Strukturen (structures), in Pascal sind das entsprechend record types. Hier ein Beispiel einer Struktur in C++, die einen Teil der Flugstrecke eines Flugzeuges darstellt: struct Flight { int altitude; int heading; int speed; 196
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eine Einfhrung in die OOP
float latitude; float longitude; } Die Struktur Flight ist ein neuer Datentyp, der aus in C/C++ enthaltenen Datentypen zusammengesetzt ist, in diesem Fall Integer- und Gleitkommazahlen. Die Komponenten einer Struktur (die Integer- und Gleitkommazahlen dieses Beispiels) sind so genannte Mitglieder (members). Die Struktur Flight kGnnte auch Mitglieder f.r das Ziel des Fluges, die Art des Flugzeugs und andere Informationen enthalten, aber die angegebenen Mitglieder sind f.r diese Beispiele ausreichend. Die Struktur selbst speichert keine Informationen. Sie stellt lediglich ein Muster f.r das Erstellen neuer Flugdaten dar. Um eine neue Flight-Variable mit dem Namen incomingFlight zu deklarieren, w.rden Sie folgenden Code verwenden: struct Flight incomingFlight; Sie greifen auf die Mitglieder von incomingFlight zu, indem Sie den Namen der Flight-Variablen gefolgt von einem Punkt (.) und dem Namen des Mitglieds angeben: IncomingFlight.altitude = 3000; if (incomingFlight.heading < 180) {. . .} In Visual Basic oder Pascal w.rden Sie $hnlichen Code verwenden, um die FlightStruktur zu erstellen und auf die Variablen der Mitglieder zuzugreifen. In nicht-OOP (strukturspezifisches Programmieren wird hier als strukturorientierter Code bezeichnet) ist der Code, der auf die Flight-Variablen zugreift, von den Datentypen abgetrennt und speziell darauf ausgerichtet. In C++ kGnnte zum Beispiel eine Routine, die eine Wende des Flugzeugs repr$sentiert, wie folgt deklariert werden: void turnFlight (Flight &aFlight, int angle) { aFlight.heading = (aFlight.heading + angle) % 360; // make sure angle is in the range 0-359 degrees if (aFlight.heading < 0) aFlight.heading = aFlight.heading + 360; } Die Routine turnFlight erwartet, dass die .bergebenen Variablen jeweils vom Datentyp Flight und integer sind. Einen Kurswechsel des Flugzeugs um 90 Grad nach rechts kGnnten Sie zum Beispiel mit folgendem Code erreichen: turnFlight (incomingFlight, 90); Sie kGnnten $hnliche Routinen schreiben, um die FlughGhe des Flugzeugs zu verringern und dies auf einem Bildschirm auszugeben. Die folgende Abbildung zeigt eine schematische Darstellung der Datenstruktur und des Codes.
197
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
Abb. 8.1 Eine schematische Darstellung der Datenstruktur Flight und des Codes, der dieser Datenstruktur entspricht
Der n$chste Schritt besteht darin, kommerzielle Fl.ge zu modellieren. Erstellen Sie dazu eine neue Struktur mit dem Namen CommercialFlight, die alles das enth$lt, was auch in der Struktur Flight gegeben ist. Zus$tzlich sollen die Flugnummer und die Anzahl der Passagiere mit ber.cksichtigt werden. Listing 8.1 Die Datenstruktur CommercialFlight
struct CommercialFlight { // extra members in CommercialFlight int flightNumber; int passengers; // members in Flight int altitude; int heading; int speed; float latitude; float longitude; } Es gilt auch hier, dass Sie eine Variable CommercialFlight mit dem Namen incomingCommercialFlight erzeugen, indem Sie folgendes Kommando eingeben: struct CommercialFlight incomingCommercialFlight; Allerdings werden Routinen, die f.r allgemeine Fl.ge nicht mit den Variablen aus CommercialFlight zusammenarbeiten, nicht ber.cksichtigt, da keine Beziehung zwischen den Flight- und CommercialFlight-Variablen besteht. Der Compiler wird Ihnen zum Beispiel nicht gestatten, die Routine turnFlight mit einer Variable des Typs CommercialFlight aufzurufen. Aus diesem Grunde ist der folgende Funktionsaufruf nicht zul$ssig: turnFlight (incomingCommercialFlight, 90); Die folgende Abbildung zeigt eine schematische Darstellung des Datentyps CommercialFlight und seiner Funktionen.
198
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eine Einfhrung in die OOP
Abb. 8.2 Die Datenstrukturen Flight und CommercialFlight und die zugeordneten Routinen
Obwohl Sie mit einigen Tricks das Problem der Datentypen umgehen kGnnen, verbessert dies nicht die Lesbarkeit Ihres Codes, sondern erhGht die Komplexit$t und verringert die Zuverl$ssigkeit. Die einzig sichere Alternative besteht darin, eine neue Routine f.r kommerzielle Fl.ge zu erstellen, genannt turnCommercialFlight: void turnCommercialFlight (CommercialFlight &aFlight, int angle) { aFlight.heading = (aFlight.heading + angle) % 360; // sicherstellen, dass angle zwischen 0 und 359 // Grad liegt if (aFlight.heading < 0) aFlight.heading = aFlight.heading + 360; } Diese Art des Codeduplizierens stellt ein Wartungsproblem dar. Wenn Snderungen an zehn verschiedenen Strukturen notwendig werden, m.ssen zehn verschiedene Routinen angepasst werden. Dies ist nicht nur eine sehr aufwendige Arbeit, sondern es stellt außerdem eine zus$tzliche Fehlerquelle dar. Die Wartung ist nur eins von vielen Problemen, die mit nicht-OOP-Strukturen verbunden sind. Traditionelle Strukturen und die entsprechend zugeordneten Routinen kGnnen sich sehr schnell vermischen. Dies f.hrt dazu, dass ein außenstehender Programmierer die Routinen, die in einem anderen Programm weiterverwendet werden sollen, nur sehr schwer auffinden wird. Der Effekt ist der, dass die Programmierer dazu gezwungen sind, jedes einzelne Detail des urspr.nglichen Codes zu analysieren und diese Details dann von den Details der Implementierung zu trennen, bevor sie diese Routinen als Teil eines neuen Programms benutzen kGnnen. Um dies zu vermeiden, m.ssen Programmierer sehr diszipliniert arbeiten, um die Schnittstellen der Strukturen und deren Routinen – mit anderen Worten diejenigen Teile, die in zuk.nftigen Programmen weiterverwendet werden sollen – von den Details der Implementierung getrennt zu halten. 199
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
Das strukturorientierte Programmieren weist außerdem ein paar dem Prinzip innewohnende Sicherheitsm$ngel auf. In den vorangegangenen Beispielen wurden Routinen entwickelt, die den Kurs eines Flugzeugs um jeden beliebigen Winkel $nderten. Die Routinen haben sichergestellt, dass der Winkel immer zwischen (jeweils inklusive) 0 und 359 Grad lag. Bei Strukturen gibt es jedoch keinen Mechanismus, der verhindert, dass ein Programmierer, der zum Beispiel mit dem Design des Programms nicht vertraut ist, die turnFlight-Routine umgeht, indem er folgenden Code eingibt: // Rechtsdrehung um 90 Grad incomingFlight.heading = incomingFlight.heading + 90; Auch wenn der oben gezeigte Code grunds$tzlich korrekt ist, kann er zu Flugrichtungen f.hren, die grGßer sind als 359 Grad. Dies wiederum kann dazu f.hren, dass bestimmte andere Teile des Programms nicht mehr funktionieren, die darauf aufbauen, dass alle Winkel zwischen 0 und 359 Grad liegen. Dieser fehlende Schutz der Daten f.hrt dazu, dass der Quellcode sehr zerbrechlich wird. Von dieser Stelle an wird statt C++ Java-Quellcode verwendet. Beachten Sie aber, dass dieser in vielen F$llen sehr $hnlich aussehen kann. Hinweis
Von Strukturen zu Klassen: Kapselung In OOP sind die Routinen einer Struktur und die Struktur selbst in einem einzelnen Gebilde, genannt Klasse (class), kombiniert oder auch eingekapselt. Hier der Quellcode f.r die Flight-Struktur in Java: Listing 8.2 Der Flight-Quellcode
200
class Flight { int altitude; int heading; int speed; float latitude; float longitude; // aendere die Flugrichtung um "angle" Grad void turnFlight (int angle) { heading = (heading + angle) % 360; // stelle sicher, dass "angle" zwischen 0 und 359 // Grad liegt if (heading < 0) heading = heading + 360; } // gib Informationen ueber den Flug aus void printFlight() { System.out.println (altitude + "/" + heading + "/" + speed); } }
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eine Einfhrung in die OOP
Die Routine turnFlight ist nun eine so genannte member function der Klasse. Das bedeutet, dass die Routine Teil der Struktur selbst ist. Sie werden feststellen, dass der Code f.r die Funktion tats$chlich etwas sauberer ist, da Sie nun auf die Flugrichtung (heading) nicht mehr als Teil einer Dummy-Variablen referenzieren m.ssen – die Variable aFlight ist komplett weggefallen. Die Routine enth$lt jetzt zus$tzlich eine Teilfunktion, printFlight, die einige Informationen .ber den Flug ausgibt. Teilfunktionen werden korrekt als Methoden bezeichnet, obwohl h$ufig andere Begriffe verwendet werden. Hinweis Genauso wie bei der Definition der Struktur ist diese Definition der Klasse ein Muster oder eine Vorlage f.r Variablen, die mit dem Datentyp der Klasse Flight erzeugt werden. Variablen, die dem Datentyp der Klasse Flight angehGren, werden FlightObjekte genannt (daher der Name objektorientierte Programmierung). Ein Objekt ist eine Speichervariable, die basierend auf einer Klasse erstellt wird. Objekte werden als Instanz einer Klasse bezeichnet. Dies mag sich im ersten Augenblick verwirrend anhGren, wenn Sie noch nie zuvor mit Objekten gearbeitet haben, aber wenn Sie sich ansehen, wie die Beispielklasse Flight angewendet wird, wird ihnen die Verwendung von Objekten bestimmt klarer werden. Der n$chste Schritt besteht darin, ein Flight-Objekt zu erstellen, das auf der Klasse Flight basiert (dieser Prozess wird sehr h$ufig Instanzbildung genannt). Eine Objektvariable ist eine Referenz auf ein Objekt. Das erzeugen eines Objekts und das Erzeugen einer Referenz auf ein Objekt sind zwei unterschiedliche Schritte. Verwenden Sie folgenden Code, um eine Objektvariable zu erzeugen: Flight incomingFlight; Die Variable Flight kann einen von zwei mGglichen Werten annehmen: Null oder ein Objekt Flight. Der Vorgabewert des vorangegangenen incomingFlight ist Null. Dies ist einfach nur ein Name und bezieht sich bisher noch nicht auf ein Objekt. Um ein Objekt zu erzeugen, welches auf incomingFlight referenziert, verwenden Sie den neuen Operatoren: incomingFlight = new Flight(); Jetzt referenziert incomingFlight auf ein neues Objekt Flight und Sie kGnnen auf dessen Teilvariablen zugreifen: incomingFlight.altitude = 2500; if (incomingFlight.heading < 180) { ... } Methoden werden analog aufgerufen: incomingFlight.turnFlight (90); Um zu verstehen, wie dies funktioniert, stellen Sie sich vor, dass incomingFlight auf ein Objekt zeigt, das versteht, wie es sich selbst drehen kann. Stellen Sie sich weiterhin vor, dass Sie an dieses Objekt eine Nachricht senden, in der Sie das Objekt auffordern, sich um 90 Grad nach rechts zu drehen. Tats$chlich interagieren die 201
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
Objekte in OOP-Systemen, indem sie einander Mitteilungen zusenden. Das objektorientierte $quivalent der Abbildung 8.1 sieht nun wie in der folgenden Abbildung gezeigt aus:
Abb. 8.3 Die Datenstruktur Flight und die assoziierten Routinen als gekapselte Klasse
Die Kapselung gestattet es Ihnen außerdem, Daten sozusagen zu verstecken (so genanntes Data Hiding). Dies ist ein Weg, mit dem verhindert werden kann, dass direkt auf die Variablen in einem Objekt zugegriffen werden kann. Somit kGnnen andere Objekte dazu gezwungen werden, Methoden zu verwenden, um Daten von Teilvariablen auszulesen oder zu ver$ndern, statt direkt auf diese zuzugreifen. Und das ist eine der Hauptst$rken der Kapselung: Die Implementierung wird von der Schnittstelle getrennt. Sie m.ssen die Implementierung nicht kennen, um die Klasse verwenden zu kGnnen. Sie kGnnen die Klasse Flight so modifizieren, dass die Teilvariable heading versteckt wird. Verwenden Sie dazu das Schl.sselwort private: Listing 8.3 Die Klasse Flight mit versteckter Teilvariable heading
class Flight { int altitude; private int heading; int speed; float latitude; float longitude; void turnFlight (int angle) { heading = (heading + angle) % 360; // stelle sicher, dass "angle" zwischen 0 und 359 // Grad liegt. if (heading < 0) heading = heading + 360; } void setHeading (int angle) { heading = angle % 360; // stelle sicher, dass "angle" zwischen 0 und 359 // Grad liegt.
202
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eine Einfhrung in die OOP
if (heading < 0) heading = heading + 360; } int getHeading() { return heading; } void printFlight() { System.out.println (altitude + "/" + heading + "/" + speed); } } Da die Variable heading als privat gekennzeichnet ist und so f.r Code außerhalb der Klasse unsichtbar bleibt, kGnnen Sie auf diese Variable nicht mehr direkt von außen zugreifen. Zwei weitere Funktionen werden benGtigt, um auf die private Variable heading zugreifen zu kGnnen: setHeading, um die Flugrichtung in der Variablen heading ablegen zu kGnnen, und getHeading, um die aktuelle Flugrichtung aus der Variablen heading auslesen zu kGnnen. Grunds$tzlich ist es vorteilhaft, so viele Variablen wie mGglich zu verstecken. Dies trennt die Implementierung der Klasse von ihrer Schnittstelle und verringert so die Gefahr, dass ein anderer Programmierer Ihren Code zerstGrt, indem er die Sicherheitsabfragen in Ihren Methoden umgeht.
Vererbung von Klassen Das Verwenden von Klassen statt Strukturen beseitigt das Problem der Codeduplizierung. Erinnern Sie sich daran, dass Sie f.r erweiterte Strukturen wie zum Beispiel CommercialFlight eine zus$tzliche Kopie f.r jede Funktion erzeugen mussten, die mit der urspr.nglichen Struktur (Flight) arbeitet. Mit Klassen kGnnen Sie sowohl Daten als auch Methoden vererben, wenn Sie neue Klassen erzeugen: class CommercialFlight extends Flight { // extra members in CommercialFlight int flightNumber; int passengers; } Die Klasse CommercialFlight, eine Unterklasse von Flight, verinnerlicht automatisch alle Daten und Methoden der Klasse Flight. Sie kGnnen deshalb schreiben: // Erzeuge die Objektvariable und bilde eine Instanz CommercialFlight incomingCommercialFlight; incomingCommercialFlight = new CommercialFlight(); // Nun greife auf die members und Methoden zu incomingCommercialFlight.altitude = 2500; 203
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
incomingCommercialFlight.setHeading (45); incomingCommercialFlight.flightNumber = 101; incomingCommercialFlight.passengers = 24; Wie Sie sehen kGnnen, macht die Vererbung das Programmieren sehr viel einfacher. Zus$tzlich ist der Code sehr viel besser zu warten, da er sich zur Ver$nderung der Flugrichtung (heading) sowohl von Flight als auch von CommercialFlight an einer Stelle befindet, n$mlich in der Definition der Ursprungs- oder Basisklasse (base class). Die folgende Abbildung zeigt eine schematische Darstellung der Beziehungen von Klassen und Unterklassen.
Abb. 8.4 Die Klasse CommercialFlight verinnerlicht die Teilvariablen und Funktionen der Klasse Flight und f1gt ihr dann eigene Teilvariablen hinzu
H$ufig ist es w.nschenswert, dass die Unterklasse eine oder mehrere Methoden der vererbenden Klasse .berschreibt. Um mit dem vorangegangenen Beispiel fortzufahren, mGchten Sie zum Beispiel, dass ein kommerzieller Flug auf eine besondere Art ausgegeben wird, indem zus$tzlich zu den .brigen Informationen die Flugnummer dargestellt wird. Sie kGnnen die Routine printFlight der Klasse Flight leicht .berschreiben, indem Sie sie in der Klasse CommercialFlight erneut implementieren: Listing 8.4 Reimplementation der Klasse CommercialFlight
class Flight { int altitude; private int heading; int speed; float latitude; float longitude; void turnFlight (int angle) { heading = (heading + angle) % 360; // stelle sicher, dass "angle" zwischen 0 und 359
204
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eine Einfhrung in die OOP
// Grad liegt. if (heading < 0) heading = heading + 360; } void setHeading (int angle) { heading = angle % 360; // stelle sicher, dass "angle" zwischen 0 und 359 // Grad liegt. if (heading < 0) heading = heading + 360; } int getHeading() { return heading; } // gebe die Flughoehe, -richtung und Geschwindig// keit aus void printFlight() { System.out.println (altitude + " ft / " + heading + " degrees / " + speed + " knots"); } } class CommercialFlight extends Flight { // extra members in CommercialFlight int flightNumber; int passengers; // reimplementiere die Routine printFlight um die // vorherige Definition zu ueberschreiben void printFlight() { System.out.print ("Flight " + flightNumber + " "); super.printFlight(); } } Beachten Sie, dass die neue Methode printFlightsuper.printFlight() aufruft. Das Schl.sselwort super bezieht sich auf die Jberklasse (superclass) von CommercialFlight und so ruft super.printFlight() die urspr.ngliche Funktion printFlight auf, die in der Flight-Klasse definiert wurde. Sie werden das Schl.sselwort super sehr h$ufig dort finden, wo es verwendet wird, um Methoden zu .berschreiben, da die .berschreibende Funktion h$ufig zus$tzliche Verarbeitungen hinzuf.gt – sie macht alles, was die vererbende Klasse getan hat und mehr.
205
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
Wenn Sie die printFlight-Funktion eines Flight-Objekts aufrufen: incomingFlight.printFlight(); werden Sie eine Ausgabe $hnlich dieser erhalten: 2500 ft / 270 degrees / 240 knots Wenn Sie die printFlight-Routine eines kommerziellen Fluges aufrufen: incomingCommercialFlight.printFlight(); kGnnte die Ausgabe wie folgt aussehen: Flight 101 3000 ft / 185 degrees / 350 knots Die folgende Abbildung zeigt, wie die Klasse CommercialFlight die Funktion printFlight reimplementiert.
Abb. 8.5 Die Klasse CommercialFlight ererbt Teilvariablen und Funktionen von Flight, f1gt seine eigenen Teilvariablen hinzu und 1berschreibt die Funktion printFlight
Manchmal ist es von Vorteil, wenn Sie die Vererbung auch dann verwenden, wenn die Ursprungsklasse so generisch ist, dass sie nicht implementiert werden kann. Sie kGnnen dies erreichen, indem Sie das Konzept der abstrakten Klasse anwenden.
Abstrakte Klassen
206
Dieser Abschnitt beschreibt, wie die Einrichtungen einer Flugleitstelle mit in das Beispiel aufgenommen werden, indem Klassen f.r den Flugkontrollturm (flight control tower) f.r das Landen, Starten und Fliegen der Flugzeuge und den Bodenkon-
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eine Einfhrung in die OOP
trollturm (ground control tower) f.r das ManGvrieren der Flugzeuge am Boden hinzugef.gt werden. Zun$chst wird eine vererbende Klasse mit dem Namen ControlFacility erzeugt. Dann werden FlightControlTower und GroundControlTower als Unterklassen erzeugt. Anschließend wird eine Methode mit dem Namen getClearance erzeugt, die aufgerufen wird, um festzustellen, ob eine Einrichtung einem Flug die Genehmigung f.r Start, Landung, ManGvrieren und so weiter erteilt. Sie kGnnen allerdings kein generisches Objekt ControlFacility erzeugen, da Sie getClearance nicht implementieren kGnnen, ohne zu wissen, ob Sie Raum in der Luft oder am Boden kontrollieren. Auf der anderen Seite wollen Sie jedoch sicherstellen, dass jede Unterklasse von ControlFacility die getClearance-Methode implementiert. Die LGsung des Problems ergibt sich aus der F$higkeit von Java, abstrakte Klassen erzeugen zu kGnnen. Der Code f.r die Klasse ControlFacility macht deutlich, wie abstrakte Klassen funktionieren: abstract class ControlFacility { abstract boolean getClearance (FlightAction request); } In diesem kurzen St.ck Code deklarieren Sie Ihre neue Klasse und die Funktion der getClearance-Methode, die einen booleschen Wert (wahr oder falsch) zur.ckgibt. Die Funktion wird ein Objekt einer Klasse namens FlightAction akzeptieren (die hier nicht definiert wird, sie ist lediglich Teil der Illustration). Die Funktion ist jedoch abstrakt definiert, das heißt, sie hat keine Implementierung. Jede Klasse, die solche abstrakten Funktionen aufweist, wird abstrakte Klasse genannt, und es kGnnen keine Objekte dieser Klasse erzeugt werden. Die Unterklassen FlightControlTower und GroundControlTower m.ssen die Funktion getClearance implementieren, damit Sie Objekte erzeugen kGnnen, die solche Einrichtungen repr$sentieren: class FlightControlTower extends ControlFacility { boolean getClearance (FlightAction request) { // Implementierung der Funktion getClearance fuer // Flugkontrolltuerme . . . } } class GroundControlTower extends ControlFacility { boolean getClearance (FlightAction request) { // Implementierung der Funktion getClearance fuer // Bodenkontrolltuerme . . . } }
207
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
Da diese beiden Unterklassen – FlightControlTower und GroundControlTower – die abstrakte Funktion implementieren, die in der vererbenden Klasse definiert wurde, sind sie keine abstrakten Klassen und es kGnnen Instanzen gebildet werden. Zus$tzlich zur Formalisierung der Schnittstelle bieten abstrakte Klassen dem Programmierer noch andere Vorteile, die im n$chsten Abschnitt diskutiert werden.
Polymorphe Funktionen Polymorphe Funktionen sind Funktionen, f.r die es ohne Belang ist, welche Variablentypen ihnen .bergeben werden. Das print-Kommando in BASIC und das writeln-Kommando in Pascal sind Beispiele f.r polymorphe Routinen, da jede Variable an diese Routinen .bergeben werden kann und sie sich trotzdem immer angemessen verhalten. Das .bliche BASIC benGtigt keine printinteger- oder printstring-Anweisung, da print geschickt genug ist, alle Datentypen zu verarbeiten. Diese F$higkeit der print-Anweisung ist jedoch speziell in den BASIC-Interpreter hineinprogrammiert worden und erstreckt sich nicht auf vom Anwender definierbare Strukturen. BASIC bietet dem Anwender außerdem keine MGglichkeiten an, eigene polymorphe Routinen zu erstellen. Java ermGglicht dem Programmierer auf drei Wegen, ihren Code mit polymorphen Funktionen zu vereinfachen: Vererbung erlaubt es Unterklassen, Methoden automatisch von der vererbenden Klasse zu .bernehmen. Zus$tzlich wird jede Methode, die eine Klasse als Argument akzeptiert, auch deren Unterklassen als Argument akzeptieren. Jberladen ist das Implementieren identisch benannter Methoden, die unterschiedliche Argumente innerhalb derselben Klasse verwenden. Schnittstellenbildung ist das Implementieren identischer Argumente in unterschiedlichen Klassen. Diese drei F$lle werden der Reihe nach n$her betrachtet.
Vererbung Die Vererbung ist die einfachste Form polymorphen Verhaltens, das Sie außerdem bereits kennen gelernt haben. In dem Luftverkehrskontrollbeispiel kGnnen Sie jedes Flight-Objekt oder jedes Objekt einer Unterklasse von Flight anweisen, nach links zu drehen, indem Sie die Methode turnFlight(-90) aufrufen. Dies bedeutet, dass Sie statt einer Vielzahl von Funktionsnamen wie turnFlight, turnCommercialFlight oder turnMilitaryFlight (f.r eine milit$rische Flight-Klasse) turnFlight konsistent verwenden kGnnen: incomingFlight.turnFlight (-90); incomingCommercialFlight.turnFlight (-90); incomingMilitaryFlight.turnFlight (-90);
208
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eine Einfhrung in die OOP
Ein weiterer Vorteil ist, dass Sie sehr einfach Code schreiben kGnnen, der mit allen Flight-Klassen und -Unterklassen zusammenarbeitet. Sie kGnnen zum Beispiel eine neue Klasse f.r Flugh$fen mit dem Namen Airport erzeugen, die eine Methode mit dem Namen aircraftInbound besitzt, mit welcher der Liste der sich n$hernden Fl.ge ein Flug hinzugef.gt wird: class Airport { String airportName; Flight inboundFlights[], outboundFlights[]; void aircraftInbound (Flight aFlight) { //implementation of aircraftInbound function . . . } } Ein Airport-Objekt wird nun jedes Flight-Objekt akzeptieren, das eine Unterklasse von Flight ist. Sie kGnnten zum Beispiel schreiben: // Erzeuge die Objektvariable cityAirport und bilde // eine Instanz Airport cityAirport; cityAirport = new Airport(); // Benenne den Flughafen und f:ge "Flight" zu der // aircraftInbound Flugliste hinzu. cityAirport.airportName = "City National Airport"; cityAirport.aircraftInbound (incomingFlight); cityAirport.aircraftInbound (incomingCommercialFlight); cityAirport.aircraftInbound (incomingMilitaryFlight); Polymorphismus durch Vererbung gestattet es Ihnen außerdem, alle Vorteile der abstrakten Klassen zu nutzen. Vererbung von einer generischen abstrakten Klasse gestattet es Ihnen, Klassen in Gruppen zusammenzufassen, die sich gemeinsame Funktionen teilen, sich aber in der Form der Implementierung unterscheiden. Nachdem Sie im vorangegangenen Abschnitt die abstrakte Klasse ControlFacility erzeugt haben, kGnnen Sie jetzt den Code schreiben, der sich auf ControlFacility-Objekte bezieht und mit allen Unterklassen von ControlFacility zusammenarbeitet, obwohl ControlFacility-Objekte selbst nie erzeugt werden kGnnen.
4berladen Ein weiterer Weg, polymorphe Funktionen hinzuzuf.gen, ist als Jberladen von Funktionen bekannt. In Java, C++ und anderen Sprachen, die das
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
mit unterschiedlichen Priorit$ten entsprechend des Fluges hinzuf.gen. Durch
Hinweis
// Funktion aircraftInbound akzeptiert Flight // Objekte void aircraftInbound (Flight aFlight) { // Implementation der Funktion aircraftInbound // fuer generische Fl:ge . . . } // Funktion aircraftInbound akzeptiert // CommercialFlight Objekte void aircraftInbound (CommercialFlight aFlight) { // Implementation der Funktion aircraftInbound // fuer kommerzielle Fluege. . . . } // Funktion aircraftInbound akzeptiert // MilitaryFlight Objekte void aircraftInbound (MilitaryFlight aFlight) { // Implementation der Funktion aircraftInbound // fuer militaerische Fluege. . . . } } Beachten Sie, dass die Signierung von Methoden f.r polymorphe Methoden nicht den R.ckgabetyp einschließt. Sie kGnnen keine zwei Methoden mit demselben Namen und derselben Parameterliste haben, die unterschiedliche R.ckgabetypen liefern. Sie rufen die Funktion genau so auf wie vorher, unabh$ngig davon, welche Art von Flight-Objekt Sie an die Funktion .bergeben: Airport cityAirport; cityAirport = new Airport(); cityAirport.aircraftInbound (incomingFlight);
210
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eine Einfhrung in die OOP
cityAirport.aircraftInbound (incomingCommercialFlight ); cityAirport.aircraftInbound (incomingMilitaryFlight); Das
Interfacing – Schnittstellenbildung Angenommen, Sie wollen eine Zusammenfassung erstellen, die alle Flugh$fen mit den sich jeweils n$hernden und abfliegenden Fl.gen darstellt. Sie kGnnen dies erreichen, indem Sie eine Funktion printOnReport f.r die Klassen Airport und Flight erstellen: class Airport { String airportName; Flight inboundFlights[], outboundFlights[]; // Die Funktion printOnReport gibt einen Airport // Eintrag in der Zusammenfassung aus void printOnReport() { System.out.println ("Airport: " + airportName); } } class Flight { int altitude; private int heading; int speed; float latitude; float longitude; // Gibt die Flughoehe, -richtung und Geschwindig// keit aus. void printOnReport() { System.out.println("Flight: " + altitude + " ft / " + heading + " degrees / " + speed + " knots"); } } Sie kGnnen diese neuen Funktionen wie folgt aufrufen: incomingFlight.printOnReport(); cityAirport.printOnReport(); Vereinfacht ausgedr.ckt haben diese Klassen jetzt eine gemeinsame Schnittstelle, soweit es die Ausgabe der Zusammenfassung angeht. Java ermGglicht es Ihnen, diese Schnittstellen zu formalisieren, damit Sie sicherstellen kGnnen, dass die Klasse
211
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
diese Funktionen unterst.tzt (es kann mehr als eine solche Funktion geben), die eine derartige Schnittstelle bilden. Indem Sie solche formalisierten Schnittstellen verwenden, kGnnen Sie Funktionen erstellen, die ein Argument jeder Klasse akzeptieren, die eine bestimmte Schnittstelle implementieren. Definieren Sie jetzt eine formale Schnittstelle f.r das Ausgabebeispiel der Zusammenfassung. Das Schl.sselwort interface wird in Java genau so verwendet, wie das Schl.sselwort class: interface ReportPrintable { void printOnReport(); } Beachten Sie, dass ReportPrintable keine Klasse ist, und deshalb keine Instanz gebildet werden kann. Grunds$tzlich sind die in einer Schnittstelle deklarierten Methoden abstrakt. Um dem Compiler mitzuteilen, dass die Klassen Airport und Flight die Schnittstelle ReportPrintable implementieren, m.ssen Sie in die Deklarationen der Klassen eine entsprechende Anweisung implementieren: class Airport implements ReportPrintable { . . . } class Flight implements ReportPrintable { . . . } Als N$chstes kGnnen Sie eine Klasse ReportGenerator erstellen, die eine Zusammenfassung von jedem Objekt erzeugt, das die Schnittstelle ReportPrintable implementiert: class ReportGenerator { void addToReport (ReportPrintable anObject) { anObject.printOnReport(); } } Die Funktion addToReport von ReportGenerator wird jede Klasse akzeptieren, die die Schnittstelle ReportPrintable implementiert. Wie Sie sehen kGnnen vereinfacht der Polymorphismus das Schreiben von Code, besonders dann, wenn Sie komplexe Situationen der wirklichen Welt modellieren. Die Programmierer m.ssen sich weniger Namen von Funktionen merken und der Quellcode wird sehr viel besser lesbar.
212
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Konstruktor und Finalizer
Konstruktor und Finalizer Sie kGnnen zwei spezielle Arten von Methoden definieren: Konstruktor – Methoden, die eine neue Instanz der Klasse zur.ckgeben. Wenn Sie keine Konstruktoren erstellen, kGnnen Sie den vordefinierten Konstruktor verwenden, um Instanzen der Klasse zu erzeugen. Finalizer – Funktionen, die unmittelbar vor der garbage collection eines Objekts aufgerufen werden. Der folgende Abschnitt beschreibt diese speziellen Methoden und die garbage collection.
Konstruktor Wenn Sie sich an die Klasse Airport erinnern, wird Ihnen einfallen, dass Sie den Code wie folgt erstellt und initialisiert haben: Airport cityAirport; cityAirport = new Airport(); cityAirport.airportName = "City National Airport"; Nach der ersten Zeile wurde cityAirport als Objektvariable definiert. Nach der zweiten Zeile ist ein Objekt erzeugt worden und cityAirport bezieht sich auf dieses Objekt. Die dritte Zeile initialisiert den Namen des Airport-Objekts. Die Funktion Airport() ist der vordefinierte Konstruktor (default constructor) der Klasse Airport. Der vordefinierte Konstruktor wird von der Oberklasse von Airport, Object, ererbt und vom Java-Compiler automatisch dieser Klasse hinzugef.gt. Der Konstruktor von Object fordert Platz f.r alle Teilvariablen an, die als eine der in Java eingebauten Datentypen deklariert wurden. In diesem Fall wird f.r keine der Teilvariablen von Airport Speicher angefordert, da weder die Variable String noch die Datentypen Flight in Java enthalten sind. So ist zum Beispiel die Teilobjektvariable airportName Null, bis Sie Platz f.r den entsprechenden String anfordern oder ihr ein Objekt zuweisen. Um den Ablauf der Erstellung eines Objekts zu vereinfachen und sich vor nicht initialisierten Objektvariablen zu sch.tzen, kGnnen Sie sich Ihren eigenen Konstruktor f.r die Klasse Airport erstellen: class Airport { String airportName; Flight[] inboundFlights, outboundFlights; // ein neuer Konstruktor, der keine Argumente // annimmt Airport() { super(); airportName = "Unknown";
Listing 8.6 Konstruktor f1r die Klasse Airport
213
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
} . . . } Ein Konstruktor wird definiert wie jede andere Methode auch, muss aber denselben Namen wie die Klasse haben und kann keinen R.ckgabedatentyp haben. Im folgenden Beispiel ruft der Konstruktor die Funktion super() auf, die eine Referenz auf den Konstruktor in der .bergeordneten Klasse Object ist. Das Schreiben des super()Aufrufs ist optional, da der Compiler implizit den Konstruktor der .bergeordneten Klasse aufruft, wenn Sie dies nicht tun. Sie kGnnen den neuen Konstruktor genau so aufrufen, wie Sie dies bereits vorher getan haben: Airport cityAirport = new Airport(); cityAirport.airportName = "City National Airport"; Nachdem Sie den neuen Konstruktor aufgerufen haben, wird der Vorgabewert von cityAirport.airportName „Unknown“ sein. Da Sie jedoch den Namen des Flughafens st$ndig wechseln werden, kGnnen Sie einen Schritt einsparen, wenn Sie einen weiteren Konstruktor erstellen, der das Airport-Objekt erzeugt und den Namen entsprechend der Auswahl des Aufrufers festlegt: class Airport { String airportName; Flight inboundFlights[], outboundFlights[]; // ein neuer Konstruktor, der keine Argumente // annimmt. Airport() { super(); airportName = "Unknown"; } // ein neuer Konstruktor, der den neuen Namen des // Flughafens als Argument annimmt. Airport (String newName) { super(); airportName = newName; } . . . } Dies ist ein Beispiel f.r das
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Konstruktor und Finalizer
Konstruktoren kGnnen andere Konstruktoren aufrufen. Sie kGnnen jetzt den Airport()-Konstruktor so .berarbeiten, dass dieser den Airport(String newName)-Konstruktor aufruft, indem Sie das Schl.sselwort this verwenden: // ein neuer Konstruktor, der keine Argumente // annimmt Airport() { this ("Unknown"); } Das Schl.sselwort this gefolgt von Klammern (und gegebenenfalls Argumenten), bezieht sich auf einen Konstruktor f.r diese Klasse. In diesem Fall weiß der Compiler, dass Sie sich auf den Konstruktor Airport(String newName) beziehen, da dies der einzige Konstruktor ist, der einen String als Argument annimmt. Da die verschiedenen Konstruktoren einer Klasse normalerweise allgemeine Aufgaben ausf.hren, werden Sie die MGglichkeit, andere Konstruktoren aufzurufen, sehr n.tzlich finden.
Garbage Collection Was passiert, wenn ein Objekt vom System nicht mehr benGtigt wird? Der folgende Code und die folgenden beiden Abbildungen stellen dar, was in diesem Fall mit „nicht mehr benGtigt“ gemeint ist: cityAirport = new Airport ("City National Airport"); cityAirport = new Airport ("Potter's Field");
Abb. 8.6 Die Objektvariable cityAirport referenziert urspr1nglich das Objekt, das „City National Airport“ darstellt
Abb. 8.7 Die Objektvariable verweist dann auf ein neues Objekt, das „Potter’s Field“ repr7sentiert. Da es keine Referenzen zum ersten Objekt gibt, wird es durch die Garbage Collection automatisch verworfen.
215
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
Zwei Objekte werden in diesem Code erzeugt, aber es gibt hier nur eine Objektvariable. Nach dem ersten Statement zeigt cityAirport auf das Objekt, das „City National Airport“ repr$sentiert. Nach dem zweiten Statement zeigt cityAirport auf das Objekt, das „Potter’s Field“ repr$sentiert, und auf das erste Objekt zeigt nichts mehr. Wie Sie bereits vermutet haben, ist dieses Objekt f.r das System verloren. Java eignet sich automatisch den freigewordenen Speicher an, der von einem Objekt belegt wurde, das nun von keiner Variablen mehr referenziert wird. Dieser Prozess wird Garbage Collection genannt. Stellen Sie sich die folgenden Zuweisungen vor und ziehen Sie die n$chsten beiden Abbildungen hinzu: localAirport = new Airport ("City National Airport"); cityAirport = localAirport; cityAirport = new Airport ("Potter's Field"); In dieser Instanz f$llt das Objekt, das „City National Airport“ repr$sentiert nicht dem Garbage Collector zum Opfer, da localAirport noch immer auf dieses Objekt referenziert.
Abb. 8.8 Die Objektvariablen localAirport und cityAirport referenzieren urspr1nglich auf das neue Objekt, das „City National Airport“ repr7sentiert
Abb. 8.9 Als N7chstes wird die Objektvariable cityAirport verwendet, um auf das neue Objekt zu referenzieren, das „Potter’s Field“ repr7sentiert. Da es noch immer eine Referenz zum ersten Objekt gibt (localAirport), bleibt dieses urspr1ngliche Objekt erhalten.
216
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Konstruktor und Finalizer
Finalizer Es gibt einige Situationen, in denen sich eine Klasse vor der Garbage Collection sozusagen selbst bereinigen muss. Dies wird durch Implementierung einer Finalizer-Methode erreicht. Finalizer werden aufgerufen, unmittelbar bevor eine Klasse durch die Garbage Collection aus dem System entfernt wird. Sie werden typischerweise verwendet, um offene Dateien oder Verbindungen zu schließen oder um sicherzustellen, dass damit zusammenh$ngende Tasks beendet werden, bevor das Objekt vergessen wird. Um einen Finalizer zu erstellen, definieren Sie einfach eine Methode, die finalize genannt wird: protected void finalize() throws Throwable { System.out.println ("This object is about to be garbage collected"); super.finalize(); } Das Schl.sselwort protected (welches sicherstellt, dass diese Funktion nur von einer begrenzten Anzahl Klassen aufgerufen werden kann) und die Anweisung throws Throwable (die beschreibt, was geschehen soll, wenn das Unvorhergesehene eintritt) werden in Kapitel 9: Fehler vermeiden durch effektives Exception Handling erkl$rt. Der Aufruf super.finalize() stellt sicher, dass die Methode finalize() Ihrer
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Arbeiten mit Java-Objekten
jekten freigegeben worden sind. Die einzig verl$ssliche LGsung in solchen F$llen besteht darin, sicherzustellen, dass Sie die close-Funktion explizit aufrufen, bevor irgendwelche Objekte verworfen werden.
Ausblick In Kapitel 9: Fehler vermeiden durch effektives Exception Handling wird die Behandlung von Ausnahmesituationen (exceptions) vorgestellt. Dabei werden grundlegende Klassen zur Behandlung von Fehlern und Ausnahmen vorgestellt. Außerdem wird das Erstellen eigener Ausnahmeklassen vorgestellt.
218
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Fehler vermeiden durch effektives Exception Handling
4bersicht ber das Exception Handling
220
Konstrukte des Exception Handlings
229
Erstellen eigener Klassen von Exceptions
236
Beispiel: Altersbedingte Exceptions
237
Ausblick
241
9
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Fehler vermeiden durch effektives Exception Handling
Aus „Mastering Java 2“ von John Zukowski
Da Programme zunehmend komplizierter werden, wird es immer schwieriger, sie stabil zu erhalten. Traditionelle Programmiersprachen, wie zum Beispiel C, verlassen sich sehr stark darauf, dass if-Statements abnorme Zust$nde entdecken, gotoStatements zu den Fehlerbehandlungsroutinen verzweigen und kryptische R.ckgabewerte diese abnormen Zust$nde an die aufrufenden Methoden zur.ckliefern. Dadurch wird der Programmablauf unter einem Netz aus Statements zur Ausnahmeerkennung und Ausnahmebehandlung begraben oder aber die Stabilit$t wird dem Segen der sauberen Programmierung geopfert. Java bietet einen eleganten Weg an, um Programme zu erstellen, die sowohl robust als auch klar sind. Dazu verwendet Java einen Mechanismus zur Ausnahmebehandlung (Exception Handling), der dem von C++ $hnelt. In diesem Kapitel werden Sie lernen, diesen saubereren Mechanismus zu verwenden und so Fehler und un.bliche Zust$nde zu behandeln.
8bersicht ,ber das Exception Handling Eine Ausnahme (Exception) ist ein abnormer Zustand, der den normalen Programmablauf stGrt oder unterbricht. Es gibt viele F$lle, in denen solche abnormen Zust$nde w$hrend der Programmausf.hrung auftreten kGnnen, wie die folgenden Beispiele zeigen: Die Datei, die Sie zu Gffnen versuchen, existiert nicht. Die .class-Datei, die Sie laden wollen, existiert nicht oder hat ein falsches Format. Das andere Ende der Netzwerkverbindung, die Sie benutzen, existiert nicht. Die Netzwerkverbindung, die Sie benutzen, wurde aus unerkl$rlichen Gr.nden unterbrochen. Ein Operand befindet sich nicht innerhalb des zuvor f.r Operationen oder Methoden definierten zul$ssigen Bereichs. So kann zum Beispiel der Index eines Arrays nicht grGßer sein, als das Array selbst, und eine Division durch Null ist immer unzul$ssig. Wenn diese abnormen Zust$nde nicht verhindert oder zumindest korrekt behandelt werden, wird entweder das Programm abrupt beendet oder falsche Ergebnisse werden vom Programm verarbeitet, was normalerweise weitere Fehler zur Folge hat. Stellen Sie sich ein Programm vor, das versucht, Daten aus einer nicht existierenden Datei auszulesen und Berechnungen auf der Grundlage dieser Eingabewerte ausf.hrt!
220
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
4bersicht ber das Exception Handling
Das grundlegende Modell Java folgt beim Exception Handling der grunds$tzlichen Syntax von C++ . Zun$chst werden Sie versuchen, einen Anweisungsblock auszuf.hren. Tritt ein abnormer Zustand ein, wird irgendetwas eine Exception auswerfen, die Sie mit einem so genannten Handler auffangen kGnnen. Zus$tzlich kann es sein, dass Sie abschließend einen Anweisungsblock immer ausf.hren wollen, unabh$ngig davon, ob eine Exception aufgetreten ist oder behandelt wurde. Eine Exception auszuwerfen, ist eine freundlichere Methode, als das Programm zu terminieren, da dies dem Programmierer die MGglichkeit gibt, diese Exception abzufangen und sie zu behandeln. Das folgende Programmbeispiel l$sst das Programm f.r 10 Sekunden (10.000 Millisekunden) durch den Aufruf der sleep()-Klassenmethode einschlafen, die in der Klasse Thread des java.lang-Pakets definiert ist. Wird sleep unterbrochen, bevor die Zeit abgelaufen ist, wird eine Nachricht ausgegeben, und die Ausf.hrung wird mit dem Statement fortgesetzt, das diesem versuchsweisen Abfangen folgt: PrintWriter out = new PrintWriter(System.out, true); try { Thread.sleep(10000); } catch (InterruptedException e) { out.println("Sleeping interrupted."); } // erreicht diese Stelle nach Ausf:hrung des tryBlocks oder die Exception behandelt wurde Das n$chste Programm, das den Inhalt einer Datei in eine andere kopiert, demonstriert das Exception Handling in einer etwas praxisn$heren Form. Das Programm .bernimmt zun$chst Dateinamen aus den Argumenten der Kommandozeile. Dann werden die Dateien geGffnet und Daten werden in 512 Byte großen BlGcken kopiert. Die Anzahl der kopierten Bytes wird mitverfolgt und ausgegeben, sobald die Operation abgeschlossen ist. Das hierzu notwendige Programmfragment sieht so aus: int byteCount = 0; byte buffer[] = new byte[512]; String inputFile = null; String outputFile = null; PrintWriter out = new PrintWriter(System.out, true); FileInputStream fin; FileOutputStream fout; inputFile = args[0]; outputFile = args[1]; fin = new FileInputStream(inputFile); fout = new FileOutputStream(outputFile); int bytesInOneRead; while ((bytesInOneRead = fin.read(buffer)) != -1) { fout.write(buffer, 0, bytesInOneRead);
Listing 9.1 Exception Handling
221
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Fehler vermeiden durch effektives Exception Handling
byteCount += bytesInOneRead; } out.println(byteCount + " written"); Die Klassen FileInputStream (f.r den Dateieingabestrom) und FileOutputStream (f.r den Dateiausgabestrom) werden in dem java.io-Paket definiert. Deren Konstruktoren erlauben es Ihnen, eine Datei .ber deren Namen zu Gffnen, und ihre Methoden lassen Sie Daten aus diesen Dateien lesen und auch Daten in ein einzelnes Byte oder ein Byte-Array schreiben. Wie soll sich das Programm jedoch verhalten, wenn keine Dateinamen angegeben wurden? Oder wenn die angegebene Eingabedatei nicht existiert? In Java sind diese F$lle im System definierte Exceptions, die vom System ausgeworfen werden, wenn sie auftreten. Der Versuch, auf den Index eines Arrays zuzugreifen, der grGßer oder gleich der GrGße des Arrays selbst ist, wird eine ArrayIndexOutOfBoundsException auslGsen. Der Konstruktor der Klasse FileInputStream wird eine FileNotFoundException auslGsen, wenn die Datei nicht gefunden werden kann. Der Konstruktor der Methoden read() und write() der Klasse FileOutputStream wird eine IOException auslGsen, wenn ein Ein-/Ausgabefehler vorliegt. Dar.ber hinaus kGnnen die Handler von Exceptions gemeinsam gefunden werden. Eine catch-Anweisung ist f.r jeden Exception Handler erforderlich, um die abnorme Situation zu identifizieren, auf die er sich bezieht. Drei Handler m.ssen dem oben gezeigten Programm hinzugef.gt werden, damit die eben genannten Exceptions ber.cksichtigt werden: Ein Handler, der die korrekte Verwendung des Programms ausgibt, wenn die Eingabe- und/oder Ausgabedatei nicht angegeben werden. Der n$chste Handler wird den Anwender dar.ber informieren, dass die Eingabedatei nicht existiert. Ein weiterer Handler wird Fehlermeldungen ausgeben, wenn andere, die Ein-/Ausgabe betreffende Exceptions auftreten. Das Programm, mit dem die Anzahl der kopierten Bytes ausgegeben wird, ist in die finally-Anweisung verschoben worden, damit diese in jedem Fall ausgef.hrt wird, selbst dann, wenn der normale Programmablauf gestGrt wird. Hier das vollst$ndige Programm: Listing 9.2 Exception Handling mit finally-Anweisung
222
import java.io.*; public class MyCopy { public static void main (String args[]) { int byteCount = 0; byte buffer[] = new byte[512]; String inputFile = null; String outputFile = null; PrintWriter out = new PrintWriter(System.out,
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
4bersicht ber das Exception Handling
true); FileInputStream fin; FileOutputStream fout; try { inputFile = args[0]; outputFile = args[1]; fin = new FileInputStream(inputFile); fout = new FileOutputStream(outputFile); int bytesInOneRead; while ((bytesInOneRead = fin.read(buffer)) != -1) { fout.write(buffer, 0, bytesInOneRead); byteCount += bytesInOneRead; } } catch (ArrayIndexOutOfBoundsException e) { out.println("Usage: java MyCopy [inputFile] [outputFile] "); } catch (FileNotFoundException e) { out.println("Cannot open input file: " + inputFile); } catch (IOException e) { out.println("I/O exception occurs! "); } finally { if (byteCount > 0) out.println(byteCount + " bytes written"); } } } Hier einige Beispielmeldungen, die von dem eben gezeigten Programm unter verschiedenen Umst$nden ausgegeben werden: C:\MasteringJava\Ch07>javac MyCopy.java C:\MasteringJava\Ch07>java MyCopy Usage: java MyCopy [inputFile] [outputFile] C:\MasteringJava\Ch07>java MyCopy MyCopy.java temp.java 1095 bytes written C:\MasteringJava\Ch07>java MyCopy NoSuchFile.java temp.java Cannot open input file: NoSuchFile.java
Warum sollten Sie das Exception Handling verwenden? Sie sollten das Exception Handling aus verschiedenen Gr.nden verwenden. Einer der Gr.nde ist, dass der Code zur Fehlerverarbeitung vom normalen Programmablauf getrennt ist. Das erhGht die Lesbarkeit des Codes und die Wartungsf$higkeit des Programms insgesamt. 223
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Fehler vermeiden durch effektives Exception Handling
Stellen Sie sich vor, wie Sie das Beispiel aus dem vorangegangenen Abschnitt in C schreiben w.rden, wenn kein Exception Handling verf.gbar w$re. Sie w.rden ein if-Statement nach jeder Ein-/Ausgabeoperation benGtigen, um sicherzustellen, dass diese erfolgreich ausgef.hrt wurden. Sie w.rden außerdem ein if-Statement benGtigen, um zu .berpr.fen, ob die richtige Anzahl Dateinamen angegeben wurde. Um diese abnormen Zust$nde zu verarbeiten m.ssten Sie entweder an den entsprechenden Stellen weiteren Code einf.gen oder aber mit goto-Statements zu den entsprechenden Routinen verzweigen, die diese .blichen Fehler behandeln. F.gen Sie ein paar Ein-/Ausgabe-Routinen hinzu, und selbst Ihnen als Autor des Programms wird es schwer fallen, zu erkennen, was das Programm urspr.nglich machen sollte. In Java m.ssen Sie nicht extra .berpr.fen, ob Ausnahmesituationen auftreten. Das Hinzuf.gen weiterer Handles macht es notwendig, dass Sie weitere catch-Anweisungen hinzuf.gen, aber der eigentliche Programmablauf wird davon nicht beeintr$chtigt. Ein weiterer Grund f.r das Verwenden des Exception Handlings ist der, dass Sie leicht feststellen kGnnen, wo eine bestimmte Ausnahme behandelt wird. Exceptions werden w$hrend der Ausf.hrung im Call Stack nach oben propagiert, zuerst innerhalb der umschließenden BlGcke, welche die Ausf.hrung versuchen, und dann zur.ck zu den aufrufenden Methoden, bis ein Exception Handler diese abf$ngt. So kann zum Beispiel das vorangegangene Beispiel zu einer Methode umgeschrieben werden, die Ein- und Ausgabedatei als Argumente verwendet. Die Verwendung der neuen Methode w$re dann entsprechend: int copyFile(String inputFile, String outputFile) Der Aufrufer dieser Methode kGnnte einen abnormen Zustand selbst behandeln wollen. So kGnnte zum Beispiel eine Applikation mit einer grafischen Benutzeroberfl$che (GUI) einen Dialog darstellen wollen, der vom Anwender die Angabe eines anderen Dateinamens verlangt, wenn zum Beispiel die Eingabedatei nicht existiert. In diesem Fall wird der Exception Handler der Ein-/Ausgabeoperation aus der Methode entfernt und eine throws-Anweisung der Deklaration der Methode hinzugef.gt. Der Aufrufer der Methode kann nun seine eigenen Routinen zur Fehlerbehandlung anwenden. Hier die angepasste Definition der Methode: int copyFile(String inputFile, String outputFile) throwsIOException { int bytesInOneRead, byteCount = 0; byte buffer[] = new byte[512]; FileInputStream fin = new FileInputStream(inputFile); FileOutputStream fout= new FileOutputStream(outputFile) while ((bytesInOneRead = fin.read(buffer)) != -1) { fout.write(buffer, 0, bytesInOneRead); byteCount += bytesInOneRead; } return byteCount; }
224
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
4bersicht ber das Exception Handling
Hier ein Codefragment, mit dem diese Methode aufgerufen wird und abnorme Zust$nde behandelt werden: int byteCount = 0; String inputFile = null; String outputFile = null; PrintWriter out = new PrintWriter(System.out, true); try { inputFile = args[0]; outputFile = args[1]; byteCount = copyFile(inputFile, outputFile); } catch (ArrayIndexOutOfBoundsException e) { out.println("Usage: java MyCopy [inputFile] [outputFile]"); } catch (FileNotFoundException e) { out.println("Cannot open input file: " + inputFile); } catch (IOException e) { out.println("I/O exception occurs!"); } finally { if (byteCount > 0) out.println(byteCount + " bytes written"); } Exceptions sind Objekte mit hierarchischen Beziehungen und Abh$ngigkeiten. Sie kGnnen einen einzelnen Exception Handler erstellen, der eine Reihe von Exceptions einer Klasse abf$ngt und diese dann durch individuelle Unterklassen behandeln l$sst. Das MyCopy-Beispiel demonstriert eine andere MGglichkeit. Die zweite catchAnweisung behandelt die FileNotFoundException und die n$chste jede andere IOException. Die FileNotFoundException ist eine Unterklasse der IOException, so dass Sie jeweils auf Exceptions der Oberklasse und der untergeordneten Klassen .berpr.fen kGnnen. Hier das vollst$ndige Programm: import java.io.*; public class MyCopy { public static void main (String args[]) { int byteCount = 0; String inputFile = null; String outputFile = null; PrintWriter out = new PrintWriter(System.out,true); try { inputFile = args[0]; outputFile = args[1]; byteCount = copyFile(inputFile, outputFile); } catch (ArrayIndexOutOfBoundsException e) { out.println("Usage: java MyCopy [inputFile]" + "[outputFile]"); } catch (FileNotFoundException e) { out.println("Cannot open input file: " +
Listing 9.3 Exception Handling mit Pr1fung der Oberklasse und der untergeordneten Klassen
225
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Fehler vermeiden durch effektives Exception Handling
inputFile); } catch (IOException e) { out.println("I/O exception occurs!"); } finally { if (byteCount > 0) out.println(byteCount + " bytes written"); } } static int copyFile(String inputFile, String outputFile) throws IOException { int bytesInOneRead, byteCount = 0; byte buffer[] = new byte[512]; FileInputStream fin = new FileInputStream(inputFile); FileOutputStream fout= new FileOutputStream(outputFile); while ((bytesInOneRead = fin.read(buffer)) != -1) { fout.write(buffer, 0, bytesInOneRead); byteCount += bytesInOneRead; } return byteCount; } }
Die Hierarchie der Exception-Klassen Wie fast alles in Java sind auch Exceptions entweder Instanzen von Objekten oder Klassen. Exceptions bilden Klassen aus ihrer eigenen Klassenhierarchie. Der Ursprung aller Klassen von Exceptions ist die Klasse Throwable, die eine unmittelbare Unterklasse der Klasse Object ist. In der Klasse Throwable werden Methoden definiert, mit denen die zu einer bestimmten Exception gehGrenden Fehlermeldungen aufgefunden werden und der Verlauf durch den Stack (die so genannte Stack Trace) ausgegeben werden kann, mit dem wiederum dargestellt wird, wo die Exception auftritt (mehr dazu in Kapitel 10: Schneller und kompatibler durch Java Packages). Die Klasse Throwable hat zwei unmittelbare Unterklassen: die Klasse Error und die Klasse Exception. Die Unterklassen der Klasse Exception sind an der Namensendung Exception erkennbar. Die Unterklassen der Klasse Error haben dagegen die Namensendung Error (zus$tzlich gibt es noch die Klasse ThreadDeath, die ebenfalls eine Unterklasse von Error ist). Die Unterklassen von Error dienen grunds$tzlich der Signalisierung abnormer Systemzust$nde. So signalisiert zum Beispiel ein OutOfMemoryError, dass die Java-VM den gesamten zur Verf.gung stehenden Speicher verbraucht hat und die Garbage Collection keinen weiteren Speicher freimachen kann. Ein StackOverflowErrror signalisiert, dass der Stack im Interpreter .bergelaufen ist. Solche Fehler sind generell nicht behebbar und sollten deshalb auch nicht behandelt werden. 226
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
4bersicht ber das Exception Handling
Die Unterklassen der Klasse Exception gelten dagegen grunds$tzlich als behebbar. So signalisiert zum Beispiel eine EOFException, dass die Datei, die Sie geGffnet haben, keine weiteren Daten mehr enth$lt, die ausgelesen werden kGnnten. Eine FileNotFoundException signalisiert, dass eine Datei, die Sie Gffnen wollen, nicht im Dateisystem existiert. Sie kGnnen sich dazu entscheiden, solche Exceptions zu behandeln, indem Sie diejenigen Statements, deren außergewGhnliche Zust$nde behandelt werden sollen, mit einem try-catch-Block umschließen. Die folgende Abbildung zeigt die hierarchischen Beziehungen zwischen einigen der etwas h$ufiger vorkommenden Errors und Exceptions. Es gibt sehr viel mehr, aber diese sind im Augenblick nicht so sehr von Bedeutung und werden betrachtet, wenn Sie in den Beispielen verwendet werden. Zus$tzliche Informationen .ber Exceptions kGnnen Sie der Onlinedokumentation unter http://www.java.sun.com/ products/jdk/1.3/docs/api/index.html entnehmen.
Abb. 9.1 Hierarchie der 1blichen Exceptions
Die folgenden Beispiele durchlaufen vier pathologische F$lle, in denen das System vier Typen von RuntimeExceptions auswirft: ArithmeticException – F.r außergewGhnliche arithmetische Zust$nde, wie zum Beispiel die Division durch Null. NullPointerException – F.r das Zugreifen auf ein Feld oder das Aktivieren einer Methode eines nicht existierenden Objekts (null object). ArrayIndexOutOfBoundsException – F.r das Zugreifen auf das Element eines Arrays .ber einen Indexwert, der entweder kleiner als Null oder grGßer oder gleich der GrGße des Arrays ist. StringIndexOutOfBoundsException – F.r das Zugreifen auf ein Zeichen in einem String oder StringBuffer mit einem Index, der kleiner als Null oder grGßer oder gleich der L$nge des Strings ist. 227
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Fehler vermeiden durch effektives Exception Handling
Hier ein Programm, mit dem dies ausprobiert werden kann: Listing 9.4 RuntimeExceptions
import java.io.*; public class ExceptionTest { public static void main(String args[]) { PrintWriter out = new PrintWriter(System.out, true); for (int i = 0; i < 4; i++) { int k; try { switch (i) { case 0: // Division durch Null int zero = 0; k = 911 / zero; break; case 1: // null pointer int b[] = null; k = b[0]; break; case 2: // array index out of bound int c[] = new int[2]; k = c[9]; break; case 3: // string index out of bound char ch = "abc".charAt(99); break; } } catch (Exception e) { out.println("\nTest case #" + i + "\n"); out.println(e); } } } } Die Ausgabe des vorangegangenen Programms kGnnen Sie hier sehen: C:\MasteringJava\Ch07>javac ExceptionTest.java C:\MasteringJava\Ch07>java ExceptionTest Test case #0 java.lang.ArithmeticException: / by zero Test case #1 java.lang.NullPointerException Test case #2 java.lang.ArrayIndexOutOfBoundsException: 9 Test case #3 java.lang.StringIndexOutOfBoundsException: String index outof range: 99
228
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Konstrukte des Exception Handlings
Konstrukte des Exception Handlings Die grunds$tzliche Form eines Konstrukts zum Exception Handling (das try-Statement) ist: try { normalProgramBody } catch (ExceptionClass1 exceptionVariable1) { exceptionHandlerProgramBody1 } catch (ExceptionClass2 exceptionVariable2) { exceptionHandlerProgramBody2 . . . } finally { exitProgramBody } Fr.he Versionen des JDK (vor 1.0.2) benGtigten keine geschweiften Klammern im Rumpf eines try-catch-finally-Konstrukts, wenn der Rumpf des Programms aus einem einzigen Statement bestand. Seit JDK 1.0.2 sind geschweifte Klammern jedoch immer notwendig.
Tipp
Das Schl.sselwort try wird verwendet, um einen Block zu spezifizieren, dessen Exceptions durch die umgebenden catch-Anweisungen behandelt werden sollen. Es kann jede beliebige Anzahl solcher catch-Anweisungen geben. Tritt eine Exception auf, wird der Rumpf des ersten Exception Handlers ausgef.hrt, dessen Klassentyp der Exception dieselbe Klasse oder die Oberklasse der ausgeworfenen Exception ist. Da das Abgleichen der Exceptions sequenziell ausgef.hrt wird, kann es vorkommen, dass ein bestimmter Exception Handler niemals ausgef.hrt wird, wenn dessen catch-Anweisungen zum Beispiel hinter der catch-Anweisung seiner Oberklasse aufgef.hrt sind. So musste zum Beispiel in einem schon vorher dargestellten Beispiel der Handler f.r die FileNotFoundException vor dem Handler der IOException erscheinen, da letztere die unmittelbare Oberklasse der FileNotFoundException ist. Der Compiler .berpr.ft, ob alle Exception Handler erreichbar sind. Wenn Sie zum Beispiel die Reihenfolge der FileNotFoundException und der IOException vertauschen, dann wird der Compiler folgende Fehlermeldung ausgeben: C:\MasteringJava\Ch07>javac MyCopy.java MyCopy.java:16: catch not reached. } catch (FileNotFoundException e) { ^ 1 error Der Programmblock exit, der dem Schl.sselwort finally folgt, wird ausgef.hrt, bevor die Kontrolle .ber das Programm nach außerhalb des programmierenden Konstrukts transferiert wurde. Dies kann mGglicherweise passieren, wenn die Ausf.hrung des Programmrumpfs oder des Exception Handlers beendet wird, ein den Pro229
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Fehler vermeiden durch effektives Exception Handling
grammfluss unterbrechendes Statement (ein break-, continue- oder return-Statement) erreicht wird oder eine Exception ausgeworfen wird, f.r die es keinen Handler innerhalb des Konstrukts gibt, der diese Exception behandeln kGnnte. Die catch-Anweisung ist optional, genauso wie die finally-Anweisung. Allerdings muss mindestens entweder eine catch- oder eine finally-Anweisung in einem trycatch-finally-Konstrukt existieren. Der Rumpf von exit program ist sehr n.tzlich, wenn Ressourcen freigemacht werden sollen, wie zum Beispiel Datei-Handler, die innerhalb des normalen Programmrumpfes zugewiesen sind. Das folgende Beispiel demonstriert die Effekte der break- und continue-Statements in einer finally-Anweisung. Innerhalb einer verschachtelten Schleife werden benannte und unbenannte break- und continue-Statements ausgef.hrt und der Ablauf mitverfolgt: Listing 9.5 break- und continueStatements
import java.io.*; public class FinallyTest { public static void main(String args[]) { PrintWriter out = new PrintWriter(System.out, true); outerLoop: for (int i = 0; i < 3; i++) for (int j = 0; j < 3; j++) try { out.println("try before if: i=" + i + ", j=" + j); if ((i == 0) && (j == 1)) continue; else if ((i == 0) && (j == 2)) continue outerLoop; else if ((i == 1) && (j == 0)) break; else if ((i == 2) && (j == 1)) break outerLoop; out.println("try after if: i=" + i + ", j=" + j); } finally { out.println("finally: i=" + i + ", j=" + j + "\n"); } } } Die Ausgabe des Programms wird als N$chstes abgebildet. Sie kGnnen sehen, dass die finally-Anweisung immer ausgef.hrt wird, sobald mit der Ausf.hrung des tryBlocks begonnen wurde:
230
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Konstrukte des Exception Handlings
C:\MasteringJava\Ch07>javac FinallyTest.java C:\MasteringJava\Ch07>java FinallyTest try before if: i=0, j=0 try after if: i=0, j=0 finally: i=0, j=0 try before if: i=0, j=1 finally: i=0, j=1 try before if: i=0, j=2 finally: i=0, j=2 try before if: i=1, j=0 finally: i=1, j=0 try before if: i=2, j=0 try after if: i=2, j=0 finally: i=2, j=0 try before if: i=2, j=1 finally: i=2, j=1 Wird die Exception nicht im aktuellen try-catch-finally-Konstrukt abgefangen, wird es durch den Stack des Programms hindurch nach oben propagiert. Derselbe Prozess zur Erkennung der Exception wird f.r alle umschließenden try-catch-finallyKonstrukte wiederholt, vom innersten zum $ußersten Konstrukt, bis schließlich ein zutreffender Exception Handler gefunden wird. Wird innerhalb der aktuellen Methode kein zutreffender Exception Handler gefunden, wird derselbe Prozess auf das try-catch-finally-Konstrukt der aufrufenden Methode angewandt, wieder vom innersten zum $ußersten Konstrukt, bis eine
231
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Fehler vermeiden durch effektives Exception Handling
gen, weil dies eine Unterklasse der IndexOutOfBoundsException ist, wird dann aber zur.ckgeworfen und durch das $ußere try-catch-finally-Konstrukt abgefangen. Die letzte Exception, StringIndexOutOfBoundsException, wird innerhalb des inneren trycatch-finally-Konstrukts abgefangen, weil dies ebenfalls eine Unterklasse der IndexOutOfBoundsException ist, und wird dann zur.ckgeworfen. Da es aber keinen entsprechenden Handler im $ußeren try-catch-finally-Konstrukt gibt, wird sie dort nicht abgefangen. Deshalb wird die Exception an die aufrufende Methode weitergegeben und dort abgefangen, weil die Exception eine Unterklasse der RuntimeException ist. Hier das Beispiel: Listing 9.6 Drei Exceptions in der for-Schleife der Methode method()
232
import java.io.*; public class NestedException { static PrintWriter out = new PrintWriter( System.out, true); public static void method() { for (int i = 0; i < 3; i++) { int k; try { out.println("\nOuter try block; Test Case #" + i); try { out.println("Inner try block"); switch (i) { case 0: // Division durch Null int zero = 0; k = 911 / zero; break; case 1: // Index des Arrays ausserhalb // des erlaubten Bereichs int c[] = new int[2]; k = c[9]; break; case 2: // Index des Strings ausserhalb // des erlaubten Bereichs char ch = "abc".charAt(99); break; } } catch (ArithmeticException e) { out.println("Inner ArithmeticException>" + e); } catch (IndexOutOfBoundsException e) { out.println("Inner IndexOutOfBoundsException>" + e); throw e; } finally { out.println("Inner finally block"); } } catch (ArrayIndexOutOfBoundsException e) {
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Konstrukte des Exception Handlings
out.println("Outer ArrayIndexOutOfBound>" + e); } finally { out.println("Outer finally block"); } } } public static void main(String args[]) { try { method(); } catch (RuntimeException e) { out.println("main() RuntimeException>" + e); } finally { out.println("\nmain() finally block"); } } } Hier die Ausgabe des Programms: C:\MasteringJava\Ch07>javac NestedException.java C:\MasteringJava\Ch07>java NestedException Outer try block; Test Case #0 Inner try block Inner ArithmeticException>java.lang.ArithmeticExcepti on: /by zero Inner finally block Outer finally block Outer try block; Test Case #1 Inner try block Inner IndexOutOfBoundsException>java.lang.ArrayIndexOutOfBo undsException: Inner finally block Outer ArrayIndexOutOfBound>java.lang.ArrayIndexOutOfBoundsE xception: Outer finally block Outer try block; Test Case #2 Inner try block Inner IndexOutOfBoundsException>java.lang.StringIndexOutOfB oundsException: String index out of range: 99 Inner finally block Outer finally block main() RuntimeException>java.lang.StringIndexOut OfBoundsException: String index out of range: 99 main() finally block 233
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Fehler vermeiden durch effektives Exception Handling
Verfgbare Methoden der Exceptions Alle Errors und Exceptions sind Unterklassen der Klasse Throwable und kGnnen deshalb die darin definierten Methoden verwenden. getMessage() – Erhalt der Fehlermeldung, die der Exception oder dem Error zugeordnet ist. printStackTrace() – Ausgabe der Stack Trace, die zeigt, wo die Exception auftritt. toString() – Ausgabe des Namens der Exception zusammen mit der von getMessage() zur.ckgelieferten Mitteilung. getLocalizedMessage() – Ausgabe einer lokalisierten Beschreibung der Exception oder des Errors, der von einer Unterklasse verwendet werden kann, um diese Methode zu .berschreiben oder eine locale-spezifische Mitteilung auszugeben. fillInStackTrace() – Zur Aufzeichnung von Informationen .ber die gegenw$rtigen Zust$nde der Stack Frames f.r den aktuellen Thread innerhalb der Exception oder des Errors. printStackTrace(PrintStream s) – Zur Ausgabe einer Stack Trace entsprechend des Ausgabesystems, die zeigt, wo die Exception auftritt. Die meisten Klassen von Exceptions haben zwei Konstruktoren: einen mit einem String-Argument, um die Fehlermeldung zu setzen, die dann sp$ter durch die Methode getMessage() ausgelesen wird; die andere ohne Argumente. Im zweiten Fall wird die Methode getMessage() null zur.ckgeben. Die gleiche Fehlermeldung wird in die R.ckgabe der Methode toString() eingebettet oder ein Teil der Ausgabe der Stack Trace der Methode printStackTrace() sein. Eine Ausgabe beziehungsweise R.ckgabe der Methoden wird hier als Beispiel gezeigt: *** example of return from getMessage() *** / by zero *** example of return from toString() *** java.lang.ArithmeticException: / by zero *** example of output by printStackTrace() *** java.lang.ArithmeticException: / by zero at NoHandler.inner(NoHandler.java:6) at NoHandler.outer(NoHandler.java:11) at NoHandler.main(NoHandler.java:16)
234
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Konstrukte des Exception Handlings
Das throw-Statement Ein throw-Statement lGst das Auswerfen einer Exception aus. Es wird wie folgt verwendet: throw expression; wobei die Expression .ber eine Instanz der Klasse Throwable (oder einer der vielen Unterklassen davon) evaluiert werden muss. Die h$ufigste Verwendung besteht darin, ein neues Statement einzusetzen, das eine Instanz innerhalb der Expression erzeugt. So wird zum Beispiel das folgende Statement eine IOException mit der Fehlermeldung „kann Verzeichnis nicht finden“ ausgeben: throw new IOException("kann Verzeichnis nicht finden" );
Die throws-Anweisung Eine Methode, die in sich selbst eine Exception auswirft, muss diese Exception selber abfangen, wenn die Exception keine Unterklasse der Klasse Error oder RuntimeException ist. Wenn mehrere Exceptions in einer throws-Anweisung verwendet werden sollen, m.ssen Sie Kommas (,) verwenden, um diese abzutrennen. Das folgende Programmsegment deklariert eine Methode, die eine IOException und eine InterruptedException nach außen propagiert: int readModel(String filename) throws IOException,Int erruptedException Es gibt vier Gr.nde daf.r, dass Exceptions, die Unterklassen von Error oder RuntimeException sind, nicht in einer Methode deklariert oder behandelt werden m.ssen: Wenn Sie eine throws-Anweisung f.r jede Exception, die in einer Methode entstehen kGnnte, abfangen oder deklarieren m.ssen, wird das Programm sehr schwerf$llig und unhandlich. Es ist sehr schwierig, w$hrend des Kompilierens zu .berpr.fen, ob eine entsprechende Exception .berhaupt auftreten kann. So kann zum Beispiel prinzipiell jede Referenz auf ein Objekt eine NullPointerException auswerfen. Sicherzustellen, dass jedes referenzierte Objekt w$hrend der Laufzeit ein nicht-Null-Objekt ist, ist eine sehr aufwendige Arbeit f.r den Compiler, besonders dann, wenn das Objekt als Argument einer Methode .bergeben wird. Die meisten Fehler kGnnen ohne KontrollmGglichkeit des Programmierers auftreten. Vom Programmierer zu verlangen, solche Fehler abzufangen und zu behandeln, macht wenig Sinn. 235
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Fehler vermeiden durch effektives Exception Handling
Die meisten Exceptions, die w$hrend der Laufzeit auftreten, sind das Ergebnis eines Programmierfehlers. Korrekter Code wird solche Exceptions nicht erzeugen. Der Compiler verl$sst sich auf die Deklaration einer throws-Anweisung, mit der festgestellt wird, ob innerhalb eines Ausdrucks, einer Anweisung oder Methode eine Exception auftreten kann. Die Exceptions, die in einer Methode auftreten kGnnen, sind die Gesamtheit aller Exceptions, die in throws-Statements innerhalb der Methode enthalten sind, sowie alle Exceptions, die in throws-Anweisungen der Methoden enthalten sind, welche innerhalb der Methode aufgerufen werden kGnnten. Der Compiler gibt f.r jede Methode, die nicht alle (nicht-Error/nicht-Laufzeit-) Exceptions innerhalb ihrer throws-Anweisung deklariert, eine Fehlermeldung aus. Ein Beispiel f.r eine solche Fehlermeldung wird hier gezeigt: DontCompile.java:8: Exception java.io.FileNotFoundException must be caught, or it must be declared in the throws clause of this method. FileInputStream fin = new FileInputStream ("BasicException.java"); ^
Erstellen eigener Klassen von Exceptions Wenn Sie eine Methode erstellen, dann haben Sie zwei MGglichkeiten, der aufrufenden Methode abnorme Zust$nde mitzuteilen: Sie verwenden entweder einen vordefinierten Fehlercode als R.ckgabewert oder Sie werfen eine Exception aus. Wenn eine Exception ausgeworfen wird, stehen der aufrufenden Methode automatisch alle Mittel und Wege des Exception Handlings zur Verf.gung, um auf die abnormen Zust$nde reagieren zu kGnnen. Außerdem wird es dem Compiler ermGglicht, zu .berpr.fen, ob diese abnormen Zust$nde korrekt behandelt werden, da solche abnormen Zust$nde in der throws-Anweisung der Methode deklariert werden. Wenn Sie eine Exception auswerfen, kGnnen Sie eine Instanz einer Exception-Klasse erzeugen, die bereits innerhalb der Sprache definiert ist, oder Sie definieren eigene Exceptions. Es kann problematisch sein, eine vordefinierte Exception zu finden, die genau auf die von Ihnen definierte Situation passt. Indem Sie eine vordefinierte Exception verwenden, kann es passieren, dass Sie die Situation f.r den Exception Handler unnGtig erschweren. Der Grund daf.r ist der, dass der Exception Handler eventuell Ihre abnormen Situationen von solchen unterscheiden muss, f.r die diese Klasse urspr.nglich erstellt wurde, wenn beide in einer Methode vorkommen kGnnen. Die .bliche Praxis beim Erstellen von eigenen Klassen-Exceptions ist, eine Unterklasse der Klasse Exception zu erstellen. Dies stellt sicher, dass der Compiler .berpr.ft, ob damit korrekt umgegangen wird. Wenn Sie aber system- oder hardware236
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Beispiel: Altersbedingte Exceptions
orientierte Software erstellen, kann es sinnvoller sein, eine Unterklasse der Klassen Error oder RuntimeException zu erstellen. Sie sollten aber keine Unterklassen von Error oder RuntimeException erstellen, um keine throws-Anweisungen f.r Ihre Methoden entwickeln m.ssen, denn dies entkr$ftet den gesamten Sinn der Verwendung von Exceptions. Da Klassen von Exceptions Objektklassen sind, kGnnen Sie data members haben und in ihnen kGnnen Methoden definiert sein. So weist zum Beispiel die InterruptedIOException, die in java.io definiert wird, eine public instance-Variable BytesTransferred auf, in der die Anzahl der gelesenen oder geschriebenen Bytes abgelegt ist, bevor die Operation unterbrochen wird. Sie kGnnen sich dazu entschließen, eigene Klassen von Exceptions in der Hierarchie zu erzeugen, damit der Handler die MGglichkeit hat, die Oberklasse als Ganzes und die Unterklassen jeweils f.r sich oder beide Klassen gleichzeitig zu verwenden.
Beispiel: Altersbedingte Exceptions Das Beispiel, das in diesem Abschnitt vorgestellt wird, demonstriert, wie eine Hierarchie anwenderdefinierter Klassen von Exceptions f.r die Behandlung abnormer Zust$nde erstellt wird und wie ein Programm zu erstellen ist, das diese anwenderdefinierten Exceptions abnormer Zust$nde verwendet. Im ersten Teil des Beispiels werden Sie eine Hierarchie von Exception-Klassen erstellen, mit der Sie .ber altersbedingte Anomalien berichten kGnnen, wie dies in der folgenden Abbildung dargestellt wird:
Abb. 9.2 Die Hierarchie der Klasse AgeException
Die Wurzel dieser Hierarchie ist die Klasse AgeException. Sie hat ein data member, age, welches das Alter enth$lt, das die Exception auslGst. Diese hat wiederum zwei Unterklassen: OutOfAgeLimitException f.r diejenigen F$lle, in denen das angegebene Alter zu niedrig oder zu hoch ist, um eine bestimmte Aktion auszuf.hren, und IllegalAgeFormatException f.r solche F$lle, in denen das Alter außerhalb des zul$ssigen Bereichs liegt oder ein ung.ltiges Format hat. OutOfAgeLimitException hat ein data member, ageLimit, das die Beschr$nkungen enth$lt, gegen die verstoßen wurde. Das Programm zur Definition dieser Klassen ist unten aufgelistet. Speichern Sie diese Hierarchie von Exceptions in einer Datei mit dem Namen AgeException.java: class AgeException extends Exception { int age; AgeException(String message) { super(message); } AgeException() {
Listing 9.7 AgeException.java
237
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Fehler vermeiden durch effektives Exception Handling
super(); } } class OutOfAgeLimitException extends AgeException { int ageLimit; OutOfAgeLimitException(int ageLimit, String message ){ super(message); this.ageLimit = ageLimit; } OutOfAgeLimitException(String message) { super(message); } } class TooYoungException extends OutOfAgeLimitException { TooYoungException(int age,int ageLimit, String message) { super(ageLimit, "You are too young to " + message + "."); this.age = age; } TooYoungException() { super("too young"); } } class TooOldException extends OutOfAgeLimitException { TooOldException(int age, int ageLimit, String message) { super(ageLimit, "You are too old to " + message + "."); this.age = age; } TooOldException() { super("too old"); } }
238
class IllegalAgeFormatException extends AgeException { IllegalAgeFormatException(String message) { super(message); } IllegalAgeFormatException() {
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Beispiel: Altersbedingte Exceptions
super("Illegal age format"); } } class NegativeAgeException extends IllegalAgeFormatException { NegativeAgeException(String message) { super(message); } NegativeAgeException(int age) { super("Age must be nonnegative."); this.age = age; } } Wie bei allen Java-Klassen, sind die einzigen Klassen, die in eigenen Dateien gespeichert werden m.ssen, die so genannten public classes, also die allgemein verf.gbaren Klassen. Deshalb kGnnen alle diese Exception-Klassen in einer Datei gesammelt oder jeweils in eigenen Dateien gespeichert werden oder auch in derselben Datei gespeichert werden, wie das Programm AgeExceptionTest, das hier ebenfalls gezeigt wird.
Tipp
Der zweite Teil des Beispiels ist ein Programm, das die vorher erstellte Hierarchie der Exceptions benutzt. Das Programm wird in Schleifen verschiedene Altersgruppen testen und so ermitteln, ob eine Person mit dem angegebenen Alter eine Achterbahnfahrt mitmachen darf. Die Methode rideRollerCoasterAtAge() wird eine TooYoungException auswerfen, wenn das angegebene Alter zu niedrig ist, ein TooOldException, wenn das angegebene Alter zu hoch ist und eine NegativeAgeException, wenn das angegebene Alter negativ ist. Hier das Programm: import java.io.*;
Listing 9.8
public class AgeExceptionTest { static PrintWriter out = new PrintWriter( System.out, true); static void rideRollerCoasterAtAge(int age) throws NegativeAgeException, OutOfAgeLimitException { out.println("Trying to ride a roller coaster at age " + age + "..."); if (age < 0) throw new NegativeAgeException(age); else if (age < 5) throw new TooYoungException(age, 5, "ride a roller coaster");
AgeExceptionTest
239
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Fehler vermeiden durch effektives Exception Handling
else if (age > 45) throw new TooOldException(age, 45, "ride a roller coaster"); out.println("Riding the roller coaster...."); } public static void main(String args[]) { int ages[] = {-3, 2, 10, 35, 65}; for (int i = 0; i < ages.length; i++) try { rideRollerCoasterAtAge(ages[i]); out.println("Wow! What an experience!"); } catch (OutOfAgeLimitException e) { out.println(e.getMessage()); if (ages[i] < e.ageLimit) out.println((e.ageLimit - ages[i]) + " more years and you'll be able to try it."); else out.println((ages[i] - e.ageLimit) + " years ago riding it was like a " + " piece of cake."); } catch (NegativeAgeException e) { out.println(e.getMessage()); } finally { out.println(); } } } Die Ausgabe des Beispielprogramms ist hier dargestellt: C:\MasteringJava\Ch07>javac AgeException.java C:\MasteringJava\Ch07>javac AgeExceptionTest.java C:\MasteringJava\Ch07>java AgeExceptionTest Trying to ride a roller coaster at age -3... Age must be nonnegative. Trying to ride a roller coaster at age 2... You are too young to ride a roller coaster. 3 more years and you'll be able to try it. Trying to ride a roller coaster at age 10... Riding the roller coaster.... Wow! What an experience! Trying to ride a roller coaster at age 35... Riding the roller coaster.... Wow! What an experience! 240
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ausblick
Trying to ride a roller coaster at age 65... You are too old to ride a roller coaster. 20 years ago riding it was like a piece of cake.
Ausblick In Kapitel 10: Schneller und kompatibler durch Java Packages wird die am h$ufigsten verwendeten Java packages behandeln, inklusive derer, die Sprachen, Grafiken, Internetprogrammierung und Swing unterst.tzen.
241
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Java-Pakete und die Klassenhierarchie
244
java.lang – Hauptuntersttzung der Sprache
246
Das Paket java.util – ntzliche Sprachuntersttzung
253
Das Paket java.io – Dateien und Ein-/AusgabestrBme
259
Das Paket java.awt – das Herz der Hierarchie
264
Das Paket javax.swing
275
Das Paket java.net – Untersttzung fr Internet, Web und HTML
281
Ausblick
285
10
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Aus „Mastering Java 2“ von John Zukowski, ISBN 0-7821-2180-2, $49,99, 1280 Seiten
Dieses Kapitel f.hrt Java-Pakete ein und konzentriert sich dabei auf die sieben Pakete, die am h$ufigsten benGtigt werden. Bevor Sie jedoch mehr .ber einzelne Pakete erfahren, werden Sie sehen, wie diese Pakete in die Klassenhierarchie von Java passen.
Java-Pakete und die Klassenhierarchie Java ist von Anfang an objektorientiert. Wie alle objektorientierten Sprachen kommt auch Java mit einem Standardsatz unterst.tzender Klassen, wohingegen Hybriden, wie zum Beispiel C++, erst jetzt standardisierte Bibliotheken erhalten. Die Klassen von Java unterscheiden sich von bekannten Bibliotheken, wie sie prozeduralen Sprachen wie C oder Pascal beiliegen. Da diese unterst.tzenden Klassen das volle Potenzial der objektorientierten Sprachen aufdecken, .bertreffen sie einfache Bibliotheken. Das Vererben von Klassen ist bei weitem die bekannteste und auch leistungsf$higste verwendete F$higkeit (siehe auch Kapitel 1: Erstellen der ersten Java-Beispiele und Kapitel 8: Arbeiten mit Java-Objekten f.r weitere Informationen .ber Klassenvererbung). Die gesamte Java-Hierarchie kann aus zwei Blickwinkeln betrachtet werden: als eine objektorientierte Vererbungshierarchie und als eine Gruppe von Klassen in Paketen. Die Vererbungshierarchie gruppiert Klassen in Pakete, die sich gemeinsame Aspekte der Implementierung teilen (Code oder Variablen). Java-Pakete sammeln einfach nur Klassen auf einer sehr pragmatischen Grundlage: Klassen mit verwandter Funktionalit$t werden im selben Paket geb.ndelt, unabh$ngig davon, ob sie Code oder Daten gemeinsam haben oder nicht. Zus$tzlich zu den offensichtlichen Vorteilen der Strukturierung verwenden Pakete zus$tzlich eine Partitionierung im Namensraum. Das bedeutet, dass jede Klasse, die in einem Paket enthalten ist, einen eindeutigen Namen hat, der nicht mit anderen Namen, die an anderer Stelle definiert sind, kollidieren kann. So kGnnten zwei Firmen zum Beispiel problemlos Klassen mit dem gleichen Namen vertreiben. Eine Klasse mit Sortieralgorithmen der Firma Mango Macrosystems kGnnte beispielsweise mango.utilities.Bubble heißen, w$hrend ein vergleichbares Produkt der Firma Sun-So-Soft sosoft.utils.Bubble heißt. Die Klassennamen sind gleich, aber Java verwendet die Namen der Pakete und Unterpakete, um Klassen voneinander zu unterscheiden. Das strikte Schema der einfachen Vererbung der Sprache bestimmt den Weg, auf dem die Standardklassen von Java im Sinne der objektorientierten Vererbung miteinander in Beziehung stehen. Der daraus entstehende Vererbungsbaum ist deshalb ein reiner Baum und kein Graph, wie dies bei anderen objektorientierten mehrfach vererbenden Hierarchien der Fall ist. Mehrfachvererbung ist in den JavaKlassen auf die eine oder andere Art durch den m$chtigen Mechanismus der Schnittstellenbildung der Sprache realisiert.
244
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java-Pakete und die Klassenhierarchie
Mehrfache Vererbung gegen einfache Vererbung Mehrfache Vererbung ist ein Mechanismus, der es einer Kasse erm"glicht, von mehr als einer Oberklasse zu erben. Dies erzeugt den Netzeffekt, der aus dem Mischen von Charakteristika mehrerer Klassen eine neue Klasse entstehen lsst. Die Mehrfachvererbung wurde eingefhrt, um die beschrnkenden Effekte der Einfachvererbung zu umgehen. Angenommen, Sie htten zum Beispiel eine einfachvererbende Hierarchie, die sich in zwei fundamentale Unterbume aufteilt: „lebendig“ und „nicht bewegt“. Aus „lebendig“ verzweigt sich der Arm der unmittelbar folgenden Unterklassen Pflanze – Obstbaum – Banane. Der aus dem „nicht bewegt“ hervorgehende Zweig k"nnte auch einen sehr sinnvollen Zweig haben, der Lebensmittel enthlt. Aus offensichtlichen Grnden m"chten Sie die Banane auch in diesen Zweig mit einbinden. Einfachvererbung lsst dies nicht zu. Sie k"nnen aus einer Oberklasse ererben, nicht aus zwei oder mehr. Der hier gezeigte einfachvererbende Baum illustriert die Beschrnkungen der reinen Einfachvererbung.
Abb. 10.1 Schematische Darstellung des Prinzips der einfachen Vererbung
Da Sie .ber Pakete eine ganze Hierarchie sehr einfach handhaben kGnnen, werden diese nun Ihr Wegweiser sein, w$hrend Sie die Hierarchie von Java kennen lernen. Das Release 2 von Java hat ca. 60 java.*-Pakete. Dieses Kapitel betrachtet die sieben meistverwendeten Pakete: java.lang enth$lt die Hauptunterst.tzungsmodule der Sprache. Diese befassen sich mit Objekt-Wrappern, Strings, Multithreading und verwandten Bereichen.
245
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
java.util enth$lt die eher werkzeugartigen Unterst.tzungsklassen. Diese beinhalten Klassen zum Sammeln und Kalenderklassen, aber auch einige n.tzliche abstrakte Designs die durch die Schnittstellen von Comparator, Iterator und Observer kodiert sind. java.io bietet ger$teunabh$ngige Ein-/Augabedienste f.r Dateien und DatenstrGme an. java.awt verbirgt den Großteil der grafischen Klassen. Da hier das Java Abstract Toolkit (AWT) und zwGlf Unterklassen enthalten sind, kann dieses Paket als Herzst.ck der Hierarchie angesehen werden. javax.swing bietet Klassen f.r Komponenten, Steuerungen auf .bergeordneter Ebene (so genannte higher-level controls) und Anschlussstellen f.r den $ußeren Eindruck eines Programms (das so genannte Look-and-Feel). java.net kombiniert die Klassen, die Programmierung f.r das Internet auf den unteren Schichten anbieten. Zus$tzlich wird Unterst.tzung f.r das World Wide Web und HTML angeboten. java.applet enth$lt eine einzige Klasse, die Unterst.tzung f.r in HTML eingebettete Java-Applets anbietet.
java.lang – Hauptunterst,tzung der Sprache Das Paket java.lang als Sammlung von Klassen ist kurz und knapp gehalten. Die Mehrzahl der java.lang-Klassen erweitern direkt die Klasse Object, welche die Wurzel f.r die gesamte Klassenhierarchie von Java ist und nicht nur f.r java.lang. Die Unterhierarchie Number ist ein gutes Beispiel daf.r, wie objektorientierte Vererbung funktioniert und wann diese angewendet werden soll. Die Klassen Byte, Short, Long, Float und Double haben Faktoren gemeinsam. Deshalb wurde eine Oberklasse geschaffen, die diese gemeinsamen Eigenschaften enth$lt (eingekapselt, engl. encapsulate). Beachten Sie, dass die Klasse Number auch als abstrakt deklariert ist. Sie kGnnen keine Objekte direkt aus einer abstrakten Klasse erzeugen (Instanzen bilden, engl. instantiate). Dies ist nur mit konkreten Klassen mGglich. Obwohl es .blich ist, dass eine .bergeordnete Klasse (superclass) abstrakt ist, ist dies keinesfalls notwendig. Konkrete Klassen kGnnen die lokale Wurzel ganzer Unterhierarchien sein (die Klasse Object ein herausragendes Beispiel daf.r). Von allen Paketen ist java.lang außergewGhnlich, da es das einzige Paket ist, das Sie niemals explizit in Ihre Programme importieren m.ssen. Der Compiler .bernimmt dies, indem er implizit die folgende Zeile an den Anfang aller Ihrer Quellcodedateien schreibt: import java.lang.*; 246
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
java.lang – Hauptuntersttzung der Sprache
Der Stern (*) in dieser Zeile bedeutet, dass alle Klassen des Pakets importiert werden. Dies importiert allerdings keine Klassen in Unterpaketen. Das Paket java.lang erh$lt eine Sonderbehandlung, weil einige der Klassen so tiefe Schichten der Programmierung ansprechen, dass sie als Teil des Java-Sprachkerns betrachtet werden. Die Trennlinie zwischen der Sprache und den externen Bibliotheken kann vielleicht f.r Sprachentwickler von Bedeutung sein, aber f.r die Programmierer von Anwendungen ist diese Problemstellung von eher akademischer Natur. BASIC hat zum Beispiel die Kommandos zur Manipulation von Strings als Teil der Sprachdefinition deklariert. Auf der anderen Seite verl$sst sich C auf externe (aber intern reorganisierte) Standardbibliotheken von Funktionen, um diese Aufgaben zu erledigen. Da sich Java mehr an die Philosophie von C anlehnt, nach der der Kern einer Sprache so einfach wie mGglich sein soll, verl$sst es sich ebenfalls auf eine externe Ansammlung von Methoden, die sich an alles richten, was .ber die grunds$tzliche Datenverarbeitung und algorithmische Steuerung hinausgeht. Die folgenden Typen von Klassen sind im Paket java.lang enthalten: Typewrapper-Klassen Unterst.tzungsklassen f.r String Eine Klasse, in der die mathematische Bibliothek enthalten ist Unterst.tzungsklassen f.r das Multithreading Unterst.tzungsklassen f.r den Systemzugriff auf den unteren Schichten (low-level system access) Error- und Exception-Klassen Im folgenden Abschnitt werden diese Klassen n$her betrachtet.
Die Typewrapper-Klassen Java arbeitet mit zwei unterschiedlichen Arten von Entities: primitiven Typen und echten Objekten. Zahlen, boolesche Werte und Zeichen verhalten sich sehr $hnlich, wie die aus prozeduralen Sprachen wie C, Pascal oder sogar C++ bekannten Entsprechungen. Andere objektorientierte Sprachen, wie zum Beispiel Smalltalk, behandeln diese primitiven Typen nicht auf dieselbe Art. Smalltalk verwendet zum Beispiel f.r alles Objekte: Zahlen sind Objekte, Zeichen sind Objekte und so weiter. Obwohl Smalltalk in einer Zeit entstand, die von Lochkarten dominiert wurde (1972), stellt diese Sprache noch immer die Referenz in Sachen objektorientierte Sprachen dar. Jede seit Smalltalk entwickelte objektorientierte Sprache hat seitdem versucht,
Hinweis 247
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
besser zu sein als Smalltalk, aber die meisten haben es nur mit M.he geschafft, mit Smalltalk gleich zu ziehen. Als rein objektorientierte Sprache gesehen kommt Java Smalltalk sehr nahe. Obwohl Java durchweg objektorientiert ist, verwendet es aus Gr.nden der Performance f.r die meisten primitiven Typen keine Objekte, weil das Manipulieren primitiver Typen ohne den objektorientierten Overhead ein wenig effizienter ist. Allerdings ist eine gleichfGrmige und konsistente Umgebung, die nur aus Objekten besteht, sehr viel einfacher und kann deshalb deutlich leistungsf$higer sein. Java enth$lt viele Untersysteme, die ausschließlich mit Objekten arbeiten kGnnen. Mit der Zahl solcher Untersysteme h$ufen sich die F$lle, in denen das System dazu in der Lage sein soll, direkt mit Zahlen, booleschen Werten und Zeichen umzugehen. Wie umgeht nun Java dieses Dilemma? Indem es die primitiven Typen in eine Art Zuckermantel der Objekte verpackt (wrapping). Sie kGnnen zum Beispiel sehr leicht eine Klasse erzeugen, deren einziger Zweck das Einkapseln einer einzelnen Integerzahl ist. Der Netzeffekt w$re, dass es ein Integerobjekt g$be, dem die Universalit$t und Macht zu Eigen w$re, die nur im Zusammenhang mit Objekten zu erreichen ist, allerdings auf Kosten eines gewissen Verlustes an Performance. Das Paket java.lang enth$lt solche Typewrapper-Klassen f.r alle primitiven Java-Typen: Klasse Integer f.r Typ int Klasse Long f.r Typ long Klasse Byte f.r Typ byte Klasse Short f.r Typ short Klasse Float f.r Typ float Klasse Double f.r Typ double Klasse Character f.r Typ char Kasse Boolean f.r Typ boolean Klasse Void f.r Typ void Unter den nummerischen Typen sind die Klassen Integer, Long, Byte, Short, Float und Double so $hnlich, dass sie sich alle aus einer abstrakten Oberklasse mit dem Namen Number ableiten. Grunds$tzlich erlaubt jede einzelne dieser Klassen das Erstellen eines Objekts aus dem $quivalenten primitiven Typ und umgekehrt.
248
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
java.lang – Hauptuntersttzung der Sprache
Die String-Klassen Es existieren in java.lang zwei Klassen, die Strings unterst.tzen: String und StringBuffer. Die Klasse String unterst.tzt Strings, die nur ausgelesen werden, w$hrend die Klasse StringBuffer modifizierbare Strings unterst.tzt. Obwohl beide Klassen offensichtlich einige Dinge gemeinsam haben, sind sie doch insofern unabh$ngig voneinander, als dass keine von beiden von einer gemeinsamen String-Oberklasse erbt. Die Klasse String enth$lt folgende Funktionalit$t: Sich auf die String-L$nge beziehende Funktionen Teilstringentnahme Teilstring-Suche und -Vergleich String-Vergleich, neben vielen anderen auch unter Verwendung der Schnittstelle Comparable Umwandlung zwischen Groß- und Kleinschreibung Entfernen vorausgehender und folgender Leerzeichen Umwandlung in und aus char-Arrays Umwandlung von primitiven Typen in String-Typen Aneinanderh$ngen von Strings (umgewandelt aus jedem Typen, inklusive Objekten) Einf.gen von Strings (wieder umgewandelt aus jedem Typen) Die Methoden zur Umwandlung und Entfernung von Leerzeichen scheinen dem Ansatz der nur lesenden String-Operationen zu widersprechen. Dies stimmt, aber die Klasse String durchbricht nicht ihre eigenen Regeln. W$hrend dieser Operationen werden von der Klasse String neue Strings erzeugt, aus denen nur gelesen wird, die jedoch unbemerkt w$hrend des Prozesses wieder verworfen werden. Im Gegensatz dazu konzentriert sich die Klasse StringBuffer auf solche Operationen, die typischerweise den String selbst oder seine L$nge ver$ndern.
Die Bibliothekenklasse Math Die Klasse Math fasst eine typische und recht konservative Sammlung mathematischer Funktionen zusammen. Die zur Verf.gung stehenden Funktionen lassen sich wie folgt klassifizieren: 249
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Absolute Werte, Ober- und Untergrenzen, Minimal- und Maximalfunktionen. Diese sind angemessen .berladen, so dass Sie jeden nummerischen Typ an diese Funktionen .bergeben kGnnen, ohne dass Ihre Argumente in andere Typen gewandelt werden und dabei mGglicherweise an Genauigkeit verlieren. Quadratwurzel, Potenzierung, Logarithmus und Exponenzialfunktionen. All diese Funktionen nehmen ausschließlich Werte doppelter Genauigkeit an und geben auch nur solche zur.ck (Double und nicht Float ist der vorgegebene Typ f.r die Gleitkommagenauigkeit in Java). Sie brauchen andere Typen (wie zum Beispiel Int oder Float), die Sie an diese Funktionen .bergeben, nicht zu wandeln, da der Compiler automatisch kompatible Argumente f.r Sie umwandelt. Eine Konstante f.r die Basis des Logarithmus ist als doppeltgenauer Wert in Math.E definiert. Trigonometrische Funktionen (sin, cos, tan, asin, acos, atan). All diese Funktionen arbeiten mit Winkeln, die in Radianten und nicht in Grad dargestellt sind. Ein Vollkreis in Radianten hat 2xPi Radianten (entgegen 360 Grad). Pi ist bequem erreichbar mit doppelter Genauigkeit als Konstante Math.PI in der Klasse Math definiert. Ein Funktion zur Erzeugung von Pseudozufallszahlen. Eine Methode random() wird angeboten, die als Grundlage f.r Zufallszahlen in Applikationen verwendet werden kann. Zufallszahlen sind von besonderer Bedeutung in Simulationen, statistischen Auswertungen und nat.rlich Spielen.
Die Untersttzungsklassen fr Multithreading Zwei Klassen, Thread und ThreadGroup und eine Schnittstelle, Runnable, sind
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
java.lang – Hauptuntersttzung der Sprache
Erzeugen neuer Threads. Dies ermGglicht es, Ihren Anwendungen unabh$ngige Auftr$ge .ber mehrere unabh$ngige „Unterprogramme“ zu verteilen. Die Gesamtperformance der Anwendung verbessert sich, wenn mehrere dieser Threads Ein-/Ausgabeoperationen durchf.hren. Starten von Threads. Wenn ein Thread initial erstellt wird, beginnt er noch nicht sofort mit der Ausf.hrung. Sie m.ssen ihn explizit mit einem start()Kommando starten. Um einen Thread zu stoppen, sollte periodisch eine Statusvariable .berpr.ft werden und einen entsprechenden Wert zur.ckliefern. Dies stellt sicher, dass alle Ressourcen wieder ordentlich freigegeben werden. Threads fr eine bestimmte Zeit anhalten oder die Ausfhrung unterbrechen. Wenn ein Thread in einer Schleife keine Arbeit mehr auszuf.hren hat, sollte er sich selbst einschl$fern, damit andere Threads mehr von den Prozessorressourcen verwenden kGnnen. Auch solche Prozesse, die die CPU stark auslasten, sollten in regelm$ßigen Abst$nden Pausen einlegen, damit andere Threads ausgef.hrt werden kGnnen. Undern der PrioritGt, des Namens oder des Zustandes des Daemons. Threads haben mehrere Attribute, die dynamisch ver$ndert werden kGnnen, w$hrend der Thread abl$uft. Das Priorit$tsattribut beeinflusst ganz besonders die dem Thread zur Verf.gung gestellten Ressourcen des Prozessors. Threads kGnnen auch als Daemon Threads gekennzeichnet werden. Abfragen der Attribute von Threads. Jeder Thread kann herausfinden, was seine Priorit$t ist, wie sein Name lautet oder wie sein Daemon-Status ist. Dies ist n.tzlich, wenn Sie mehrere Klone eines Threads starten, die sich zum Beispiel nur durch den Namen unterscheiden, aber trotzdem als individuelle Threads handeln ($hnlich den Zwillingen im wirklichen Leben). Benutzen der Schnittstelle Runnable, um einem Thread mitzuteilen, was er zu tun hat. Verwenden von Instanzen der Klassen ThreadLocal und InheritableThreadLocal um thread-level-, static- und private-Variablen bereitstellen zu kGnnen. Die Klasse ThreadGroup kapselt Methoden ein, die mit denen unter Thread aufgelisteten vergleichbar sind, mit der Ausnahme, dass eine Gruppe von Threads eben genau das ist: Eine Ansammlung von miteinender in Beziehung stehenden Threads, die in einem gemeinsamen Rahmen operieren. Diese gestatten einer Anzahl in Beziehung stehender Threads, sich Attribute zu teilen und kGnnen als Gruppe ver$ndert werden. Mehr .ber das Multithreading entnehmen Sie bitte Kapitel 4: Textfelder, KontrollkGstchen und Layoutmanager verwenden.
251
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Klassen fr Systemzugriffe auf unteren Ebenen Es gibt einige wenige Klassen, die Zugriff auf Systeminformationen und Systemressourcen ermGglichen: System, Runtime und Process. Die Klasse System kapselt die klassischen Dateizugriffe stdin (als System.in), stdout (als System.out) und stderr (als System.err). Diese ermGglichen es Ihnen, Eingaben und Ausgaben .ber die Konsole wie gewohnt zu verarbeiten. Zus$tzlich zu diesen Klassenvariablen sind folgende Typen von Methoden in der Klasse System enthalten: Plattformoptimiertes kopieren von Arrays Katastrophales Ende (terminiert die Applikation und das Java-Interpreter-Subsystem!) Abfrage von Systemeigenschaften Methoden, die sich mit Sicherheitsrichtlinien befassen Die Klasse Runtime bietet speziellen Zugriff auf die Laufzeitumgebung .ber die folgenden Typen von Methoden an: Verf.gbarer Speicher und Hinweise zur Speichernutzung Plattformspezifisches Starten von Programmen mit Hilfe der Klasse Process Die Klasse Process bietet Zugriff auf stdin, stdout und stderr f.r die Ausf.hrung des Programmprozesses und die jeweiligen R.ckgabewerte an.
Die Klassen Error und Exception Der Unterschied zwischen Fehlern (Errors) und Ausnahmezust$nden (Exceptions) besteht darin, dass Errors Probleme in der Java-VM anzeigen, w$hrend Exceptions auf Probleme im Programm hindeuten. Es gibt ungef$hr 20 Error-Klassen und mehr als 40 Exception-Klassen in JDK 1.3. Throwable ist die Wurzel aller Exception-Klassen. Diese Klasse ist eine direkte Unterklasse von Object. Die Methoden in der Klasse Throwable dienen dem Erhalten von Fehlermeldungen, die den jeweiligen Exceptions zugeordnet sind und der Angabe der Stelle, an der die Exception aufgetreten ist. Die unmittelbare Unterklassen von Throwable sind die Klassen Error und Exception. Die Unterklassen von Error werden grunds$tzlich dazu verwendet, abnorme Zust$nde des Systems zu signalisieren, wie zum Beispiel wenn der Java-VM der 252
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket java.util – ntzliche Sprachuntersttzung
Speicher ausgeht oder im Interpreter ein Stack .berl$uft. Grunds$tzlich lassen sich solche Fehler nicht auffangen und deren Handhabung sollte nicht eigenen Programmen .berlassen werden. Auf der anderen Seite geben die Unterklassen der Klasse Exception solche Zust$nde wieder, die potenziell aufzufangen sind. So kann zum Beispiel eine Unterklasse von Exception das Ende einer Dateieingabe signalisieren. StGßt ein Programm auf diesen Zustand, dann sollte es nicht abst.rzen, sondern das Lesen der Datei beenden. In Kapitel 9: Fehler vermeiden durch effektives Exception Handling erfahren Sie mehr .ber den Umgang mit Java Exceptions.
Das Paket java.util – n,tzliche Sprachunterst,tzung Das Paket java.util enth$lt weitere abstrakte Datentypen (ADT) und zus$tzlich 13 Schnittstellen, von denen die Mehrzahl mit Java 2 eingef.hrt wurde, um das Collections Framework zu unterst.tzen (siehe unten). Das Paket java.util verwendet das Prinzip der Vererbung in sehr viel st$rkerem Maße als das Paket java.lang. So ist zum Beispiel die Klasse Properties eine Erweiterung (Unterklasse) der Klasse Hashtable, die wiederum eine Erweiterung der Klasse Dictionary ist. Sie werden im Verlauf der Vorstellung weiterer Pakete erkennen, dass die Hierarchien zunehmend tiefer und komplexer werden. Stack ist als Unterklasse von Vector implementiert. Sie werden wahrscheinlich neben dieser Klasse eine Klasse Queue erwarten. Jedoch wird die Unterst.tzung von Queues („First-in, first-out“ oder FIFO) durch die Klasse LinkedList bereitgestellt.
Hinweis
Die folgenden Klassen sind die in java.util enthaltenen Hauptklassen: Kernschnittstellen f.r Sammlungen: Collection, Set, SortedSet, List, Map und SortedMap Konkrete Implementierungen: ArrayList, HashMap, HashSet, Hashtable, LinkedList, Properties, Stack, TreeMap, TreeSet, Vector und WeakHashMap Abstrakte Implementierungen: AbstractCollection, AbstractList, AbstractMap, AbstractSequentialList, AbstractSet und Dictionary Infrastrukturelle Schnittstellen: Iterator, ListIterator, Comparable (aus java.lang) und Comparator Infrastrukturelle Klassen: Collections und Arrays Date und seine unterst.tzenden Kalender- und Zeitklassen
253
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Locale und die unterst.tzenden Klassen zur Ressourcenb.ndelung BitSet Dieses Paket enth$lt zus$tzlich drei andere Schnittstellen: Enumeration Observer (mit der Klasse Observable) EventListener Die folgenden Abschnitte werden sich mit diesen Klassen und Schnittstellen etwas detaillierter befassen.
Das Collections Framework Seit der JDK-Version 1.3 von Java gibt es eine neue Fhigkeit im Sprachumfang: Das so genannte Collections Framework (Collections bedeutet w"rtlich aus dem Englischen bersetzt „Sammlungen“ und ein Framework ist im weitesten Sinne ein „Rahmen“; Collections Framework ließe sich damit ungefhr als „Rahmen fr Sammlungen“ bersetzen, es hat sich jedoch die Verwendung des Begriffs Collections Framework eingebrgert). Eine Collection reprsentiert eine Gruppe von Objekten, wie zum Beispiel einen Vektor oder eine Liste. Das Collections Framework erstellt eine vereinheitlichte Architektur zur Reprsentation und Manipulation jeder Art von Collection. Dies reduziert den notwendigen Programmieraufwand, erh"ht gleichzeitig die Performance und verbessert die Weiterverwendung von Software. Das neue Collections Framework basiert auf sechs Sammlungsschnittstellen und den zur Manipulation notwendigen Algorithmen. Die einfachste Schnittstelle ist Collection. Es gibt drei Schnittstellen, die Collection erweitern: Set, List und SortedSet. Weiterhin gibt es zwei andere Schnittstellen (Map und SortedMap), die Kartografien reprsentieren und Operationen in Bezug auf die Sicht der Sammlungen enthalten, was es erm"glicht, solche Sammlungen als Gesamtheit zu manipulieren.
254
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket java.util – ntzliche Sprachuntersttzung
Die Hauptschnittstellen der Collections Die Schnittstellen Collection, Set, SortedSet, List, Map und SortedMap bilden die Basis des Collection Frameworks. Sie bieten unsortierte Gruppen .ber Collection an, Sammlungen ohne Duplikate via Set, sortierte Gruppen .ber List und Sammlungen von Schl.ssel-Wertepaaren .ber Map, aber auch sortierte Gruppen mit SortedSet und SortedMap. Die Methoden dieser Schnittstellen werden f.r die folgenden Aufgaben verwendet: Hinzuf.gen von Elementen Finden von Elementen Zugreifen auf Elemente Entfernen von Elementen Auflisten der Elemente Erhalten von GrGßeninformationen Es ist Ihnen wahrscheinlich aufgefallen, dass einige Schnittstellen und Klassen in java.util dieselbe Funktionalit$t doppelt anbieten. Vor Java 2 war die Schnittstelle Enumeration mit den Klassen Vector, Hashtable und Dictionary die Basis der Unterst.tzung von Sammlungen. Das mit Java 2 eingef.hrte Collections Framework bietet eine vervollst$ndigte Grundlage f.r das Arbeiten mit Objektgruppen an. Neue Programme sollten auch die neueren Klassen benutzen.
Tipp
Konkrete Klassen der Implementierung von Sammlungen Die Klassen ArrayList, HashMap, HashSet, Hashtable, LinkedList, Properties, Stack, TreeMap, TreeSet, Vector und WeakHashMap werden Sie aus java.util wahrscheinlich am h$ufigsten verwenden. Sie repr$sentieren die Implementierungen der Kernschnittstellen der Sammlungen: ArrayList ist ein in der GrGße ver$nderbares Array, das die Schnittstelle List implementiert. Die Klasse Vector verh$lt sich wie eine ArrayList, w$hrend Stack einen „last-in, first-out“-Objektvektor darstellt. HashMap und HashSet sind durch Hashtabellen unterst.tzte Sammlungen, welche die Schnittstellen Map respektive Set implementieren. Eine Hashtabelle funktioniert entsprechend zu HashMap, w$hrend Properties eine spezielle Hashtabelle f.r String ist, die leicht gespeichert und wiederhergestellt werden kann. WeakHashMap bietet Hashtabellen f.r schwache Referenzen an.
255
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
LinkedList ist eine doppelt verkettete Liste und implementiert die Schnittstelle List. Kann f.r Queues und Stacks verwendet werden. TreeMap und TreeSet sind Implementierungen gleichverteilter Bin$rb$ume, welche die Schnittstellen Map und Set implementieren. Vector und HashSet bieten synchronisierten Zugriff auf ihre Elemente an, w$hrend die verbleibenden neuen Klassen asynchronen Zugriff anbieten. F.r diese Klassen kann der Zugriff durch die Verwendung synchronisierender Wrapper extern synchronisiert werden. Weitere Informationen .ber die Synchronisation von Threads und die Kommunikation zwischen Threads kGnnen Sie Kapitel 14: Threads und Multithreading entnehmen.
Die Klassen und Schnittstellen der Infrastruktur Es gibt drei Schnittstellen der Infrastruktur innerhalb von java.util: Iterator, Comparator und ListIterator. Mit dem Hinzuf.gen der Schnittstelle Comparable in java.lang bieten diese Schnittstellen den Rahmen f.r Iterationen durch Listen hindurch und das Sortieren der Elemente an.
Hinweis
Die Schnittstelle Iterator soll eventuell zuk.nftig Enumeration ersetzen. Sie sollten deshalb versuchen, die Verwendung der Schnittstelle Enumeration zu vermeiden. Wenn es notwendig ist, alle Elemente einer Sammlung zu untersuchen, werden Sie Iterator und ListIterator verwenden. Die Schnittstelle Iterator ist ein beeindruckendes Beispiel f.r die umfangreichen F$higkeiten der Schnittstellen von Java. Iterator definiert das Verhalten (in Bezug auf die Methoden, die unterst.tzt werden sollen), das daf.r sorgt, dass jede Komponente, jedes Element, Objekt oder jeder Eintrag – kurz: alles, was in einer Klasse, die gewisse „Beh$ltereigenschaften“ hat, enthalten sein kann – gez$hlt werden kann. ListIterator erweitert Iterator und ermGglicht so das Ver$ndern der zugrunde liegenden Sammlung sowie bidirektionale Durchg$nge. Die Schnittstelle Iterator zwingt alle Klassen, drei Methoden zu implementieren: hasNext(), next() und ein operationales remove(). Die beiden Schl.sselmethoden bedeuten, dass jedes Objekt, das ein Z$hlen seiner Komponenten .ber diese Standardschnittstelle (Methode) ermGglicht, mit folgender Standard-while-Schleife von Java erkundet werden kann: iter = someCollection.iterator(); while (iter.hasNext()) { containedObj = iter.next(); // bearbeite dieses Objekt }
256
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket java.util – ntzliche Sprachuntersttzung
Die Schnittstellen Comparable und Comparator bieten Kontrolle .ber mGgliche Sortierreihenfolgen. Viele Klassen, wie zum Beispiel String, Date und URL, implementieren die Schnittstelle Comparable bereits, wodurch das Sortieren interner Java-Klassen relativ einfach ist. Wenn Sie Ihre eigenen Klassen sortieren wollen, werden Sie diese Schnittstellen verwenden. Die infrastrukturellen Klassen, Sammlungen und Arrays verwenden die Schnittstellen der Infrastruktur, um diese Sammlungen zu sortieren und zu durchsuchen. Die Einfachheit der Schnittstellen ermGglicht erstaunliche Resultate, die alle von der verwendeten Sammlung abh$ngen: Sortieren unter Verwendung eines angepassten quick-sort-Algorithmus Suchen unter Verwendung eines bin$ren Suchalgorithmus Finden der jeweils minimalen und maximalen Elemente einer Sammlung Suchen und Sortieren unter Verwendung eines angegebenen Comparators
Date und untersttzende Klassen Die Klasse Date kapselt die Repr$sentation von Zeitangaben mit einer auf Millisekunden genauen Pr$zision. Die unterst.tzenden Klassen sind TimeZone, SimpleTimeZone, Calendar und GregorianCalendar. Diese bieten die folgenden Typen von Methoden f.r das Interpretieren und Modifizieren von Datumsinformationen an: Ver$ndern der Komponenten von Jahr, Monat, Tag, Stunde, Minute und Sekunde Abfragen der Komponenten von Jahr, Monat, Tag, Stunde, Minute und Sekunde Konvertieren von und in Strings und Long Integer-Werte Vergleichen von Zeit- und Datumswerten (inklusive Kalenderarithmetik) Unterst.tzung von Zeitzonen
Locale und untersttzende Klassen Die Klasse Locale bildet die Grundlage f.r regionale Anpassungen von Java-Programmen. Die unterst.tzenden Klassen sind ResourceBundle, ListResourceBundle und PropertyResourceBundle.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Das Paket java.text enth$lt viele zus$tzliche Klassen zur Unterst.tzung solcher regionalen Anpassungen, zum Beispiel DateFormat, MessageFormat und NumberFormat. Diese Klassen ermGglichen es, Text entsprechend lokaler Gegebenheiten unter anderem in Bezug auf die Sortierung von Jahr, Monat und Tag und W$hrungssymbole darzustellen.
Die Klasse BitSet Die Klasse BitSet implementiert das Setzen von Bits. Anders als andere Klassen oder Sprachfunktionen, die Bits setzen, hat diese Klasse keine Begrenzungen. Sie kGnnen deshalb sehr viel weiter gehen, als dies mit den sonst .blichen 32- oder 256-Bit_Beschr$nkungen mGglich w$re. Operationen von BitSet umfassen: das Auslesen, Setzen und LGschen einzelner Bits AND-, OR- und XOR-Operationen auf Bits das Vergleichen von Bits
Die Schnittstelle Observer und die Klasse Observable Die Schnittstelle Observer und die Klasse Observable stellen gemeinsam exemplarisch dar, wie die Entwickler von Java vermieden haben, das Rad erneut zu erfinden, was in der Softwareentwicklung leider immer wieder vorkommt. Die Metapher des Beobachters (engl. observer) und des „Beobachtbaren“ (engl. observable) adressiert ein etwas abstrakteres Problem als das des Z$hlens von Elementen, wie es durch die Schnittstelle Iterator gelGst wird. Manchmal ist es innerhalb einer Applikation notwendig, ein Objekt zu ver$ndern, um damit Ver$nderungen in anderen Objekten auszulGsen; diese Ver$nderungen kGnnen wiederum andere Objekte ver$ndern und so weiter. Dieses Netz wechselseitiger Abh$ngigkeiten wird dependency network genannt. Die Designer der Klassenhierarchie von Java entwickelten Observer und Observable, um dieses Designproblem zu lGsen. Der durch dieses Duo erzwungene Mechanismus ist vergleichsweise simpel: Jedes Ursprungsobjekt, das irgendeine Art von Nachricht an andere Objekte senden soll, sollte als Unterklasse der Klasse Observable gebildet werden. Objekte, die solche Nachrichten empfangen kGnnen sollen, sollten entsprechend die Schnittstelle Observer implementieren. Hier die einzige Methode, welche die Schnittstelle Observer benGtigt: public void update(Observable o, Object arg)
258
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket java.io
Um die Abh$ngigkeit einzurichten, werden alle beobachtenden Objekte (alle, die die Schnittstelle Observer implementieren) dem beobachtbaren Objekt (das als Unterklasse von Observable gebildete Objekt) hinzugef.gt. Sobald sich das beobachtbare Objekt ver$ndert, ruft es seinerseits die Methode notifyObservers() auf.
Das Paket java.io – Dateien und Ein-/Ausgabestr1me Das Paket java.io enth$lt ein Arsenal von Klassen, die sich mit Ein-/Ausgabe besch$ftigen. Eine Unterscheidung der Klassen auf oberster Ebene gliedert diese wie folgt: Eingabe- und AusgabestrGme von Bytes Leser und Schreiber von Zeichen Filterung von StrGmen, Lesern und Schreibern Kennzeichnung von StrGmen Die Klasse RandomAccessFile Wie Sie aus dem Vererbungsbaum erkennen kGnnen, verzweigt sich der Strom der Ein-/Ausgabe aus dem Hauptbereich des Baums. Ein Strom (engl. stream) ist ein abstraktes Konzept, das regelm$ßig im Zusammenhang mit der Programmierung von Ein- und Ausgaben verwendet wird. Es repr$sentiert einen linearen, sequenziellen Fluss von Bytes, die eine Eingabe oder eine Ausgabe darstellen. StrGme kGnnen „auf Sie zu fließen“ oder „von Ihnen weg fließen“, je nachdem, ob Sie einen Eingabestrom (engl. input stream) oder Ausgabestrom (engl. output stream) betrachten. Sie lesen aus EingabestrGmen (d.h., der Strom liefert Ihnen die Daten an) und Sie schreiben in einen Ausgabestrom (d.h., Sie transferieren Daten in den Strom). Der Schl.sselpunkt der StrGme ist, dass diese Sie von den Eingabe- und Ausgabeger$ten abschirmen, die Sie im Endeffekt ansprechen. Wenn Ihr Code diese abstrakten Objekte (StrGme) ansteuert, kGnnen Sie einfach auf andere physikalische Ein-/ Ausgabeger$te umschalten, ohne dabei den Code Ihrer Applikation $ndern zu m.ssen. Dies ist der Hauptgrund daf.r, dass StrGme existieren. Leser und Schreiber entsprechen den Eingabe- und AusgabestrGmen, aber die kleinste Einheit ist hier das Zeichen im Unicode-Format. Die Klassen, die sich mit Lesern, Schreibern und StrGmen im Paket java.io befassen, lassen sich entsprechend Ihrer Hauptaufgabe in zwei Typen klassifizieren:
259
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Klassen, die einen Strom, Leser oder Schreiber an eine konkrete Datenquelle oder ein bestimmtes Datenziel anbinden Klassen, die Leser, Schreiber oder StrGme funktional erweitern Das Paket java.io und seine Klassen werden im Detail in Kapitel 11: Dateiein-/-ausgabe und Str me diskutiert. Die folgenden Abschnitte stellen einen
Die Klasse InputStream Die Klasse InputStream ist eine abstrakte Klasse, von der die gesamte Subhierarchie der EingabestrGme erbt. Grunds$tzlich kann jeder Eingabestrom ein oder mehrere Bytes (und nur Bytes) aus jeder beliebigen Datenquelle lesen, die er unterst.tzt (die Funktionalit$t, die sich mit dem Kennzeichnen und Zur.cksetzen befasst, engl. mark/reset-related, wird nicht von Haus aus unterst.tzt). F.nf Unterklassen behandeln spezielle Datenquellen f.r EingabestrGme: Ein Array von Bytes Eine externe Datei Ein geleiteter Strom (vom Typ PipedInputStream) Zwei oder mehr zusammengef.gte EingabestrGme Ein String (und nicht ein StringBuffer, wie gerne falsch vermutet wird), der als Argument des Konstruktoren .bergeben wird
Hinweis
Die Verwendung von LineNumberedInputStream und StringBufferInputStream wird nicht empfohlen. Die entsprechende Verwendung ist funktional durch LineNumberReader und dem passender benannten StringReader ersetzt worden. Die folgende Abbildung soll Ihnen dabei helfen, sich die Beziehung zwischen einem Eingabestrom und seiner Datenquelle bildlich vorzustellen. Die Abbildung stellt die Klasse ByteArrayInputStream dar, einen Eingabestrom, der Sie wie gewohnt Bytes oder BlGcke von Bytes aus einem Strom lesen l$sst (wie durch die Klasse InputStream definiert). Im Fall der Klasse ByteArrayInputStream stammen die gelesenen Bytes aus einem Array von Bytes. Andere Klassen werden andere Datenquellen haben, so wird zum Beispiel die Klasse FileInputStream seine Daten aus einer Datei lesen.
260
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket java.io
Abb. 10.2 Datenquelle und Eingabestrom
Die EingabestrGme, die Sie bisher kennen gelernt haben, lesen Bytes aus verschiedenen Datenquellen aus. Sie kGnnen diese als low-level-EingabestrGme ansehen. Die verbleibenden EingabestrGme im Paket java.io lesen Bytes aus low-level-EingabestrGmen und organisieren diese Eingabe in Informationen hGherer Ebenen. Die abstrakte Klasse FilterInputStream ist die Wurzel des Teilbaums, der die meisten dieser zus$tzlichen Klassen zusammenfasst, die folgende Funktionen aus.ben: Einf.gen eines die Performance verbessernden Puffermechanismus zwischen der .blichen Lesefunktionalit$t der Klasse InputStream (Eingabestrom) und Ihrer Applikation Hinzuf.gen einer Leseunterst.tzung f.r alle primitiven Java-Typen, die zuvor in einen Strom gespeichert wurden Hinzuf.gen einer Option zum Widerrufen der letzten ein-Byte-Leseoperation Lesen von Objekten aus low-level-StrGmen Die Unterklassen von FilterInputStream kGnnen mit anderen InputStream-Klassen zu sehr m$chtigen Werkzeugen kombiniert werden. Sie kGnnten zum Beispiel ByteArrayDataInputStream oder FilePushbackInputStream erzeugen. Um dies zu tun, w.rden Sie eine Instanz eines Objekts des Typs der Datenquelle InputStream (Byte,File, Pipe, Sequence oder String) als Argument an den Konstruktor des Filtertyps InputStream (Buffered, DataInput, LineNumber oder Pushback) .bergeben. Da diese Unterklassen von FilterInputStream wiederum jeweils selbst ein InputStream sind, kGnnen Sie mehrere dieser filternden Typen kombinieren. Es w$re also denkbar, dass Sie einen BufferingPushbackLineNumberingStringInputStream erstellen, aber damit w.rden Sie gegen alle Regeln dieses Buchs in Bezug auf lesbaren Code verstoßen. Diese Technik kann auch auf AusgabestrGme angewandt werden.
Hinweis
Die Klasse OutputStream Die Klasse OutputStream ist eine abstrakte Klasse, von der die gesamte Subhierarchie der AusgabestrGme erbt. Die einzige Anforderung an einen Ausgabestrom ist die F$higkeit, einzelne Bytes oder Arrays von Bytes schreiben zu kGnnen. (Entleeren,
261
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
engl. flushing, und Schließen, engl. closing, des Stroms sind f.r das, was StrGme ausmacht, von peripherer Bedeutung.) Wie im Fall der EingabestrGme kGnnen AusgabestrGme nicht f.r sich selbst existieren. Sie m.ssen mit einem Datenziel verbunden sein, um n.tzlich sein zu kGnnen. Die Unterklassen von OutputStream kGnnen entsprechend ihrer Hauptaufgabe klassifiziert werden: Auswahl des Datenziels (genannt sink) oder Erweiterung des stromschreibenden Verhaltens. Die folgende Abbildung soll Ihnen dabei helfen, sich die Abh$ngigkeit zwischen einem Ausgabestrom und dessen sink vorzustellen. Sie stellt ein Szenario f.r die Klasse FileOutputStream dar. Alle Bytes, die in den Strom geschrieben werden, enden in einer externen Datei. Die Datei kGnnen Sie angeben, wenn Sie den Ausgabestrom erschaffen. Die Klassen der AusgabestrGme, die das Verhalten der AusgabestrGme erweitern, zweigen fast deckungsgleich aus dem Ausgabe$quivalent von FilterInputStream ab, das im vorangegangenen Abschnitt erkl$rt wurde: FilterOutputStream.
Abb. 10.3 Ein Ausgabestrom und ein sink
Die Klassen Reader und Writer Die Klasse Reader ist das abstrakte und zeichenorientierte Gegenteil von InputStream. Reader hat neun Unterklassen. Wie EingabestrGme gliedern sich diese in zwei Kategorien: low-level-Leser, die rohe Zeicheneingaben aus verschiedenen Quellen auslesen, und filternde Leser der hGheren Ebenen, die von den low-level-Lesern angelieferte Daten organisieren. Die Klasse Writer ist das abstrakte zeichenorientierte Gegenteil von OutputStream. Writer hat acht Unterklassen, die sich wiederum in die bekannten Kategorien lowlevel und high-level gliedern. Die low-level-Schreiber liefern zeichenorientierte Ausgaben an verschiedene Ziele. Die filternden Schreiber der hGheren Ebenen wandeln organisierte Eingaben in Zeichen um, die an die .brigen Schreiber weitergegeben werden. 262
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket java.io
Die Klasse RandomAccessFile Die Klasse RandomAccessFile umfasst vollst$ndigen Lese- und Schreibzugriff auf Dateien auf Byteebene. Diese Klasse f$llt im Vergleich zu den anderen Klassen der Ein-/Ausgabehierarchie aus dem Rahmen. Sie w.rden erwarten, dass diese Klasse aus einer abstrakten Eingabeklasse und einer abstrakten Ausgabeklasse abgeleitet wird (zus$tzlich zu einigen Suchfunktionen), aber da sich RandomAccessFile unmittelbar aus Object ableitet, wurde stattdessen ein eher konventioneller (genauer: nicht-objektorientierter) Designansatz gew$hlt. Die von RandomAccessFile implementierten Methoden kGnnen in folgende Gruppen zusammengefasst werden: Lesen von primitiven Typen und Byte-Arrays (in bin$rer Form) Schreiben von primitiven Typen und Byte-Arrays (in bin$rer Form) Positionieren des Dateizeigers (Suchen)
Die Klasse StreamTokenizer Die Klasse StreamTokenizer extrahiert identifizierbare Teile aus Zeichenketten und Zeichensetzung aus einem Eingabestrom, entsprechend der vom Anwender definierten Regeln. Dieser Prozess wird tokenizing genannt, weil der Strom auf so genannte Tokens reduziert wird. Tokens repr$sentieren typischerweise Schl.sselwGrter, Namen von Variablen, nummerische Konstanten, literarische Zeichenketten und syntaktische Zeichensetzung (wie Klammern, Gleichheitszeichen und so weiter). StreamTokenizer enth$lt zus$tzlich verschiedene Methoden, welche die Regeln beeinflussen, nach denen der Strom in Tokens gewandelt wird. Zus$tzlich enth$lt es die Methode nextToken(), mit der das n$chstfolgende Token aus dem Eingabestrom extrahiert werden kann. Das Wandeln von Text in Tokens ist eine .bliche Technik, um die Komplexit$t der Texteingabe zu verringern. Das klassische Beispiel f.r eine Anwendung dieses Verfahrens ist der Compiler einer Programmiersprache. Compiler analysieren nicht die Datei, in der der Quellcode zu finden ist, weil dies zu einer Flut unabh$ngiger Zeichen f.hren w.rde. Stattdessen analysieren Compiler einen Strom von Tokens, der aus der Datei generiert wurde und jetzt diese Datei repr$sentiert. Schl.sselwGrter, Identifier, Zeichensetzung, Kommentare, Zeichenketten und so weiter werden zun$chst in einfach zu verarbeitende Tokens umgewandelt. Erst nach dieser Stufe der lexikalischen Analyse beginnt der Compiler damit, die komplexe Grammatik der Programmiersprache zu .berpr.fen. Der Java-Compiler javac verwendet zu diesem Zweck die Klasse StreamTokenizer.
263
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Hinweis
Die Pakete java.util.zip und java.util.jar enthalten Klassen, die das Lesen und Schreiben komprimierter .gz, .zip und .jar-Dateien ermGglichen.
Das Paket java.awt – das Herz der Hierarchie Das Paket java.awt ist in die folgenden Hauptgruppen gegliedert: zwei Zweige mit Komponenten des GUI den Teilbaum Component mit einem weiteren wichtigen Teilbaum (Container), der etwas tiefer in seinem Innern verborgen ist den Teilbaum MenuComponent Klassen zur Verwaltung des Layouts FlowLayout BorderLayout CardLayout GridLayout GridBagLayout und GridBagConstraints Insets grafische Klassen Graphics, Graphics2D und PrintGraphics Image Color und SystemColor Font FontMetrics AlphaComposite und Composite BasicStroke und Stroke GraphicsConfigTemplate, GraphicsConfiguration, GraphicsDevice und GraphicsEnvironment GradientPaint und TexturePaint RenderingHints geometrische Klassen 264
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket java.awt – das Herz der Hierarchie
Point Polygon Dimension Rectangle Shape Event-Klassen Event AWTEvent AWTEventMulticaster EventQueue ActiveEvent sonstige Klassen MediaTracker Toolkit PrintJob Cursor ComponentOrientation Das Paket AWT ist das umfangreichste und auch wichtigste Paket der gesamten Java-Hierarchie. Dies verdeutlicht, dass das Design und die Implementierung der GUI einer Applikation leicht mehr als ein Drittel der Ressourcen der Softwareentwicklung verbrauchen kann. Das AWT zielt darauf ab, diesen Anteil des Aufwands an der Entwicklung zu reduzieren, indem es ermGglicht, das GUI auf einfache Weise unabh$ngig von der verwendeten Plattform zu gestalten – ein revolution$rer Schritt. Die gesamte Hierarchie existiert, um den Anwendungsentwicklern das Leben zu erleichtern, aber es soll besonders das Entwickeln einer GUI so einfach und problemlos wie mGglich machen. Alle Klassen, die nachfolgend umrissen werden, sind zu 100 Prozent unabh$ngig von den Plattformen, auf denen sie eingesetzt werden. Das bedeutet, dass Ihre Java-GUI-basierten Applikationen auf jeder Java-f$higen Plattform funktionieren werden.
Die GUI-Klassen Der Großteil der Klassen im java.awt-Paket beziehen sich auf das Erstellen und Verwalten des GUI. Die Klassen lassen sich in folgende Kategorien gliedern: 265
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Widget-Klassen Container-Klassen Widgetlayout-Klassen Men.klassen
Widget-Klassen Die fundamentalen Grundbausteine eines GUI-Designs werden Widgets, Gadgets, Buttons oder Kn pfe genannt, je nachdem, welche GUI-Schule diese erfunden hat. Der am weitesten verbreitete Begriff ist Widgets (f.r Window Gadgets). Java implementiert davon eine schGne Auswahl. Diese lassen sich leicht in Ihren GUI-Entw.rfen einsetzen, wie Sie bereits in fr.heren Kapiteln gesehen haben. Die Klasse Button implementiert das prim$re Widget: die Schaltfl$che, den so genannten Button. Die einfachste und auch am h$ufigsten verwendete Form des Buttons ist die beschriftete Variante. Sie kGnnen auch Buttons mit Bildern anstelle einer Beschriftung erstellen (wird jedoch nicht von der Klasse Button unterst.tzt, sondern von der Klasse javax.swing.JButton, die sp$ter noch beschrieben wird). Eine Canvas-Komponente stellt eine Fl$che bereit, auf der gezeichnet werden kann. Als solche ist sie unsichtbar (hat keine grafische Repr$sentation), kann aber Mausklicks und andere Events erkennen, die dann von Ihrer Applikation verwendet werden kGnnen. Die Klassen Checkbox und CheckboxGroup, die ankreuzbare Elemente implementieren. CheckboxGroup zwingt Checkbox in eine wechselseitig exklusive Gruppe, auch bekannt unter der Bezeichnung Radio Buttons. Die Klasse Choice implementiert eine Mehrfachauswahlkomponente, typischerweise mit nur einigen wenigen AuswahlmGglichkeiten (verwenden Sie List f.r eine grGßere Menge Elemente). Die grafische Implementierung von Choice $hnelt in der Regel einem Popup-Men.. Die Klasse Component ist eine abstrakte Oberklasse f.r Widgets. Ein Label wird verwendet, um Bereichen oder anderen Widgets des GUI Namen zuzuweisen. Enth$lt lediglich eine einzelne Zeile Text, die nicht .berschreibbar (read-only) ist. List ist eine Listendarstellung mit AuswahlmGglichkeit f.r den intensiven Gebrauch. Hat eine vertikale Bildlaufleiste und ermGglicht die gleichzeitige Auswahl mehrerer Elemente.
266
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket java.awt – das Herz der Hierarchie
Die Scrollbar-Komponente ist das Steuerelement der Bildlaufleiste in Java. Diese kann horizontal oder vertikal verwendet werden. Falls ein List-Objekt nicht gen.gend Funktionalit$t bietet, kGnnten Sie sich leicht Ihr eigenes Objekt zum Auflisten von Elementen erstellen, indem Sie zum Beispiel ein vertikales Scrollbar-Objekt in eine Panel-Unterklasse einbetten. Dieses Widget repr$sentiert eine kontinuierliche Wertespanne, die jederzeit von der Applikation ausgelesen werden kann. Wird mit der Maus auf die Pfeile der Bildlaufleiste geklickt, wird dies als Kommando zum zeilenweisen Verschieben behandelt, die das Bildlauffeld um eine definierte Anzahl Zeilen versetzt. Entsprechend werden Mausklicks .ber oder unter das Bildlauffeld als Kommandos zum seitenweisen Verschieben mit analogen Ergebnissen interpretiert. Die Texteingabekomponenten beginnen mit TextComponent, das die abstrakte Oberklasse von TextField und TextArea ist. Das Widget TextField ist die S$ule der GUI-basierten Formulardarstellungen. Es erlaubt Ihnen die Texteingabe in ein kurzes, einzeiliges Eingabefenster. TextArea ist eine Variante von TextField. Es erlaubt mehrere Zeilen Text, wie zum Beispiel f.r die Eingabe einer formlosen Notiz. Sowohl TextField als auch TextArea erlauben die uneingeschr$nkte Dateneingabe, was meistens nicht von Applikationen benGtigt wird. Um Eingabefelder zu implementieren, die nur bestimmte Arten der Eingabe zulassen (nur Text, nur Zahlen, nur Daten etc.), m.ssen Sie eine Unterklasse von entweder TextField oder TextArea bilden (entsprechend Ihren Anforderungen) und deren Verhalten dahingehend erweitern, dass die Eingaben des Anwenders gegen die zul$ssigen Wertebereiche abgeglichen werden und so die Eingabe auf bestimmte Daten begrenzt wird. W$hrend einige dieser Komponenten in verschiedenen Beispielen verwendet werden, werden die Swing-Komponenten detaillierter erst in Kapitel 13: JFC-Swing-Komponenten beschrieben. Programme, die neu erstellt werden, sollten solche SwingKomponenten verwenden.
Die Container-Klassen Ein Fenster einer Anwendung ist normalerweise nicht einfach nur eine unsortierte Ansammlung anklickbarer oder ausw$hlbarer Komponenten. Gut durchdachte GUI sind in hohem Maße strukturiert und unterst.tzen den Anwender bei der Bedienung der Oberfl$che. Diese Strukturierung kann zum Teil durch so genannte Container fr Komponenten (engl. component containers) erreicht werden. Ein Fenster kann in Bereiche oder Zonen unterteilt werden, die jeweils diesen Bereichen zugeordnete Buttons, Auswahllisten und so weiter enthalten kGnnen. Wenn Sie Container verwenden, um solche visuellen und logischen Bereiche zu implementieren, bilden Sie diese Hierarchie in Ihrem Code ab. Dies ist ein weiteres Beispiel f.r das Schl.sselprinzip der Objektorientierung, in dem das Vokabular und die Strukturen des Problems auf den Code abgebildet werden. Die Container-Klassen im AWT von Java sind auch diejenigen, mit denen die Layoutmanager-Klassen arbeiten (siehe folgender Abschnitt).
267
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Hinweis
Verwechseln Sie den Ausdruck „Container“, wie er von Javas AWT verwendet wird, nicht mit der allgemeineren Klasse container, wie er von anderen objektorientierten Frameworks verwendet wird. AWT-Container sind Container f.r GUI-Komponenten, aber auf der anderen Seite Klassen abstrakter Datentypen, die andere Objekte enthalten kGnnen (zum Beispiel verkettete Listen, Stacks und Vektoren). Die AWT-Container enthalten Folgendes: Eine Klasse Container, die der generische Widget-Container ist, mit dem der Layoutmanager arbeitet. Alle weiteren Container sind aus dieser Oberklasse abgeleitet. Die Klasse Panel, die eine konkrete VerkGrperung der KlasseContainer ist. Diese Klasse hat keine grafische Repr$sentation, nicht einmal eine einfache Umrandung. Sie bilden normalerweise eine Unterklasse von Panel oder Container, wenn Sie eine logische Gruppierung von Widgets definieren und steuern wollen. Die Klasse Frame, die eine Art Grundbaustein f.r das Erstellen vollst$ndiger Fenster ist. (Es gibt auch eine Klasse Window, die „Fenster“ ohne Umrandung und Men.leiste erstellt.) Die Klasse Frame bietet Titel, Hintergrundfarben, optionale Men.leisten und Layoutmanager an. Die Klasse Dialog, die zur Implementierung direkter Kommunikation zwischen Anwendung und Anwender verwendet werden. Typische Verwendungen sind zum Beispiel Dialogfenster mit Warnhinweisen, Best$tigungsabfragen etc. Die Klasse Dialog ist keine eigenst$ndige Komponente, wie zum Beispiel Frame. Tats$chlich verl$sst sich Dialog darauf, dass die Klasse Frame einen Bereich zur Darstellung der Komponente bereitstellt. Die Klasse FileDialog, die das unverzichtbare Fenster zum ]ffnen oder Speichern von Dateien komplett mit Filtereigenschaften und jedem weiteren transparenten Zusatz implementiert, der vom verwendeten Betriebssystem bereitgestellt wird. Unter Windows 95/98/2000 und NT4 kann der Anwender zum Beispiel Verzeichnisse erstellen, bevor eine Datei erstellt wird. Die Klasse ScrollPane, die einen Container mit Bildlaufleisten implementiert, damit auch große Inhalte mit kleinen Darstellungsbereichen betrachtet werden kGnnen.
Die Widgetlayout-Klassen
268
Eine von Javas Innovationen im Bereich der GUI-Programmierung ist die Strategie der Komponentenplatzierung. Andere GUI-Frameworks verlangen, dass Sie Pixelkoordinaten f.r alle Komponenten angeben. Selbst mit speziellen Werkzeugen zum Erstellen von GUIs m.ssen Sie die einzelnen Komponenten absolut positionieren. Java wurde dahingehend entworfen, dass es unabh$ngig von bestimmten Plattformen ist. AWT verl$sst sich aber auf das von der ausf.hrenden Plattform ver-
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket java.awt – das Herz der Hierarchie
wendete System der Fensterdarstellung, um so die Bausteine f.r Fenster und Buttons zu erhalten. Es ist deshalb nicht mGglich, die Dimensionen der einzelnen Komponenten mit absoluter Pr$zision zu spezifizieren. Vom AWT wird ein automatisches Layoutsystem verwendet, das auf Layoutmanagern basiert: Mit der Klasse FlowLayout wird die GrGße jeder Komponente sowie seine Position bestimmt. Es wird dabei das von WYSIWYG-Textverarbeitungen her f.r Fließtext bekannte Verfahren f.r die Anordnung der Elemente benutzt: von links nach rechts, und bei Erreichen des Zeilenendes wird wieder links, jedoch eine Zeile unterhalb der gerade gef.llten fortgesetzt. Die Klasse BorderLayout positioniert und skaliert Komponenten entsprechend der konventionellen Verteilung der Komponenten in einem beliebigen Fenster. Diese Klasse erlaubt, dass Komponenten entlang des oberen, unteren, linken oder rechten Randes eines Fensters angeordnet werden, wodurch in der Mitte eine grGßere Fl$che f.r die .brigen Komponenten frei bleibt. Die anderen Bereiche werden den nicht belegten Platz weiterverwenden. Die Klasse GridLayout erzeugt, wie der Name bereits erraten l$sst, ein einfaches Rasterlayout. Sie kGnnen dieses Raster allerdings nicht verwenden, um den Komponenten Positionen mit zweidimensionalen Koordinaten zuzuweisen. Sie m.ssen stattdessen einen eindimensionalen Index verwenden (verwenden Sie GridBagLayout, um dies zu umgehen). Die Klasse GridBagLayout erweitert den von der Klasse GridLayout eingeschlagenen Weg. Grunds$tzlich erlaubt es diese Klasse den einzelnen Komponenten, mehr als nur eine Zelle des Rasters in entweder horizontaler oder vertikaler Richtung zu belegen. Zus$tzlich wird .ber Instanzen der Helferklasse GridBagConstraints eine pr$zisere Kontrolle .ber das Layout gew$hrt. Dies ist der leistungsst$rkste Layoutmanager aller standardm$ßigen MGglichkeiten. Die Klasse CardLayout verkGrpert das Konzept einer Reihe von Karten, durch die gebl$ttert werden kann, wobei jeweils nur eine Karte zur Zeit sichtbar ist. Dieses Art der Layoutverwaltung wird meistens verwendet, um ein mehrseitiges Layout zu realisieren. Die einzelnen Seiten sind dann .ber jeweils eigene so genannte Tabs (wie zum Beispiel h$ufig in Telefonregistern zu finden) zugreifbar. Da die Klasse CardLayout nicht .ber das Layout der Komponenten hinausgeht, sollte die ansprechende Darstellung der Karten selbst anderen Klassen .berlassen werden. Die Standardkomponente, mit der dies meistens realisiert wird, ist JtabbedPane, die im Paket javax.swing zu finden ist. Die Klasse Insets enth$lt Informationen dar.ber, wie nah die Komponenten eines Containers am Rand dargestellt werden d.rfen.
269
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Menklassen Dropdown- oder Popup-Men.s werden mit Fenstern als Teil jeder modernen Applikation assoziiert. Java-AWT unterst.tzt die vollst$ndige Men.funktionalit$t (inklusive Untermen.s und ankreuzbarer Elemente), wobei nur eine kleine Anzahl erstaunlich leicht zu bedienender Men.klassen verwendet wird: Die Klasse MenuBar dient als Anker f.r die gesamte Sammlung der Men.s, die mit einer Applikation, genauer einem Frame, verbunden sind. Jeder JavaFrame kann eine eigene Men.leiste mit Eintr$gen entsprechend seines eigenen Kontexts haben. Die Klasse Menu ist der logische Baustein f.r jedes Men.system. Men.s enthalten logisch voneinander abh$ngende Elemente und/oder Untermen.s. Ein Men. wird prim$r .ber einen einfachen Men.titel identifiziert. Die Klasse PopupMenu implementiert ein Men., das an jeder Stelle des GUI geGffnet werden kann. Die Klasse MenuComponent ist die abstrakte Oberklasse von MenuItem und CheckboxMenuItem, die jeweils Men.elemente repr$sentieren, welche der Anwender in einem Men. ausw$hlt. Die Klasse MenuShortcut enth$lt ein Tastaturk.rzel f.r ein Men.element.
Hinweis
Lassen Sie sich nicht durch die Klassenhierarchie verwirren. Ein MenuItem (oder CheckboxMenuItem) ist die logische Blattkomponente in einem finalen konkreten Men.system. Soweit jedoch die objektorientierte Hierarchie betroffen ist, muss ein MenuItem der „Vater“ eines Menu-Objekts sein. Dies widerspricht der intuitiven Auffassung, aber Sie kGnnen sich dies wie folgt vorstellen: Wo Sie auch immer ein Men.element haben, Sie kGnnen es immer durch ein vollst$ndiges Untermen. ersetzen. Deshalb muss die Klasse Menu eine Unterklasse von MenuItem sein. In jedem Fall macht dieses Henne-Ei-Problem das Programmieren der AWT-Men.s nicht komplizierter. Tats$chlich ist das Hinzuf.gen von Men.s wahrscheinlich die einfachste Sache, die Sie mit AWT machen kGnnen.
Die grafischen Klassen F.r Animationen oder Spezialeffekte benGtigen Sie MGglichkeiten, die sich deutlich von der .blichen GUI unterscheiden. Sie m.ssen Farben und Bilder kontrollieren kGnnen, ohne dabei den Beschr$nkungen der GUI Klassen f.r die Darstellung von Fenstern unterworfen zu sein. Java bietet sowohl Klassen f.r elementares Darstellen als auch Klassen f.r ausgefeiltere Darstellungen an. Graphics ist die Kernklasse f.r elementares Darstellen, w$hrend Graphics2D f.r die eher fortgeschritteneren Methoden zust$ndig ist. Die folgenden Abschnitte bieten Ihnen eine
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket java.awt – das Herz der Hierarchie
Die Klassen Graphics und PrintGraphics und die Schnittstelle PrintGraphics Graphics ist eine abstrakte Klasse, die ein einfaches 2D-Zeichenmodell mit den .blichen Grundformen der Darstellung unterst.tzt. Im Einzelnen werden folgende Klassen von Methoden angeboten: Textdarstellung Kopieren rechteckiger Bereiche (auch blitting genannt) abgerundete und umrandete Rechtecke, Ovale, Polygone und Winkel Linien
271
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Die Klasse Image Die Klasse Image kapselt eine von Plattformen unabh$ngige Datenstruktur f.r Bilder ein. Dieser Ansatz schirmt Sie von der Last der von Hardware und/oder Software abh$ngigen Bitmap-„Standards“ ab (als Stichworte seien hier nur bitplane, chunky und interleaved genannt). Die von der Klasse Image angebotenen Methoden erlauben es Ihnen, folgende Aufgaben zu bew$ltigen: Abfragen der Bilddimensionen Abfragen der Bildeigenschaften (zum Beispiel Format der Bildquelle, Informationen .ber das Copyright und so weiter) Erstellen eines grafischen Kontextes f.r das Bild, damit Sie die Methoden von Graphics auf das Bild anwenden kGnnen
Die Klassen Color und SystemColor Die Klasse Color enth$lt die von der verwendeten Plattform unabh$ngige Datenstruktur der Farben. Wie bei Bitmapgrafiken kGnnen diese auf verschiedene Arten implementiert werden. Die Klasse Color schirmt Sie von diesen Plattformabh$ngigkeiten ab. Die bereitgestellten Methoden unterst.tzen: Umwandlung zwischen RGB- (Rot, Gr.n, Blau) und HSB (Hue, Saturation, Brightness)-Farbmodellen Zugriff auf die einzelnen Rot-, Gr.n- und Blauanteile Ver$ndern der Helligkeit des Bildes Die Klasse SystemColor ist eine Unterklasse von Color. Sie bietet Zugriff auf die jeweils von der Arbeitsumgebung (dem Desktop) verwendeten Farben.
Die Klassen Font und FontMetrics Die Klassen Font und FontMetrics bieten Ihnen einen von der jeweils verwendeten Plattform unabh$ngigen Weg an, die Eigenschaften der von der jeweiligen Plattform verwendeten Zeichens$tze abzufragen. Die Methoden ermGglichen Ihnen Folgendes: Spezifizierung von Zeichensatzfamilie, Stil und PunktgrGße Abfragen der Zeichensatzattribute und metrischen Eigenschaften (Name der Zeichensatzfamilie, Stil, PunktgrGße, Zeichenbreite und Zeichenkettenbreite, L$nge von Unterschneidung und
272
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket java.awt – das Herz der Hierarchie
Die Klasse AlphaComposite und die Schnittstelle Composite Die Schnittstelle Composite beschreibt, wie Bilder .berblendet werden, damit Effekte wie zum Beispiel Transparenz mGglich werden. AlphaComposite ist ein spezieller Effekt f.r die
Die Klasse BasicStroke und die Schnittstelle Stroke Die Schnittstelle Stroke bietet einen Weg an, mit dem der logische Malstift, der Grafiken zeichnet, beschrieben werden kann. W$hrend die Basisobjekte aus Graphics nur die Darstellung von ein Pixel breiten farbigen Linien unterst.tzen, stellen die von Graphics2D unterst.tzten Operationen einen eher regelbasierten Ansatz dar. BasicStroke ist eine spezifische Implementierung, die Verzierungen wie Stiftbreite, Schr$gstellung der Schreibspitze, Linienenden und das Verbinden von Linien unterst.tzt.
Die Klassen GraphicsConfigTemplate, GraphicsConfiguration, GraphicsDevice und GraphicsEnvironment Diese vier Klassen beschreiben das Aussehen der Ziele der Operationen von Graphics2D wie Drucker, Bildschirm oder andere Arten der Darstellung. Jede grafische Umgebung kann aus einer Anzahl grafikf$higer Ger$te bestehen. In diesem Fall hat jedes dieser Ger$te eine oder mehrere Konfigurationen.
Die Klassen GradientPaint und TexturePaint Zusammen mit der Klasse Color bieten die Klassen GradientPaint und TexturePaint Muster f.r die Operationen von Graphics2D an. GradientPaint liefert einen linearen Farbverlauf an, w$hrend TexturePaint ein Bild als F.llmuster ermGglicht. Color wird f.r einfarbige F.llungen verwendet.
Die Klasse RenderingHints Sie verwenden die Klasse RenderingHints, um optionale Attribute zum Zeichnen, wie zum Beispiel Antialiasing, f.r das Graphics2D-Objekt zu aktivieren.
Geometrische Klassen Das Paket java.awt enth$lt vier geometrische Klassen. Diese enthalten die mathematischen Konzepte von Punkt, Polygon, Rechteck und Dimension:
273
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
Die Klasse Point repr$sentiert zusammen mit den Methoden setLocation() und translate() eine einfache (x,y)-Datenstruktur. Wie bei allen geometrischen Klassen werden anstelle von Gleitkommazahlen Integerwerte verwendet. Dies spiegelt den Hauptverwendungszweck dieser Klassen als Hilfsklassen f.r das Programmieren von GUIs wieder (und nicht reine Mathematik, was eine unendliche Genauigkeit f.r Zahlen und Formen voraussetzt).
Hinweis
Es gibt eine Version von setLocation(), die bei Point mit Gleitkommazahlen arbeitet. Allerdings rundet diese lediglich die Gleitkommazahl auf den n$chstliegenden Integerwert. Die Klasse Polygon repr$sentiert eine geordnete Sammlung von Punkten, die als Definition des Polygons behandelt werden. Drei Methoden erg$nzen die Datenstruktur: addPoint() ver$ndert das Polygon dahingehend, dass der neue Punkt mit in das Polygon aufgenommen wird. getBounds() berechnet das kleinste umschreibende Rechteck um das Polygon. contains() stellt fest, ob ein gegebener Punkt innerhalb oder außerhalb des Polygons liegt. Die Klasse Dimension ist eine reine Datenstruktur, die eine Variable f.r Breite und HGhe enth$lt. Die rohe Datenstruktur wird durch keine Methoden erweitert (mit anderen Worten: Dies ist wirklich die Entsprechung einer C-Structure oder eines Pascal Records). Die Klasse Rectangle repr$sentiert ein Rechteck an einer bestimmten (x,y)-Position. Die Klasse f.gt einige Methoden hinzu, mit denen Rechtecke ver$ndert werden kGnnen (Bewegen, Verkleinern, VergrGßern,
Hinweis
274
Wenn Sie mit den geometrischen Klassen vor Java 2 vertraut sind, sollten Sie sich diese noch einmal ansehen. Die Vererbungshierarchie und F$higkeiten haben sich zwischen Java 1.1 und Java 2 signifikant ver$ndert. Sie sollten sich außerdem das Teilpaket java.awt.geom f.r weitere die Geometrie unterst.tzende Klassen ansehen.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket javax.swing
Verschiedene AWT-Klassen Die folgenden Klassen aus java.awt lassen sich nicht ganz so einfach in bestimmte Kategorien einordnen: Die Klassen, die das Handling von Events unterst.tzen, sind AWTEvent, AWTMulticasterEvent und EventQueue. Die Event Klassen unterst.tzen das $ltere (vor Version 1.1) Event-Modell, das mit dem moderneren 1.1-Modell nicht kompatibel ist. Die Behandlung von Events ist in Kapitel 12: Benutzerdefinierte Komponenten n$her beschrieben. Die Klasse Cursor erlaubt es Ihnen, die Form des Mauscursors festzulegen. Die Klasse MediaTracker .berwacht Bilder, die von einem Server geladen werden. Die Klasse PrintJob dient der Kommunikation zwischen der Java-Applikation und einem Drucker. Die Klasse Toolkit ermGglicht Ihnen, auf spezifische Ressourcen des jeweils zugrunde liegenden Fenstersystems zuzugreifen, was die Anbindung an AWT-Komponenten genauso einschließt wie das Abfragen der Namen der Systemzeichens$tze oder der besten GrGße f.r einen Cursorbild. Die Klasse ComponentOrientation hilft Ihnen bei der sprachsensitiven Positionierung von Komponenten. W$hrend westliche Alphabete von links nach rechts laufen, haben andere zum Beispiel eine von oben nach unten weisende Laufrichtung.
Das Paket javax.swing Eingef.hrt mit Java 2 stellen die javax.swing-Pakete die zweite Generation der grafischen Java-Widgets dar. W$hrend die meisten Dinge wie zum Beispiel AWT Akronyme sind, gilt dies f.r Swing nicht. Angeblich soll sich dieser Begriff auf eine lose Assoziation der Java-Figur „Duke“ auf Duke Ellington und seinen Ausspruch beziehen: „Es bedeutet gar nichts, wenn es nicht diesen besonderen Swing hat.“ (Original: „It don’t mean a thing if it ain’t got that swing.“) Außerdem hat ein Ingenieur der Firma Sun kommentiert, dass die Musikrichtung Swing jetzt „in“ sei.
Hinweis
Die javax.swing-Klassen lassen sich in folgende Verantwortungsbereiche aufteilen: den Zweig JComponent f.r Widgets die Layoutmanager-Klassen
275
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
BoxLayout OverlayLayout ScrollPanelLayout ViewportLayout SizeRequirements Die Model-Klassen und -Schnittstellen Die Manager-Klassen DesktopManager und DefaultDesktopManager FocusManager und DefaultFocusManager MenuSelectionManager RepaintManager ToolTipManager UIManager Die Klassen AbstractAction und KeyStroke und die Schnittstelle Action verschiedene Klassen BorderFactory ImageIcon und die Schnittstelle Icon ProgressMonitorInputStream SwingUtilities GrayFilter Timer Zus$tzlich zum oben angef.hrten Kern des java.swing-Pakets bieten viele Unterpakete zus$tzliche Unterst.tzung an: javax.swing.borderdefiniert verschiedene Darstellungsstile f.r Rahmen und enth$lt unerst.tzende Klassen f.r farbw$hlende Komponenten. javax.swing.event enth$lt Swing-spezifische Event-Klassen und unterst.tzende Klassen f.r dateiausw$hlende Komponenten. java.swing.plaf.* enth$lt modulare Klassen zur Unterst.tzung f.r das Look-and-Feel. javax.swing.table enth$lt Klassen zur Unterst.tzung von Tabellen. 276
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket javax.swing
javax.swing.text enth$lt unterst.tzende Klassen f.r Textkomponenten, inklusive HTML und Rich Text Format (.rtf). javax.swing.tree enth$lt unterst.tzende Klassen f.r Baumkomponenten. javax.swing.undo enth$lt unterst.tzende Klassen f.r die Implementierung von undo/redo. Die Swing-Komponenten und -Architektur werden tiefergehend in Kapitel 13: JFCSwing-Komponenten beschrieben. W$hrend dieses Kapitel die gemeinsamen Teile beschreibt, die mit AWT-Widgets geteilt werden, wie Component, Container, die Layoutmanager und Events, wird die Verwendung der AWT-Widgets nicht beschrieben und auch nicht empfohlen. Die com.sun.*-APIs repr$sentieren Schnittstellen, die Sun vollst$ndig unterst.tzt und auf deren Stabilit$t Sie sich verlassen kGnnen. Es kann jedoch sein, dass diese nicht in allen Java-Laufzeitumgebungen vorhanden sind. Falls Sie eine Java-1.1-Laufzeitumgebung (oder neuer) ohne die Unterst.tzung der Swing-Pakete verwenden, kGnnen Sie diese von Sun .ber die Adresse http://java.sun.com/products/jfc/ erhalten.
Hinweis
Jcomponent-Klassen Wie java.awt so beziehen sich die meisten der Klassen, die in javax.swing enthalten sind, auf das Erstellen des GUI. JButton und JLabel sind die Swing-Widgets f.r Buttons und Labels, die Unterst.tzung f.r einzeiligen Text, ein Bild, und Popup-Tooltipps anbieten. JPanel und Box sind Container-Widgets, die Unterst.tzung f.r das Puffern von malenden Operationen, anwenderdefinierte R$nder und Vieles mehr anbieten. JMenu, JMenuItem, JSeparator, JCheckBoxMenuItem, JRadioButtonMenuItem, JMenuBar und JPopupMenu enthalten jeweils die Men.-Widgets: Men., Men.elemente, Men.teiler, umschaltbare Men.elemente, Men.leiste und Popup-Men.. Jedes kann eine einzelne Textzeile, ein Bild und Popup-Tooltipps haben. JToggleButton, JRadioButton, Jcheckbox und ButtonGroup enthalten die umschaltbaren Widgets alleine, in einer Gruppe oder außerhalb. Die letzte Klasse dient der Unterst.tzung gruppierter Buttons. JColorChooser enth$lt ein Popup-Widget zur Farbauswahl.
277
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
JComboBox und JList bieten eine Reihe von Widgets zur Unterst.zung gruppierter Auswahlen an, die nicht mehr nur auf Texte beschr$nkt sind. Außerdem bietet JComboBox Unterst.tzung f.r die Eingabe einer Auswahl, wenn die gew.nschte Auswahl nicht angeboetn wird. JFileChooser bietet ein Widget f.r Datei- und Verzeichnisauswahl an. JInternalFrame, JDesktop und JDesktopIcon enthalten Widgets, die einen Desktop anbieten, der das ]ffnen, Schließen und Ver$ndern der internen Frames $hnlich einem Desktopmanager unterst.tzt. JLayeredPane, JDesktopPane und JDesktopIcon enthalten Widgets, die einen Schichteneffekt erzeugen, wenn Komponenten .ber anderen Komponenten dargestellt werden. JOptionPane dient dem Erstellen und Darstellen von einfachen Dialogboxen mit Icons, die den jeweiligen Verwendungszweck kennzeichnen. JProgressBar enth$lt Widgets, die den Fortschritt einer mehrstufigen Operation darstellen. JSlider und JScrollBar enthalten Widgets f.r das Ausw$hlen einer Wertespanne mit und ohne Labels, die die verf.gbare Spanne anzeigen. JScrollPane und JViewPort enthalten Widgets zur Darstellung einzelner großer Komponenten in einer kleineren Fl$che. JScrollPane bietet Bildlaufleisten an, JViewPort nicht. JSplitPane enth$lt ein Widget, das zwei Widgets enth$lt. Sobald es erschaffen wurde, kann der Anwender jedes der beiden in der GrGße ver$ndern. JTabbedPane bietet direkten Zugriff auf mehrere Panels. JTable enth$lt ein Widget zur Darstellung mehrspaltiger Daten. JTextComponent, JTextField, JPasswordField, JTextArea, JTextPane und JEditorPane bieten Widgets zur Texteingabe und Textdarstellung an. JTextComponent ist der Ursprung der anderen Klassen, die ihrerseits ein- oder mehrzeiligen Text unterst.tzen. Die am weitesten fortgeschrittenen Klassen sind JTextPane f.r die Darstellung von formatiertem Text und Bildern sowie JEditorPane zur Darstellung von HTML. JToolBar und JToolTip enthalten Widgets, mit denen Men.zeilen und Hilfstexte in Form von Popup-Tooltipps erzeugt werden kGnnen. JTree enth$lt das Swing-Applet-Widget, das Unterst.tzung f.r die Darstellung von Men.s in Applets hinzuf.gt.
278
JWindow, JDialog und JFrame enthalten die Widgets f.r Swing-Fenster, -Dialogboxen und -Rahmen und erweitern auf Grundlage der AWT-Widgets.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Paket javax.swing
Layoutmanager-Klassen Zus$tzlich zu dem ungef$hr halben Dutzend Layoutmanager-Klassen in AWT sind weitere in javax.swing verf.gbar. Diese bieten die gleichen Vorteile, die mit den java.awt-Layoutmanagern verf.gbar sind, welche prinzipiell von der verwendeten Plattform unabh$ngige Applikationen sind: Mit der Klasse BoxLayout werden Komponenten entweder horizontal oder vertikal in einzelnen Zeilen oder Spalten angeordnet. Jede einzelne Komponente kann unterschiedlicher GrGße sein und es besteht die MGglichkeit, zwischen den einzelnen Komponenten Freiraum zu bestimmen. Die Klasse OverlayLayout bietet Unterst.tzung f.r das Anordnen der Komponenten .bereinander an. Die Klasse ScrollPaneLayout bietet den Layoutmanager an, der von JScrollPane auf Ihre eigenen Bereiche angewandt wird, damit diese Bildlaufleisten,
Model-Klassen und -Schnittstellen Die Swing-Widgets bieten zwei Arten von Operationen an: Sie kGnnen deren Daten intern speichern und entsprechend handeln lassen oder Sie speichern die Daten extern. Werden die Daten tats$chlich außerhalb des Widgets gespeichert, entspricht die verwendete Art dem Modell, das innerhalb des Pakets javax.swing verwendet wird. Werden die Daten extern gespeichert, kGnnen Sie das verwendete Widget und die Darstellung der Daten austauschen, ohne sich Sorgen um einen etwaigen Datenverlust machen zu m.ssen. Obwohl diese Arbeitsweise stabilere und besser zu wartende Programme erzeugt, dauert das Initialisieren l$nger.
Manager-Klassen Wie bei fast .berall in Swing, so kGnnen Sie auch hier nahezu jedes Verhalten Ihren Bed.rfnissen anpassen. Die verschiedenen Manager-Klassen in javax.swing helfen Ihnen bei der Anpassung der $ußeren Erscheinung Ihres Programms. Wenn Ihnen zum Beispiel die Art der Darstellung von Tooltipp-Mitteilungen nicht gef$llt, kGn-
279
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
nen Sie den ToolTipManager benutzen, um das Verhalten, zum Beispiel die VerzGgerung der Darstellung, zu ver$ndern. Jeder der Manager ist f.r einen anderen Teil von Swing zust$ndig.
Die Klassen AbstractAction und KeyStroke und die Schnittstelle Action Die Klassen AbstractActionund KeyStroke sowie die Schnittstelle Action bieten einen unkomplizierten Weg der Handhabung von Events innerhalb der AWT-Widgets. Normalerweise w.rden Entwickler die Antworten auf ein bestimmtes Verhalten des Anwenders programmieren. Dies ist f.r isoliert existierende Dinge auch in Ordnung. Indem Sie aber ein gut durchdachtes Serviceprotokoll erstellen, kGnnen Sie Dienste definieren, die bestimmten Objekten zugeordnet sind, diese Dienste zusammen mit den Objekten registrieren und dann andere diese Dienste benutzen lassen, sobald sie benGtigt werden. Wenn Sie zum Beispiel definieren, wie eine JTextArea, ein mehrzeiliges Eingabefeld, gespeichert wird, w.rden Sie dieses Verhalten normalerweise dem Men.punkt Datei>Speichern zuordnen. Wenn Sie aber die Aktion Speichern zusammen mit der JTextArea registrieren w.rden, kGnnten Sie das Speichern von Text immer JTextArea .berlassen. Durch den Prozess der Registrierung weiß JTextArea, welche Action-Operationen es unterst.tzt. In diesem Fall w$re Ihre Implementierung der AbstractAction verf.gbar, wodurch die Speichern-Operation verf.gbar ist. So kGnnte sie auch anderen Schnittstellen zugeordnet werden. Die Klasse KeyStroke unterst.tzt tastaturorientierte Events.
Verschiedene Swing-Klassen Die folgenden javax.swing-Klassen lassen sich nicht so einfach in Kategorien einordnen: Die Klasse BorderFactory arbeitet mit dem border-Teilpaket zusammen. Sie erzeugt unter Verwendung des Abstracts Factory Border-Objekte, ohne dass Sie eine konkrete Klasse angeben m.ssen, die die Schnittstelle Border implementiert. Die Klasse ImageIcon bietet eine Implementierung der Schnittstelle Icon f.r Image-Objekte. Sie verwenden Icons, um Bilder zusammen mit Beschriftungen, Buttons, Men.s und anderem verwenden zu kGnnen. Zusammen mit einigen weiteren .plaf-Teilpaketen stellt die Klasse LookAndFeel die Basis der modularen Look-and-Feel-Unterst.tzung dar. ProgressMonitor und ProgressMonitorInputStream stellen gemeinsam ein vorgefertigtes generisches Popup-Fenster zur Verf.gung, das es dem Anwender gestattet, das Laden von Dateien zu unterbrechen. 280
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Pakete java.net
Die Klasse SwingUtilities stellt einen Satz allgemeiner Routinen f.r h$ufig benGtigte Operationen zu Verf.gung. Neben vielen anderen Methoden gibt es zum Beispiel solche, die es ermGglichen, das hierarchisch oberste Fenster einer Komponente zu finden oder festzustellen, welcher Maustaste ein Maus-Event zugeordnet ist. Die Klasse GrayFilter ist eine bildbearbeitende Unterst.tzungsklasse, von der das Entwicklungsteam dachte, dass Softwareentwickler diese begr.ßen w.rden. GrayFilter wandelt ein Bild in ein Graustufenbild um. Die Klasse Timer stellt MGglichkeiten bereit, periodische Operationen zu signalisieren.
Das Paket java.net – Unterst,tzung f,r Internet, Web und HTML Das Paket java.net ist eine der verbleibenden Haupteigenschaften der Core-Java-API. Es bietet auf sehr hoher Ebene angesiedelte Schnittstellen f.r die eher weniger fortschrittlichen Datenkommunikationsprotokolle (und deren assoziierte APIs), genannt TCP/IP und UDP/IP, an. Die java.net-Klassen verbergen einen großen Teil der technischen Schwierigkeiten, die mit dem Programmieren der unteren Schichten des Internets verbunden sind. Die java.net-Klassen kGnnen entsprechend der folgenden Aufgaben kategorisiert werden.
Internetadressierung (Die Klassen InetAddress und URL) TCP/IP – verbindungsorientierte Klassen (verschiedene Socket-Klassen) UDP/IP – verbindungslose Klassen (DatagramPacket, DatagramSocket und MulticastSocket) URL – Authentisierungsklassen (Authenticator, PasswordAuthentication, URLDecoder und URLEncoder) Inhaltsverwalter f.r MIME-Typen (ContentHandler und URLStreamHandler) Web-verwandte Klassen (verschiedene URLConnection-Klassen) In Bezug auf die Komplexit$t – und damit auch in Bezug auf die Einfachheit der Anwendung – liegt das UDP-Protokoll zwischen dem Transmission Control Protocol (TCP, das Protokoll der unteren Schichten) und dem Internet Protocol (IP, das Protokoll der oberen Schichten). UDP ist ein Datagram-orientiertes Protokoll, was bedeutet, dass Datenpakete individuell durch das Netz reisen (vergleichbar mit Brie-
Hinweis
281
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
fen im Postsystem) und ohne eine Garantie der Auslieferung. Dies liegt daran, dass UDP – im Gegensatz zu TCP – keinen Versuch unternimmt, Datenverluste oder
Hinweis
Sockets sind Softwareschnittstellen, die eine Anwendung mit einem darunter liegenden Netzwerk verbinden. Im Internet hat jede Maschine 65.536 (64K) adressierbare Sockets, die sie verwenden kann. Alle Standarddienste (wie E-Mail, FTP und so weiter) verwenden allgemein anerkannte und .bliche Socketnummern, die zusammengefasst werden als „allgemein bekannte Portnummern“ (alle Ports bis 1.024 sind somit gesch.tzt und sollten nicht verwendet werden). Serverprogramme kontrollieren diese Ports auf ankommende Serviceanfragen. Ein Client muss erst selbst einen Port Gffnen, bevor er zu einem Server am anderen Ende eine Verbindung herstellen kann. DatagramPacket und DatagramSocket bieten zusammen das User Datagram Protocol (UDP) an. Durch die Klasse DatagramPacket hindurch kGnnen Sie den Zielhost eines Pakets (unter Verwendung einer Instanz der Klasse Inet-
282
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Pakete java.net
Address), den zu kontaktierenden Port (oder Socket) und die bin$ren Inhalte des Pakets bestimmen. Mit einer Instanz der Klasse DatagramSocket kGnnen Sie Datagramme versenden oder empfangen. Die Klasse Authenticator ermGglicht den Zugriff auf durch Passwort gesch.tzte URLs. Durch Verwendung von PasswordAuthentication kGnnen Sie Daten von solchen Adressen empfangen, die Zugriff auf dem Prinzip von Abfrage-Best$tigung verlangen.
Das Paket java.applet – in HTML eingebettete Applets Einer der Hauptgr.nde f.r den großen Erfolg von Java ist die Tatsache, dass Java verh$ltnism$ßig leicht zu erlernen und als Programmiersprache f.r verteilte Komponenten sehr effizient ist. Java-Applets sind nichts anderes als verteilte Softwarekomponenten. Deshalb enth$lt das Standard-Klassenframework wenig, was sich explizit mit solchen instrumentalen Applets befasst. Das Paket java.applet sieht verglichen mit anderen Paketen ziemlich leer aus. Die einzigen Inhalte sind eine Klasse und drei Schnittstellen. Die Klasse java.applet. Applet ist die Hauptanlaufstelle f.r Methoden, die Funktionalit$ten von Applets unterst.tzen. Die Methoden, die das Paket verf.gbar macht, kGnnen in folgende Gruppen eingeteilt werden: Initialisieren, Neustarten und Einfrieren von Applets Eingebettete Unterst.tzung von HTML-Applet-Parametern Laden von Bildern auf hoher Ebene Laden und Abspielen von Audiodaten auf hoher Ebene f.r Applets und Applikationen Herkunftsabfrage (getDocumentBase() und getCodeBase()) Einfache Statusanzeige (showStatus(String))
Verschiedene Java-Pakete javax.swing.accessibility Dieses Paket bietet Entwicklern die MGglichkeit, leicht auf unterst.tzende Technologien wie Braille-Terminals zuzugreifen. java.awt.color, java.awt.font und java.awt.geom. Diese Pakete unterst.tzen das Java2D-Framework. Sie werden in Kapitel 16: Die 2-D-Grafik-API n$her erkl$rt.
283
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Schneller und kompatibler durch Java Packages
java.awt.datatransfer Die Klassen in diesem Paket unterst.tzen Clipboard-Datentransfermodelle. java.awt.dnd Drag-and-Drop-Funktionalit$t wird nun in Version 2 angeboten. Durch das Verwenden einer Reihe von Drag-Quellen und Drop-Zielen kGnnen Java-Programme mit anderen Java- und nativen Applikationen interagieren. java.awt.event Dieses umfangreiche Paket unterst.tzt das Deligieren von Events. java.awt.im Dieses kleine Paket f.gt Unterst.tzung f.r Eingabemethoden asiatischer Sprachen hinzu. java.awt.image Die Klassen dieses Pakets gehGren zur Bildverarbeitung. java.awt.peer Dieses Paket enth$lt Klassen, die AWT-Komponenten sozusagen an das zugrunde liegende Fenstersystem „kleben“. Wenn Sie Java Programme schreiben, werden Sie diese Schnittstellen niemals verwenden m.ssen. java.awt.print Die Klassen dieses Pakets erweitern die Druckf$higkeiten, die mit Java 1.1 eingef.hrt wurden und unterst.tzen jetzt auch das Drucken von Seiten und B.chern. java.beans Dieses Paket unterst.tzt die Entwicklung von Komponenten, Beans genannt, die dermaßen wiederverwendbar sind, dass sie auch mit nicht-Java-Systemen zusammenarbeiten kGnnen, wie zum Beispiel ActiveX und LiveConnect. JavaBeans werden in Kapitel 17: JavaBeans: Ein Jberblick und Kapitel 18: JavaBeanEigenschaften behandelt. java.beans.beancontext Diese Klassen repr$sentieren das JavaBeans Runtime Containment andService Protocol, das Dienste aufsp.rt, die von der Umgebung angeboten werden. java.lang.ref Dieses Paket beschreibt einen Satz Klassen, die schwache Referenzen oder Caching anbieten. Eine Verwendung mit einem Referenzobjekt z$hlt f.r die Garbage Collection nicht als Verwendung. java.lang.reflect Die Klassen in diesem Paket unterst.tzen Reflektionen .ber ein Objekt (engl. object reflection). Dies ist eine Eigenschaft, die es ermGglicht, die Beschaffenheit der Klasse eines willk.rlichen Objekts zu untersuchen.
284
java.math Diese Klasse unterscheidet sich deutlich von der h$ufiger verwendeten Klasse java. lang.Math, die mathematische Standardfunktionen in Form statistischer Methoden implementiert. Das Paket java.math enth$lt zwei kaum verwandte Klassen, die Dezimal- und Integerzahlen beliebig hoher Genauigkeit repr$sentieren.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ausblick
java.rmi, java.rmi.activation, java.rmi.dgc, java.rmi.registry und java.rmi.server Diese Klassen unterst.tzen die so genannte Remote Method Invocation (RMI), die es ermGglicht, dass ein Objekt eine Methode eines anderen Objekts aufruft, das sich auf einer anderen Maschine befindet. java.security, java.security.acl, java.security.cert, java.security.interfaces und java. security.spec Diese Klassen unterst.tzen sichere Datenkommunikation. javax.servlet Dieses Paket stellt die Rahmenbedingungen f.r das Einbetten von JavaServlets in Webservern, um so CGI (Common Gateway Interface)-Skripte zu ersetzen. java.sql Dieses Paket bietet die Klassen und Schnittstellen an, die Java Database Connectivity (JDBC) unterst.tzen, das in Kapitel 15: Java Database Connectivity (JDBC) n$her erl$utert wird. java.text Dieses Paket bietet Klassen an, die internationalisierten Text formatieren. java.util.mime Dieses Paket bietet Unterst.tzung f.r das Beschreiben von Multipurpose Internet Mail Extensions (MIME)-Typen an.
Ausblick In Kapitel 11: Dateiein-/-ausgabe und Str me werden die Klassen vorgestellt, die Eingabe und Ausgabe unterst.tzen, sowie das Strom-basierte Modell des Lesens, Schreibens und Organisierens von Daten.
285
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und Str1me
Ein 4berblick ber StrBme
288
Die abstrakte Oberklasse
290
Die Stromklassen der unteren Ebenen
294
Andere Stromklassen der unteren Ebenen
296
Die Klassen der High-Level-StrBme
301
Die Klassen der Nicht-StrBme
314
Ausblick
318
11
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
Aus „Java 2 Developer's Handbook“ von Philip Heller und Simon Roberts, ISBN 0-7821-2179-9, 1248 Seiten, $59,99
Die Klassen, die in Java die Ein-/Ausgabe unterst.tzen, sind im Paket java.io zu finden. Die Mehrzahl dieser Klassen unterst.tzen ein Strom-basiertes Modell zum Lesen und Schreiben von Dateien. Es gibt Klassen, die StrGme auf den unteren Eben unterst.tzen, zum Beispiel f.r die Kommunikation mit Festplatten, und solche, die auf hGherer Ebene die Informationen organisieren, die durch die StrGme der unteren Ebenen geleitet werden. Die StrGme der hGheren Ebenen sind auch n.tzlich zum Organisieren von Informationen, die durch ein Netzwerk gesendet und empfangen werden. Dieses Kapitel untersucht alle Klassen, die sich mit StrGmen der unteren und hGheren Ebenen befassen. Es werden auch diejenigen Klassen betrachtet, die mit Dateien auf Speichermedien kommunizieren, ohne dabei das Strommodell zu verwenden.
Ein 8berblick ,ber Str1me Sie kGnnen sich einen Strom als Rohrleitung f.r Daten vorstellen, $hnlich einem Strohhalm oder Siphon, mit einer Quelle (engl. source) am einen Ende und einem Verbraucher (engl. consumer) am anderen. So kann zum Beispiel ein Java-Programm mit der Klasse FileInputStream Bytes aus einer Datei auf einer Festplatte lesen, wie dies in der folgenden Abbildung gezeigt wird. In dieser Abbildung ruft das Java-Programm die Funktion Lesen aus FileInputStream auf, woraufhin Bytes von der Datei gelesen und an den Aufrufer geliefert werden. In der darauf folgenden Abbildung schreibt ein Programm mit der Klasse FileOutputStream in eine Datei.
Bytes
Ein einfacher Eingabestrom
File
Inp
utS t
rea
m
Abb. 11.1
Bytes gespeicherte Datei
288
In der Praxis ist dieser Mechanismus nicht besonders n.tzlich. Dateien enthalten normalerweise hochgradig strukturierte Informationen. Aus den Bytes ergeben sich Zahlen, Text, Quellcode und so weiter. Das Paket java.io bietet eine Reihe EingabestrGme der hGheren Ebenen an, die Bytes aus einem Strom der unteren Ebenen lesen und die so gelesenen Daten bereits aufbereitet weitergeben. Die Klasse DataInputStream zum Beispiel konsumiert Bytes und produziert daraus primitive Java-Typen und Zeichenketten, wie in der folgenden Abbildung gezeigt wird. Die Technik, einen solchen hochentwickelten Strom an einen der unteren Ebenen anzuh$ngen wie in der Abbildung gezeigt, wird verketten (engl. chaining) genannt.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ein 4berblick ber StrBme
Abb. 11.2 Ein einfacher Ausgabestrom
Abb. 11.3 Zwei Stufen von Eingabestr<men
Entsprechend m.hsam ist das Schreiben von Bytes auf ein Speichermedium. Normalerweise muss ein Programm strukturierte Informationen in eine Datei schreiben. Die Klasse DataOutputStream besitzt Methoden f.r das Schreiben primitiver Datentypen und Zeichenketten. Der Dateiausgabestrom konvertiert die Daten der Quelle in Bytes, die an die Ausgabe des Strom weitergereicht werden. In der folgenden Abbildung ist die Datenausgabe des Stroms mit einer Datenausgabe eines Dateiausgabestroms verkettet. Das Resultat ist, dass die primitiven Datentypen, die ein Programm schreibt, in die entsprechenden Bytes umgewandelt werden, die dann auf dem Medium (zum Beispiel der Festplatte) gespeichert werden. Die Bytes werden in einer von der verwendeten Plattform unabh$ngigen Reihenfolge geschrieben. Deshalb kGnnen zum Beispiel Dateien, die auf einem big-endian-System geschrieben wurden, auch auf einem little-endian-System gelesen werden und umgekehrt.
Abb. 11.4 Zwei Ebenen von Ausgabestr<men
289
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
Traditionell haben die StrGme der unteren Ebenen in Java auf Bytes operiert. Die Version 1.1 des JDK f.hrte zeichenbasierte StrGme ein. Diese StrGme sehen den bytebasierten sehr $hnlich, aber sie ermGglichen es dem Entwickler, auf einer von Kodierungsschemata unabh$ngigen Ebene zu arbeiten, da alle
Die abstrakte Oberklasse Die bytebasierten Ein- und AusgabestrGme von Java werden aus den abstrakten Klassen InputStream und OutputStream abgeleitet. Die zeichenorientierten Klassen werden von den abstrakten Klassen Reader und Writer abgeleitet.
Die Klasse InputStream Die meisten Methoden der Klasse InputStream interagieren mit Systemressourcen und haben das Potenzial, plattformspezifische Probleme zu verursachen und so eine IOException auszuwerfen. Der Konstruktor f.r InputStream ist: InputStream() Dies konstruiert eine Instanz der Klasse. Nachfolgend die Methoden von InputStream: int read() throws IOException Liest ein Byte aus der Datenquelle. Gibt das Byte als die unteren 8 Bits des R.ckgabewerts zur.ck. Sind keine weiteren Daten verf.gbar, wird -1 zur.ckgegeben. Sind die Daten im Augenblick des Aufrufs nicht verf.gbar, wird der aufrufende Thread blockiert. Lesen Sie (Kapitel 14: Threads und Multithreading f.r weitere Informationen .ber das Blockieren). int read(byte[] dest) throws IOException Liest Bytes aus der Datenquelle in das Array dest ein. Kehrt zur.ck, wenn das Array gef.llt ist oder wenn die Datenquelle keine weiteren Daten mehr hat. Der R.ckgabewert ist die Anzahl der gelesenen Bytes. Beachten Sie, dass Sie ein Array von Bytes in eine Zeichenkette umwandeln kGnnen, indem Sie den Konstruktor String(byte) aufrufen. int read(byte[] dest, int offset, int length) throws IOException Wie das zuvor genannte int read(byte[] dest), mit dem Unterschied, dass hier versucht wird, Bytes einer bestimmten L$nge in das Array einzulesen, wobei bei offset begonnen wird. Liefert -1 am Ende der Eingabe zur.ck. 290
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die abstrakte Oberklasse
void close() throws IOException Setzt Systemressourcen frei, die einer bestimmten Datenquelle zugeordnet sind. Auf einer UNIX-Plattform zum Beispiel verbraucht der Eingabestrom einen File Descriptor. Der Aufruf close() setzt diesen wieder frei, und der Strom ist dann f.r das Lesen von Daten auf Dauer nicht mehr verf.gbar. int available() throws IOException Liefert die Anzahl Bytes zur.ck, die sofort und ohne Blockade gelesen werden kann. Dieser Aufruf ist nicht auf allen Systemen zuverl$ssig. Von manchen Implementierungen ist bekannt, dass sie immer 0 (Zahl Null) zur.ckliefern. long skip(long nbytes) throws IOException Versucht nbytes aus der Datenquelle zu .berspringen und zu verwerfen.
Die Klasse OutputStream Wie bei EingabestrGmen, interagieren die meisten Methoden der Klasse OutputStream mit Systemressourcen und informieren den Aufrufer durch Auswerfen einer IOException .ber plattformspezifische Probleme. Der Konstruktor f.r OutputStream ist: OutputStream() Konstruiert eine Instanz der Klasse. Im Folgenden die Methoden von OutputStream: void write(int b) throws IOException Schreibt das Byte, das in den unteren 8 Bits des Arguments auftaucht, wobei die oberen 24 Bits verworfen werden.
291
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
void write(byte b[]) throws IOException Schreibt ein Array von Bytes. Beachten Sie, dass ein String in ein Array umgewandelt werden kann, indem Sie die Methode getBytes() aufrufen, aber alle Informationen .ber das obere Byte jedes Unicode-Zeichens gehen verloren. void write(byte b[], int offset, int length) throws IOException Schreibt einen Teil des Byte-Arrays, beginnend bei offset und mit einer L$nge von length Bytes. void flush() throws IOException Schreibt alle Bytes, die der Strom gepuffert hat. void close() throws IOException Entl$sst alle Systemressourcen, die mit der Datenquelle assoziiert sind. Der Strom ist fortan f.r Schreiboperationen nicht mehr verf.gbar. F.r jeden Ausgabestrom sollte vor dem Aufruf von close() flush() aufgerufen werden.
Die Klasse Reader Die abstrakten, zeichenorientierten Klassen Reader und Writer definieren zus$tzlich zu ihren verschiedenen Methoden beide zus$tzlich zwei Konstruktoren. Hier die Konstruktoren f.r Reader: Reader() Konstruiert eine Instanz der Klasse. Reader(Object lock) Konstruiert eine Instanz der Klasse. Kritische Bereiche der Methoden der Klasse werden beim Aufruf von lock synchronisiert. Die folgenden Methoden sind die der Klasse Reader. Nur zwei dieser Methoden – read(char[], int, int) und close() – sind abstrakt und m.ssen durch konkrete Unterklassen angegeben werden. Alle anderen Methoden sind implementiert. int read() throws IOException Liest ein Zeichen von der Datenquelle in die unteren 16 Bits des R.ckgabewerts. Hat die Datenquelle keine weiteren Daten, wird der Integerwert -1 zur.ckgegeben. Sind im Augenblick des Aufrufs keine Daten verf.gbar, wird der aufrufende Thread blockiert (Kapitel 14: Threads und Multithreading f.r weitere Informationen .ber Threads und das Blockieren). int read(char[] dest) throws IOException Liest Zeichen von der Datenquelle in das Array dest. Kehrt zur.ck, wenn die Datenquelle keine weiteren Daten mehr hat oder das Array voll ist. Der R.ckgabewert ist gleich der Anzahl der gelesenen Zeichen oder -1, wenn keine weiteren Daten mehr verf.gbar sind. int read(char[] dest, int offset, int length) throws IOException Wie int read(char[] dest) (siehe oben), aber es wird versucht length Zeichen in den Teil des Zeichen-Arrays zu lesen, der bei offset beginnt. Liefert -1 zur.ck, wenn keine weiteren Daten mehr verf.gbar sind. 292
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die abstrakte Oberklasse
void close() throws IOException Setzt Systemressourcen frei, die einer bestimmten Datenquelle zugeordnet sind. Auf einer UNIX-Plattform zum Beispiel verbraucht der Eingabestrom einen File Descriptor. Der Aufruf close() setzt diesen wieder frei, und der Strom ist dann f.r das Lesen von Daten auf Dauer nicht mehr verf.gbar. long skip(long nchars) throws IOException Versucht nchars aus der Datenquelle zu .berspringen und zu verwerfen.
Die Klasse Writer Wie Reader hat auch die Klasse Writer einen .berladenen Konstruktor, der die Option anbietet, sich auf ein willk.rliches Objekt zu synchronisieren. Writer hat zwei Konstruktoren: Writer() Konstruiert eine Instanz der Klasse. Writer(Object lock) Konstruiert eine Instanz der Klasse. Kritische Bereiche der Klassenmethoden werden durch Aufrufen von lock synchronisiert. Im Folgenden werden die Methoden der Klasse Writer vorgestellt. Drei dieser Methoden – write(char[], int, int), flush() und close() – sind abstrakt und m.ssen durch konkrete Unterklassen angeboten werden. Alle anderen Methoden sind implementiert.
293
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
void write(int c) throws IOException Schreibt die Zeichen, die in den unteren 16 Bits des Arguments erscheinen. void write(char c) throws IOException Schreibt ein Array von Zeichen. void write(char c, int offset, int length) throws IOException Schreibt einen Teil des Zeichen-Arrays. void write(String s) throws IOException Schreibt eine Zeichenkette. void write(byte[] b, int offset, int length) throws IOException Schreibt einen Teil einer Zeichenkette. void flush() throws IOException Schreibt alle Zeichen, die ein Strom gepuffert hat. Ein einzelner Aufruf von flush() wird alle Puffer in einer Kette von Writer und OutputStream (auch mehrere!) betreffen. void close() throws IOException Setzt Systemressourcen frei, die einer bestimmten Datenquelle zugeordnet sind. Der Strom ist dann f.r das Lesen von Daten auf Dauer nicht mehr verf.gbar. Vor dem Aufruf von close() sollte flush() aufgerufen werden.
Die Stromklassen der unteren Ebenen Das Paket java.io bietet vier Klassen f.r StrGme der unteren Ebenen an, die aus Dateien lesen. Diese Klassen sind FileInputStream f.r Byte-Eingaben, FileOutputStream f.r Byte-Ausgaben, FileReader f.r Zeicheneingaben und FileWriter f.r Zeichenausgaben.
Die Klasse FileInputStream Die Klasse FileInputStream ist ein bytebasierter Eingabestrom, der aus einer Datei liest. Zus$tzlich zu den bereits in den vorangegangenen Abschnitten beschriebenen Methoden enth$lt diese Klasse drei Konstruktoren. Alle drei benGtigen Argumente, die die zu Gffnende Datei angeben: FileInputStream(String path) throws FileNotFoundException Versucht einen Strom zu der in file beschriebenen Datei zu Gffnen. Wirft eine Exception aus, wenn die Datei nicht existiert. FileInputStream(File file) throws FileNotFoundException Versucht einen Strom zu der in file beschriebenen Datei zu Gffnen. Wirft eine Exception aus, wenn die Datei nicht existiert. FileInputStream(FileDesription fdObj) ]ffnet einen Strom zu der von file beschriebenen Datei unter Verwendung des File Descriptors, der eine bereits bestehende Verbindung zu der Datei angibt. 294
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Stromklassen der unteren Ebenen
Unter Microsoft Windows kann ein Pfad zu einer Datei entweder „/“ oder „\“ als Pfadtrenner verwenden. Hinweis
Die Klasse FileOutputStream Die Klasse FileOutputStream ist ein Ausgabestrom, der in eine Datei schreibt. Diese Klasse hat vier Konstruktoren: FileOutputStream(String path) throws FileNotFoundException Versucht einen Strom zu der Datei in dem durch path beschriebenen Pfad zu Gffnen. Die Datei wird erstellt, wenn sie nicht existiert. FileOutputStream(String path, Boolean append) throws FileNotFoundException Versucht einen Strom in dem durch path beschriebenen Pfad zu Gffnen. Ist append true, wird die Datei im append-Modus geGffnet. Dies bedeutet, dass im Falle, dass die Datei existiert, alle Daten an das Ende der Datei angeh$ngt werden. Die Datei wird erstellt, wenn sie nicht existiert. FileOutputStream(File file) throws FileNotFoundException Versucht einen Strom zu der durch file beschriebenen Datei zu Gffnen. Die Datei wird erstellt, wenn sie nicht existiert. FileOutputStream(FileDescriptor fdObj) ]ffnet einen Strom zu der Datei, die durch den File Descriptor beschrieben wird, der eine bereits bestehende Verbindung zu einer Datei darstellt. In einem Applet wird der Zugriff auf eine Datei .ber den Sicherheitsmanager des Browsers erlaubt oder verboten. Wird der Zugriff verboten, werfen alle Konstruktoren eine SecurityException aus. Da die SecurityException eine Laufzeit-Exception ist, braucht sie nicht abgefangen zu werden.
Hinweis
Die Klasse FileReader Die Klasse FileReader ist ein zeichenbasierter Strom zum Lesen aus einer Datei. Sie ist eine Unterklasse der Klassen Reader und InputStreamReader (diese werden sp$ter im Abschnitt „Die Klassen InputStreamReader und OutputStreamWriter“ beschrieben) und f.gt selbst keine weiteren Methoden hinzu, mit der Ausnahme der folgenden drei Konstruktoren: FileReader(String path) throws FileNotFoundException Versucht einen Strom zu der in path beschriebenen Datei zu Gffnen. FileReader(File file) throws FileNotFoundException Versucht einen Strom zu der in file beschriebenen Datei zu Gffnen. FileReader(FileDescriptor fd) ]ffnet einen Strom zu der durch den File Descriptor beschriebenen Datei, der eine bereits bestehende Verbindung zu dieser Datei darstellt.
295
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
Die Klasse FileWriter Die Klasse FileWriter ist ein zeichenbasierter Strom zum Schreiben einer Datei. Die Klasse ist eine Unterklasse der Klassen Writer und OutputStreamWriter (die sp$ter im Abschnitt „Die Klassen InputStreamReader und OutputStreamWriter“ beschrieben werden) und f.gt keine weiteren Methoden hinzu, mit der Ausnahme der folgenden vier Konstruktoren: FileWriter(String path) throws IOException Versucht einen Strom zu der von path beschriebenen Datei zu Gffnen. FileWriter(String path, boolean append) throws IOException Versucht einen Strom in dem durch path beschriebenen Pfad zu Gffnen. Ist append true, wird die Datei im append-Modus geGffnet. Dies bedeutet, dass im Falle, dass die Datei existiert, alle Daten an das Ende der Datei angeh$ngt werden. FileWriter(File file) throws IOException Versucht einen Strom zu der durch file beschriebenen Datei zu Gffnen. FileWriter(FileDescriptor fd) throws IOException ]ffnet einen Strom zu der durch den File Descriptor beschriebenen Datei, der eine bereits bestehende Verbindung zu dieser Datei darstellt.
Andere Stromklassen der unteren Ebenen Zus$tzlich zu FileInputStream enth$lt das Paket java.io einige andere Klassen f.r EingabestrGme der unteren Ebenen: ByteArrayInputStream und PipedInputStream f.r Byte-Eingaben sowie CharArrayReader, PipedReader und StringReader f.r die Eingabe von Zeichen. Diese Klassen lesen aus anderen Ressourcen als Dateien. Ansonsten sind sie FileInputStream und StringReader sehr $hnlich – sie erben von den Klassen InputStream oder Reader und kGnnen Bytes oder Zeichen von einer Datenquelle lesen. Ein Programm kann diese Daten unmittelbar verwenden oder damit einen Eingabestrom der hGheren Ebenen f.r komplexere Verarbeitung der Daten verketten. Es gibt zus$tzlich zu FileOutputStream ein Reihe von Ausgabeklassen der unteren Ebenen: ByteArrayOutputStream und PipedOutputStream f.r Ausgaben von Bytes sowie CharArrayWriter, PipedWriter und StringWriter f.r die Ausgabe von Zeichen. Diese Klassen schreiben in Datenziele, die keine Dateien sind. Ansonsten sind sie mit FileOutputStream und Writer vergleichbar: Sie erben von den Klassen OutputStream oder Writer und sie schreiben Bytes oder Zeichen. Ein Programm kann Daten an diese Klassen senden, oder einen Ausgabestrom der hGheren Ebene mit diesen verketten, um so das Schreiben st$rker strukturierter Informationen zu ermGglichen. Die folgenden Abschnitte betrachten diese Klassen detaillierter.
296
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Andere Stromklassen der unteren Ebenen
Klassen von StrBmen, die sich mit Arrays verbinden Die Klasse ByteArrayInputStream erh$lt seine Eingaben aus einem Array von Bytes oder aus einem Teil eines solchen Arrays von Bytes. Es gibt zwei Konstruktoren: ByteArrayInputStream(byte[] buf) Konstruiert einen Eingabestrom, der Bytes aus dem Array buf liest. ByteArrayInputStream(byte[] buf, int offset, int length) Konstruiert einen Eingabestrom, der Bytes aus Teilen des Arrays buf liest. Der Array-Teil beginnt bei offset und ist length Bytes lang. Die Klasse ByteArrayOutputStream schreibt in ein Array aus Bytes. Sofern erforderlich w$chst das Array automatisch mit. Es gibt zwei Konstruktoren: ByteArrayOutputStream() Erzeugt eine neue Instanz. ByteArrayOutputStream(int size) Erzeugt eine neue Instanz mit einem initialen Ziel-Array der in size angegebenen GrGße. Wenn die Anzahl der in den Strom geschriebenen Bytes es erforderlich macht, w$chst das Array automatisch mit. Es gibt drei Methoden, mit denen der Ausgabestrom aus einem Byte-Array in besser zugreifbare Daten umgewandelt werden kann: String toString() Liefert einen String zur.ck, der alle bislang in den Strom geschriebenen Bytes enth$lt. String toString(String encoding) throws UnexpectedEncodingException Liefert einen String zur.ck, der alle bislang in den Strom geschriebenen Bytes enth$lt. Der String wird unter Verwendung des angegebenen Kodierschemas erstellt. byte[] toByteArray() Liefert einen String zur.ck, der alle bislang in den Strom geschriebenen Bytes enth$lt. Dieses Array ist eine Kopie des Inhalts des Stroms. Diese Kopie kann ver$ndert werden, ohne die Originaldaten zu besch$digen. Diese beiden Klassen (ByteArrayInputStream und ByteArrayOutputStream) haben zeichenbasierte Gegenst.cke, welche die abstrakten Oberklassen Reader und Writer erweitern. Die Klasse CharArrayReader liest Zeichen aus einem Zeichen-Array aus. Die Klasse CharArrayWriter schreibt Zeichen in ein Zeichen-Array. Es gibt die folgenden Konstruktoren f.r CharArrayReader: CharArrayReader(char[] chars) Erzeugt einen Leser f.r ein Zeichen-Array.
297
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
CharArrayReader(char[] chars, int start, int length) Erzeugt einen Leser f.r einen Teil eines Zeichen-Arrays. Das Teil-Array beginnt bei start und ist length Zeichen lang. Es gibt die folgenden Konstruktoren f.r CharArrayWriter: CharArrayWriter() Erzeugt einen Schreiber f.r ein Zeichen-Array. CharArrayWriter(int length) Erzeugt einen Schreiber f.r ein Zeichen-Array, dessen initiale GrGße length entspricht. Wirft eine IllegalArgumentException aus, wenn die initiale L$nge einen negativen Wert hat.
Klassen von StrBmen, die sich mit Strings verbinden Die Klassen StringReader und StringWriter kommunizieren mit Java-Zeichenketten und Puffern von Zeichenketten. Die Klasse StringReader liest Zeichen aus einer Zeichenkette. Die Klasse StringWriter schreibt Zeichen in einen Puffer (nicht in eine Zeichenkette!). Die Klasse StringReader hat folgenden Konstruktor: StringReader(String s) Erzeugt einen Eingabestrom, der Zeichen aus der angegebenen Zeichenkette ausliest. Die Klasse StringWriter sammelt ihre Zeichen in einem Puffer f.r Zeichenketten. Sie hat zwei Konstruktoren: StringWriter() Erzeugt einen Schreiber f.r Zeichenketten mit einem internen Puffer der voreingestellten GrGße. StringWriter(int size) Erzeugt einen Schreiber f.r Zeichenketten mit einem internen Puffer der durch size angegebenen GrGße. Version 1.1 des JDK enthielt eine Klasse StringBufferInputStream. Die Verwendung dieser Klasse wird seit Version 1.2 missbilligt. Hinweis
Klassen von StrBmen, die StrBme verbinden
298
Das Paket java.io enth$lt vier geleitete (engl. piped) Klassen, die in Paaren oder als Tandem operieren. Ein geleiteter Eingabestrom liest Bytes, die in einen korrespondierenden geleiteten Ausgabestrom geschrieben werden. Ein geleiteter Leser liest Zeichen, die in einen korrespondierenden geleiteten Schreiber geschrieben werden. Die h$ufigste Verwendung f.r diese Klassen ist die Kommunikation der Threads untereinander: Ein Thread schreibt in einen geleiteten Schreiber oder Ausgabe-
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Andere Stromklassen der unteren Ebenen
strom, w$hrend ein anderer Thread diese Daten aus dem Eingabestrom des geleiteten Lesers ausliest. F.r zeichenbasierte Kommunikation ist es in der Regel besser, ein Paar aus Reader und Writer zu verwenden, da diese Klassen f.r den Blocktransfer optimiert sind. Jede dieser Klassen hat zwei Konstruktoren. PipedInputStream hat folgende Konstruktoren: PipedInputStream() Konstruiert einen geleiteten Eingabestrom ohne Datenquelle. Der Strom ist so lange nutzlos, bis ein geleiteter Ausgabestrom assoziiert wird. Dies wird durch Aufrufen der Methode connect(PipedOutputStream) erreicht. PipedInputStream(PipedOutputStream source) throws IOException Konstruiert einen geleiteten Eingabestrom, dessen Datenquelle die geschriebenen Bytes des in source genannten Ausgabestroms sind. Daten, die in die Datenquelle geschrieben werden, stehen als Eingabe von diesem Strom zur Verf.gung. Tritt ein Ein-/Ausgabefehler auf, wird eine IOException ausgeworfen. Die Klasse PipedOutputStream hat folgende Konstruktoren: PipedOutputStream() Konstruiert einen geleiteten Ausgabestrom ohne Datenquelle. Der Strom ist so lange nutzlos, bis ein geleiteter Ausgabestrom assoziiert wird. Dies wird durch Aufrufen der Methode connect(PipedInputStream) erreicht. PipedOutputStream(PipedInputStream receiver) throws IOException Erzeugt einen geleiteten Ausgabestrom, dessen Daten in den in receiver genannten geleiteten Eingabestrom geschrieben werden. Daten-Bytes, die in diesen Strom geschrieben werden, werden als Eingabe vom receiver verf.gbar sein. Falls ein Ein-/Ausgabefehler auftritt, wird eine IOException ausgeworfen. Die folgenden Konstruktoren gibt es f.r PipedReader: PipedReader() Konstruiert einen geleiteten Leser ohne Datenquelle. Dieser Strom ist nutzlos, bis ein geleiteter Leser assoziiert wird. Dies wird durch Aufruf der Methode connect(PipedWriter) erreicht. PipedReader(PipedWriter source) throws IOException Konstruiert einen geleiteten Leser, dessen Datenquelle die in den in source bezeichneten Schreiber geschriebenen Zeichen sind. Daten, die in die Datenquelle geschrieben werden, stehen als Eingabe von diesem Strom zur Verf.gung. Falls ein Ein-/Ausgabefehler auftritt, wird eine IOException ausgeworfen. Folgende Konstruktoren gibt es f.r PipedWriter:
299
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
PipedWriter() Konstruiert einen Schreiber ohne Datenempf$nger. Der Strom ist nutzlos, bis ein geleiteter Leser assoziiert wird. Dies kann durch Aufrufen der Methode connect(PipedReader) erreicht werden. PipedWriter(PipedReader receiver) throws IOException Konstruiert einen geleiteten Schreiber, dessen Daten in einen in receiver angegebenen Leser geschrieben werden. Daten, die dann in die Datenquelle geschrieben werden, stehen als Eingabe von diesem Strom zur Verf.gung. Falls ein Ein-/Ausgabefehler auftritt, wird eine IOException ausgeworfen. Ein Strom jeden Typs kann mit einem Strom des entgegengesetzten Typs assoziiert werden, indem die Methode connect() aufgerufen wird. Es gibt zwei Wege, um ein Paar geleiteter StrGme zu erzeugen. Der erste Weg besteht darin, zun$chst einen geleiteten Eingabestrom zu erzeugen, dann einen geleiteten Ausgabestrom zu Gffnen und schließlich den geleiteten Eingabestrom mit dem geleiteten Ausgabestrom zu verbinden: PipedInputStream instream = new PipedInputStream(); PipedOutputStream outstream = new PipedOutputStream(instream); instream.connect(outstream); Der alternative Weg besteht darin, zun$chst mit dem Ausgabestrom zu beginnen, dann den Eingabestrom zu Gffnen und schließlich den geleiteten Ausgabestrom mit dem geleiteten Eingabestrom zu verbinden: PipedOutputStream outstream = new PipedOutputStream() ; PipedInputStream instream = new PipedInputStream(outstream); outstream.connect(instream); Dieses Prinzip gilt auch f.r das Erzeugen eines Paares geleiteter Leser/Schreiber. Ein mGglicher Weg besteht darin, mit dem Leser zu beginnen: PipedReader reader = new PipedReader(); PipedWriter writer = new PipedWriter(reader); instream.connect(writer); Die Alternative besteht darin, mit dem geleiteten Schreiber zu beginnen: PipedWriter writer = new PipedWriter(); PipedReader reader = new PipedReader(writer); outstream.connect(instream);
300
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klassen der High-Level-StrBme
Die Klassen der High-Level-Str1me EingabestrGme der hGheren Ebenen (so genannte High-Level-Eingabestr me) erhalten ihre Eingaben von anderen EingabestrGmen. AusgabestrGme der hGheren Ebenen (so genannte High-Level-Ausgabestr me) leiten ihre Ausgabe an andere AusgabestrGme weiter. Jede dieser Klassen wird erzeugt, indem sie als Argument an einen Konstruktor eines anderen Stroms .bergeben wird. Der neue Strom wird an den Strom des Arguments gekettet. Ein High-Level-Eingabestrom wird Daten von dem Strom des Arguments lesen, w$hrend ein High-Level-Ausgabestrom Daten in den Strom des Arguments schreiben wird. Der Strom des Arguments kann seinerseits wieder ein High-Level-Strom sein. Die folgenden Abschnitte stellen die Klassen der High-Level-Eingabe- und AusgabestrGme vor.
Die Klassen BufferedInputStream und BufferedOutputStream Die Klasse BufferedInputStream verwaltet ein internes Zeichen-Array, in dem es die Daten puffert, die von der Quelle gelesen werden. Die vorgegebene GrGße betr$gt 2.048 Bytes. Wird die Methode read() das erste Mal f.r den gepufferten Eingabestrom aufgerufen, f.llt dieser seinen Puffer mit Daten aus der eigenen Datenquelle. Darauf folgende Aufrufe von read() liefern so lange Daten aus diesem Puffer, bis er leer ist. An diesem Punkt wird der Puffer erneut aus der Datenquelle gef.llt. Ein gepufferter Eingabestrom ist in Situationen hilfreich, in denen das Lesen großer Mengen aufeinander folgender Bytes nicht sehr viel aufwendiger ist als das Lesen eines einzelnen Bytes. Es wird zum Beispiel beim Lesen aus Dateien sehr viel Zeit darauf verwendet, den Lesekopf zu positionieren und darauf zu warten, dass die richtige Stelle unter dem Schreib-/Lesekopf liegt. Diese Zeit muss in jedem Fall investiert werden, unabh$ngig von der Menge der aufeinander folgenden Bytes, die gelesen werden sollen. In diesem Fall ist es sinnvoll, zun$chst den ganzen Datenblock von der Festplatte zu lesen (bei den meisten Systemen 512 oder 1.024 Bytes) und die unerw.nschten Bytes im Puffer zwischenzuspeichern, falls diese sp$ter noch benGtigt werden. Es gibt zwei Konstruktoren f.r die Klasse BufferedInputStream. Eine Version erzeugt einen Puffer mit der voreingestellten GrGße von 2.048 Bytes, die andere Version ermGglicht es Ihnen, eine eigene GrGße anzugeben: BufferedIputStream(InputStream source) Erzeugt einen Eingabestrom mit einem Puffer von 2.048 Bytes GrGße. Der Eingabestrom verwendet source als Datenquelle.
301
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
BufferedInputStream(InputStream source, int bufsize) Erzeugt einen Eingabestrom mit einem Puffer in der GrGße von bufsize Bytes. Der Eingabestrom verwendet source als Datenquelle. Wirft eine IllegalArgumentException aus, wenn bufsize einen negativen Wert hat. Die Klasse BufferedReader ist das zeichenbasierte Squivalent des gepufferten Eingabestroms. Da diese Klasse von den bereits vorgestellten Vorteilen des Puffers profitiert, ist diese Klasse das bevorzugte Werkzeug f.r das Lesen von Eingabezeilen. Es gibt wieder zwei Konstruktoren: BufferedReader(Reader source) Erzeugt einen gepufferten Leser mit einem Puffer f.r 8.192 Zeichen. Der Leser verwendet source als Datenquelle. BufferedReader(Reader source int bufsize) Erzeugt einen gepufferten Leser mit einer von Ihnen in Zeichen anzugebenden PuffergrGße. Der Leser verwendet source als Datenquelle. Wird eine IllegalArgumentException auswerfen, wenn bufsize einen negativen Wert hat. Die Klasse BufferedOutputStream unterst.tzt ebenfalls einen Byte-Puffer. Daten, die in einen gepufferten Ausgabestrom geschrieben werden, werden im Puffer zwischengespeichert, bis der Puffer voll ist. Dann werden die Bytes in einer einzigen Operation in einen beliebigen verketteten Ausgabestrom geschrieben. Wie BufferedInputStream hat auch BufferedOutputStream zwei Konstruktoren. Eine Version stellt einen voreingestellten Puffer von 512 Bytes zur Verf.gung, die andere Version l$sst den Aufrufer einen Wert f.r die GrGße angeben: BufferedOutputStream(OutputStream dest) Erzeugt einen gepufferten Ausgabestrom mit einem voreingestellten Puffer von 512 Bytes GrGße. Der Strom schreibt Daten an den Ausgabestrom dest. BufferedOutputStream(OutputStream dest, int bufsize) Erzeugt einen gepufferten Ausgabestrom mit einer von Ihnen in Bytes anzugebenden PuffergrGße. Der Strom verwendet dest als Datenziel. Wird eine IllegalArgumentException auswerfen, wenn bufsize einen negativen Wert hat. Die Klasse BufferedWriter ist analog dazu: BufferedWriter(Writer dest) Erzeugt einen gepufferten Schreiber mit einem 512 Bytes großen Puffer. Der Schreiber schreibt seine Daten nach dest. BufferedWriter(Writer dest, int bufsize) Erzeugt einen gepufferten Schreiber mit einer PuffergrGße von bufsize Zeichen. Der Strom schreibt seine Daten nach dest und wird eine IllegalArgumentException auswerfen, wenn bufsize einen negativen Wert hat.
302
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klassen der High-Level-StrBme
Die grGßte PaketgrGße f.r Netzwerke ist 64 KB. Wird ein gepufferter Schreiber f.r Netzwerkausgaben verwendet, macht es keinen Sinn, einen Puffer mit mehr als 64 KByte zu verwenden.
Hinweis
Die Klassen DataInputStream und DataOutputStream Die Klasse DataInputStream liest Bytes von einem anderen Strom und interpretiert diese als primitive Java-Typen, Zeichen-Arrays und Zeichenketten. Es gibt keine korrespondierende Klasse eines zeichenorientierten Lesers, da es keinen Sinn macht, primitive Java-Typen als Zeichen zu schreiben. Der Konstruktor setzt voraus, dass er an einen Eingabestrom .bergeben wird: DataInputStream(InputStream source) Erzeugt einen FilterInputStream, der seine Daten aus dem in source angegebenen Eingabestrom entnimmt. Zus$tzlich zu den .blichen ererbten Methoden unterst.tzen DateneingabestrGme folgende Methoden: int read(byte[] dest) throws IOException Liest Bytes von der Datenquelle in das Ziel-Array dest. Kehrt zur.ck, wenn das Array voll ist oder von der Datenquelle keine weiteren Daten mehr geliefert werden. Der R.ckgabewert entspricht der Anzahl gelesener Bytes. Beachten Sie, dass Sie ein Byte-Array durch Aufrufen von String(byte) in eine Zeichenkette umwandeln kGnnen. int read(byte[] dest, int offset, int length) throws IOException Dies entspricht weitgehend dem zuvor beschriebenen read(byte [] dest), allerdings wird versucht, die Daten in den Teil des Arrays zu schreiben, der bei offset beginnt. Wird die Eingabe beendet, ist der R.ckgabewert -1. boolean readBoolean() throws IOException Liest einen booleschen Wert. byte readByte() throws IOException Liest ein zweierkomplement$res Byte mit Vorzeichen aus dem Eingabestrom. int readUnsignedByte() throws IOException Liest ein vorzeichenloses Byte aus dem Eingabestrom. Liefert das n$chste Byte dieses Stroms zur.ck, das als 8-Bit-Integerzahl ohne Vorzeichen interpretiert wird. short readShort() throws IOException Liest einen Typ Short als Zweierkomplement$r mit Vorzeichen aus dem Eingabestrom aus. Liefert die n$chsten Bytes dieses Stroms zur.ck, die als 16-BitIntegerzahl mit Vorzeichen interpretiert werden. int readUnsignedShort() throws IOException Liest einen Typ Short ohne Vorzeichen aus dem Eingabestrom. Liefert die n$chsten Bytes dieses Stroms zur.ck, die als 16-Bit-Integerzahl ohne Vorzeichen interpretiert werden. 303
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
char readChar() throws IOException Liest ein zwei Byte langes Unicode-Zeichen aus dem Eingabestrom. Das erste Byte wird als oberes Byte des Zeichens interpretiert. Liefert die n$chsten zwei Bytes des Eingabestroms als ein Unicode-Zeichen zur.ck. int readInt() throws IOException Liest ein Zweierkomplement eines vier Byte Java-Integers mit Vorzeichen aus dem Eingabestrom. Das erste Byte wird als oberes Byte interpretiert. Liefert die n$chsten vier Bytes des Eingabestroms zur.ck, die als Integer interpretiert werden. long readLong() throws IOException Liest ein acht Byte langes Zweierkomplement vom Java-Typen long mit Vorzeichen aus dem Eingabestrom. Das erste Byte wird als oberes Byte interpretiert. Liefert die n$chsten acht Bytes des Eingabestroms zur.ck, die als Integer interpretiert werden. float readFloat() throws IOException Liest eine 4-Byte-Repr$sentation des Java-Typs Float aus dem Eingabestrom. double readDouble() throws IOException Liest eine 8-Byte-Repr$sentation des Java-Typs Double aus dem Eingabestrom. String readUTF() throws IOException Liest eine Serie von Bytes aus dem Eingabestrom und interpretiert diese als eine Java-modifizierte Universal Text Format (UTF-8)-Zeichenkette.
Hinweis
UTF ist ein wachsender internationaler Standard, der ein, zwei oder drei Bytes zur Repr$sentation jedes Zeichens verwendet. Es gibt keine Terminierung von Strings, da ein UTF-String bereits L$ngeninformationen enth$lt. static String readUTF(DataInput din) throws IOException Eine statische Methode. Liest eine UTF-Zeichenkette aus dem angegebenen Dateneingabestrom und interpretiert diese als Java-modifizierte Universal Text Format (UTF-8)-Zeichenkette. void readFully(byte[] dest) throws IOException Versucht das Byte-Array dest mit Bytes aus der Datenquelle zu f.llen. Der ausf.hrende Thread blockiert, wenn nicht gen.gend Bytes verf.gbar sind. Ist die Datenquelle erschGpft, bevor der Puffer dest gef.llt ist, wird eine EOFException ausgeworfen. Ansonsten wird eine IOException ausgeworfen. void readFully(byte [] dest, int offset, int length) throws IOException Wie das zuvor gezeigte readFully(), jedoch wird nur ein Teil des Puffers dest gef.llt. Das Teil-Array beginnt bei offset und ist length Bytes lang. void skipBytes(int offset) throws IOException Wie das oben vorgestellte readFully(), jedoch wird eine durch offset spezifizierte Menge Bytes verworfen, statt diese in einem Array zu speichern. Der ausf.hrende Thread blockiert, wenn nicht gen.gend Bytes verf.gbar sind.
304
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klassen der High-Level-StrBme
Die Klasse DataOutputStream unterst.tzt das Schreiben primitiver Java-Typen in einen Ausgabestrom. Zeichenketten und Byte-Arrays kGnnen ebenfalls geschrieben werden. Es gibt keine analoge zeichenorientierte Schreiberklasse. Sowohl DataOutputStream als auch DataInputStream kommunizieren auf eine von der verwendeten Plattform unabh$ngige Art. Der Konstruktor erwartet als Argument, an einen Ausgabestrom .bergeben zu werden: DataOutputStream(OutputStream dest) Erzeugt einen neuen Datenausgabestrom, der seine Daten in den angegebenen Ausgabestrom dest schreibt. Die in einen Datenausgabestrom geschriebenen Daten werden in die einzelnen Bytes aufgebrochen, die in den jeweils verketteten Ausgabestrom geschrieben werden. Zus$tzlich zu den verschiedenen byteschreibenden Methoden, die von der DataOutput-Oberklasse geerbt werden, unterst.tzt die Klasse DataOutputStream folgende Schreibmethoden: void writeBoolean(boolean b) throws IOException Schreibt einen booleschen Wert in den Ausgabestrom. Ein true-Wert wird durch (Byte) 0 (Null) repr$sentiert, ein false-Wert durch (Byte) 1. void writeByte(int i) throws IOException Schreibt das niederwertigste Byte von i in den Ausgabestrom. Wird keine Exception ausgeworfen, wird der Z$hler um 1 erhGht. void writeChar(int i) throws IOException Schreibt ein Zeichen als Wert von zwei Bytes in den Ausgabestrom. Wird keine Exception ausgeworfen, wird der Z$hler um 2 erhGht. void writeShort(int i) throws IOException Schreibt die beiden niederwertigsten Bytes von i in den Ausgabestrom. Von den beiden geschriebenen Bytes ist das erste das obere Byte (Bits 0 bis 7). Wird keine Exception ausgeworfen, wird der Z$hler um 2 erhGht. void writeInt(int i) throws IOException Schreibt alle vier Bytes der angegebenen Integerzahl i in den Ausgabestrom, wobei mit dem obersten Byte (Bits 24 bis 31) begonnen wird. Wird keine Exception ausgeworfen, wird der Z$hler um 4 erhGht. void writeLong(long theLong) throws IOException Schreibt alle vier Bytes der angegebenen Long-Zahl i in den Ausgabestrom, wobei mit dem obersten Byte (Bits 56 bis 63) begonnen wird. Wird keine Exception ausgeworfen, wird der Z$hler um 8 erhGht. void writeFloat(float f) throws IOException Schreibt die 4-Byte-Repr$sentation von f in den Ausgabestrom. Wird keine Exception ausgeworfen, wird der Z$hler um 4 erhGht. void writeDouble(double d) throws IOException Schreibt die 8-Byte-Repr$sentation von f in den Ausgabestrom. Wird keine Exception ausgeworfen, wird der Z$hler um 8 erhGht. 305
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
void writeBytes(String s) throws IOException Schreibt s als Serie von Bytes. Nur jeweils das niederwertigste Byte jedes zweiten Bytes des Unicode-Zeichens wird geschrieben. Die oberen acht Bits werden verworfen. Wird keine Exception ausgeworfen, wird der Z$hler um die L$nge von s erhGht. void writeChars(String s) throws IOException Nur jeweils das obere Byte jedes zweiten Bytes des Unicode-Zeichens wird geschrieben. Die unteren acht Bits werden verworfen. Wird keine Exception ausgeworfen, wird der Z$hler um die doppelte L$nge von s erhGht. void writeUTF(String s) throws IOException Schreibt den String s als Zeichenkette im modifizierten Java-Universal Text Format (UTF-8) in den Ausgabestrom. Wird keine Exception ausgeworfen, wird der Z$hler um die Anzahl der in den Ausgabestrom geschriebenen Bytes erhGht. void write(int b) throws IOException Schreibt das Byte, das in den unteren Bits des Arguments erscheint. Die .brigen 24 Bits werden verworfen. Wird keine Exception ausgeworfen, wird der Z$hler um 1 erhGht. void write(byte b[], int offset, int length) throws IOException Schreibt den Teil eines Arrays, der length Bytes lang ist und bei offset beginnt. void flush() throws IOException Schreibt alle Bytes, die der Strom noch gepuffert hat. int size() Liefert den aktuellen Wert des Z$hlers zur.ck, der die Anzahl der bislang geschriebenen Bytes spezifiziert.
Die Klasse LineNumberReader Die Klasse LineNumberReader verwaltet das interne Z$hlen der von ihr bisher gelesenen Zeilen. Eine Zeile ist jede Anzahl Bytes, die von einem Return (‚\r‘) oder einem Newline (‚\n‘) oder einem Return, dem unmittelbar ein Newline folgt, terminiert wird. Diese Klasse ist eine Unterklasse von BufferedReader. Die Konstruktoren, die als Argumente an einen Leser .bergeben werden m.ssen: LineNumberReader(Reader source) Erzeugt einen line-number-Reader, der seine Daten aus der angegebenen Quelle des Lesers bezieht. LineNumberReader(Reader source, int size) Erzeugt einen line-number-Reader, der seine Daten aus der angegebenen Quelle des Lesers bezieht. Die GrGße des initialen Puffers wird durch size angegeben.
306
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klassen der High-Level-StrBme
Diese Klasse f.hrt folgende neue Methoden ein und erbt die bereits in java.io beschriebenen Klassen: read(), read(char[] buf, int offset, int length), mark(int readlimit), reset() und skip(long nchar): int getLineNumber() Liefert die aktuelle Zeilennummer zur.ck. void setLineNumber(int newvalue) Setzt die aktuelle Zeilennummer auf den durch newvalue angegebenen Wert. public String readLine() throws IOException Liefert die n$chste Zeile als Eingabe zur.ck, die keine Terminierungszeichen enth$lt, oder Null, wenn das Ende der Eingabe erreicht wurde. Version 1.0 des JDK bot eine Klasse LineNumberInputStreaman. Die Verwendung dieser Klasse wird ab Version 1.1 missbilligt. Hinweis
Die Klassen PrintStream und PrintWriter Die Klassen PrintStream und PrintWriter enthalten Methoden, die das Ausdrucken von Text unterst.tzen. Diese Unterst.tzung besteht in automatischer Wandlung des Datentyps und automatischem Leeren der Puffer (flushing). Die Klasse PrintStream hat zwei Konstruktoren: PrintStream(OutputStream dest) Konstruiert einen Druckstrom und verkettet dessen Ausgabe an dest. Automatisches Flushing wird in dieser Klasse nicht unterst.tzt. PrintStream(OutputStream dest, boolean autoFlush) Konstruiert einen Druckstrom und verkettet dessen Ausgabe an dest. Ist der boolesche Wert true, wird automatisches Flushing auf den verwendeten Puffer angewendet, sobald ein Array geschrieben wurde. Die Klasse PrintStream enth$lt eine Vielzahl von Methoden f.r das Wandeln und Schreiben verschiedener Datentypen. F.r jeden Datentyp gibt es eine Methode print(), welche die Daten als Zeichenkette schreibt, und eine Methode println(), welche die Daten als Zeichenkette schreibt und daran ein Newline-Zeichen anh$ngt. Die unterst.tzten Methoden werden in der folgenden Tabelle aufgelistet:
Die Methoden von PrintStream
Methode
Beschreibung
void print(char c)
Gibt ein Zeichen aus.
void println(char c)
Gibt ein Zeichen gefolgt von Newline aus.
void print(int i)
Gibt ein int aus.
307
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
Die Methoden von PrintStream (Forts.)
Methode
Beschreibung
void println(int i)
Gibt ein intgefolgt von Newline aus.
void print(long ln)
Gibt ein long aus.
void println(long ln)
Gibt ein long gefolgt von Newline aus.
void print(float f)
Gibt ein float aus.
void println(float f)
Gibt ein float gefolgt von Newline aus.
void print(double d)
Gibt ein double aus.
void println(double d)
Gibt ein double gefolgt von Newline aus.
void print(boolean b)
Gibt einen boolean aus. Ist der Wert true, wird die Zeichenkette true, andernfalls die Zeichenkette false ausgegeben.
void println(boolean b)
Gibt einen boolean gefolgt von einem Newline aus.
void print(char[] c)
Gibt ein Zeichen-Array aus.
void println(char[] c)
Gibt ein Zeichen-Array gefolgt von Newline aus.
void print(String s)
Gibt eine Zeichenkette aus.
void println(String s)
Gibt eine Zeichenkette gefolgt von Newline aus.
void print(Object ob)
Gibt ein Objekt aus. Die Zeichenkette ist das Resultat aus dem Aufruf von ob.toString().
void println(Object ob)
Gibt ein Zeichen gefolgt von Newline aus.
void println()
Terminiert die aktuelle Zeile durch Schreiben des Zeilentrenners.
boolean checkError()
Die Puffer des Stroms werden entleert und der Fehlerstatus .berpr.ft.
void setError()
Setzt den Fehlerstatus des Stroms auf true. Tabelle 11.1
Hinweis
PrintStream sollte nur in Programmen verwendet werden, die auf JDK-Versionen $lter als Version 1.1 basieren. Seit JDK 1.1 wurde die gesamte Klasse zeitweise missbilligt und rehabilitiert. PrintWriter ist die bessere Wahl. Die einzige Ausnahme ist das ph$nomenal n.tzliche System.out, das ein PrintStream ist. Die Klasse PrintWriter hat vier Konstruktoren:
308
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klassen der High-Level-StrBme
PrintWriter(Writer dest) Konstruiert einen Schreiber vom Typ print und kettet dessen Ausgabe an dest. Automatisches Flushing wird nicht unterst.tzt. PrintWriter(Writer dest, boolean autoflush) Konstruiert einen Schreiber vom Typ print und kettet dessen Ausgabe an dest. Der Wert von autoflush bestimmt, ob automatisches Flushing unterst.tzt wird. Ist dieser Wert true, werden die Puffer des Stroms entleert, sobald ein Newline geschrieben wird. PrintWriter(OutputStream out) Konstruiert einen Schreiber vom Typ print und kettet dessen Ausgabe an dest. Ausgangspunkt ist ein existierender Ausgabestrom, der in out angegeben wird. Autoflushing wird nicht unterst.tzt. Dieser Konstruktor erzeugt den erforderlichen dazugehGrenden OutputStreamWriter, der Zeichen in Bytes wandelt und dabei die vorgegebene Zeichenkodierung verwendet. PrintWriter(OutputStream out, boolean autoflush) Konstruiert einen Schreiber vom Typ print, der automatisches Flushing unterst.tzt. Die Ausgabe wird an einen existierenden Ausgabestrom gekettet, der in out spezifiziert wird. Die Namen dieser Schreibermethoden f.r diese Klassen passen pr$zise auf die Klasse PrintStream (siehe Tabelle 11.1) der einzige funktionale Unterschied besteht darin, dass PrintWriter Zeichen anstelle von Bytes schreibt. Nichts in PrintWriter oder PrintStream macht es notwendig, dass die Ausgabe an einen Drucker gesandt wird. PrintWriter kann zum Beispiel an einen StringWriter gekettet werden (der in einen StringBuffer und nicht in einen String schreibt!). Ist die Ausgabe an PrintWriter abgeschlossen, kann der StringBuffer in eine Zeichenkette umgewandelt und in eine Textbereichkomponente geschrieben werden.
Die Pushback-Klassen Die Klassen PushbackInputStream und PushbackReader ermGglichen es, dass Daten ungelesen (engl. unread) oder in die Quelle zur.ckgestoßen (engl. pushback) werden. Die Klassen verwalten interne Stacks f.r zur.ckgestoßene Bytes und Zeichen. Leseoperationen holen Daten vom Stack, bis dieser leer ist. Erst dann wird erneut auf die Datenquelle zugegriffen. Folgende Konstruktoren existieren f.r PushbackInputStream: PushbackInputStream(InputStream source) Erzeugt einen Eingabestrom, der mit der Quelle source verbunden ist. Der Puffer des Stroms fasst ein einzelnes Byte. PushbackInputStream(InputStream source, int bufsize) Erzeugt einen Eingabestrom, der mit der Quelle source verbunden ist. Der Puffer des Stroms fasst bufsize Bytes. Dieser Konstruktor wird nicht von JDK vor Version 1.1 unterst.tzt. Eine IllegalArgumentException wird ausgeworfen, wenn bufsize kleiner oder gleich 0 (Null) ist.
309
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
Folgende Konstruktoren gibt es f.r PushbackReader: PushbackReader(Reader source) Erzeugt einen Strom, der mit der Quelle source verbunden ist. Der Puffer des Stroms fasst ein einzelnes Byte. PushbackReader(Reader source, int bufsize) Erzeugt einen Strom, der mit der Quelle source verbunden ist. Der Puffer des Stroms fasst bufsize Bytes. Dieser Konstruktor wird nicht von JDK vor Version 1.1 unterst.tzt. Eine IllegalArgumentException wird ausgeworfen, wenn bufsize kleiner oder gleich 0 (Null) ist. Pushback-EingabestrGme unterst.tzen folgende Methoden f.r das Zur.ckstoßen: void unread(int ch) throws IOException Speichert das niederwertige Byte von ch in einem internen Puffer. Die n$chste Leseoperation aus dem Strom liefert ch als Byte zur.ck. void unread(byte bytes[]) throws IOException Widerruft das Lesen aller Bytes im Array bytes[]. Wird vor JDK-Version 1.1 nicht unterst.tzt. void unread(byte bytes[], int start, int length) throws IOException Macht die Bytes eines Teils eines Arrays bytes[] ungelesen. Der Teil des Arrays ist length Bytes lang und beginnt bei start. Wird vor JDK Version 1.1 nicht unterst.tzt. Die pushback-Leser haben folgende Methoden: void unread(int ch) throws IOException StGßt die unteren beiden Bytes von ch zur.ck. void unread(chars chars[]) throws IOException Widerruft das Lesen aller Bytes im Array chars[]. void unread(chars chars[], int start, int length) throws IOException Macht die Bytes eines Teils eines Arrays chars[] ungelesen. Der Teil des Arrays ist length Bytes lang und beginnt bei start. Alle Methoden von pushback f.r beide Klassen werfen eine IOException aus, wenn ein interner pushback-Puffer den f.r eine Operation erforderlichen Platz hat. Dies kann passieren, wenn die Operation mehr Daten bewegt, als der Kapazit$t des Puffers entspricht, oder wenn nicht gen.gend Leseoperationen seit dem letzten Zur.ckstoßen durchgef.hrt wurden und so kein ausreichender Platz im Puffer geschaffen wurde. Zus$tzlich zu den oben gezeigten Methoden unterst.tzen die Klassen ererbte Methoden, die bereits vorher vorgestellt wurden: available(), close(), markSupported(), read(), read(byte[] b, int offset, int length) und skip(long nbytes).
310
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klassen der High-Level-StrBme
Felder in nicht unterteilten Eingaben finden Die pushback-Eingabeklassen sind n.tzlich, wenn Felder in nicht unterteilten (engl. nondelimited) Eingaben gefunden werden sollen. Angenommen sei das Problem, dass ein Feld in unterteilter Eingabe gefunden werden soll. Angenommen, ein Eingabestrom besteht aus verschiedenen nicht-nummerischen Daten, gefolgt von jeweils einem einzelnen „/“-Zeichen, gefolgt von einer Zahl. Weiterhin sei angenommen, dass der Strom im Augenblick irgendwo innerhalb der nicht-nummerischen Daten ist und es sei notwendig, jetzt zum nummerischen Feld zu springen. Da die Eingabe unterteilt ist (die einzelnen Felder sind begrenzt), ist dies relativ einfach, wie vom folgenden Codefragment gezeigt wird. Dieses Codefragment setzt voraus, dass der Eingabestrom inreader heißt. // Angenommen, inreader ist irgendwo im // nicht-nummerischen Feld. int intchar; while ((intchar=inreader.read()) != -1) { if (intchar == '/') break; } // Jetzt ist inreader am ersten Zeichen nach '/' // positioniert. Leider sind nicht alle Eingaben unterteilt. Sollten die Eingabedaten das „/“-Zeichen aus dem vorangegangenen Beispiel nicht haben, ist es naheliegend, zun$chst eine Variation des folgenden Beispiels auszuprobieren: int intchar; while ((intchar=inreader.read()) != -1) { if (Character.isDigit((char)intchar)) break; } Dieser Code funktioniert nicht, weil in dem Augenblick, in dem die Schleife verlassen wird, das erste Zeichen des nummerischen Feldes bereits gelesen ist und der Eingabestrom nun auf dem zweiten Zeichen positioniert ist. Der Code muss das Lesen des ersten Zeichens widerrufen und der Lesestrom wieder auf dem ersten Zeichen positioniert werden. Das Lesen wird durch das Ketten eines pushback-Eingabestroms an inreader widerrufen. int intchar; PushbackInputStream pbis = new PushbackInputStream(inreader); while ((intchar=pbis.read()) != -1) { if (Character.isDigit((char)intchar)) { pbis.unread(intchar); 311
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
break; } } Die F$higkeit, einzelne Bytes zur.ck in die Datenquelle stoßen zu kGnnen, erscheint nicht sehr beeindruckend, aber sie ermGglicht einen einfachen Weg, auf dem durch strukturierte, nicht unterteilte Eingaben gesucht werden kann.
Die Klasse SequenceInputStream Die Klasse SequenceInputStream ist ein Mechanismus, mit dem zwei oder mehr EingabestrGme kombiniert werden kGnnen. Es gibt keine korrespondierende zeichenbasierte Leser-Klasse. Ein Sequenzeingabestrom liest aus seinem ersten Eingabestrom, bis dieser erschGpft ist. Dann wird aus dem n$chsten Strom gelesen, bis dieser erschGpft ist. Dies wird so lange wiederholt, bis alle StrGme ausgelesen worden sind. Erst dann wird ein Lesen des Eingabestroms -1 zur.ckliefern. Die Klasse hat zwei Konstruktoren: SequenceInputStream(InputStream s1, InputStream s2) Erzeugt eine Sequenz von EingabestrGmen aus s1 und s2, die in der Reihenfolge „erst s1, dann s2“ ausgelesen werden. SequenceInputStream(Enumeration enum) Erzeugt eine Sequenz von EingabestrGmen aus der durch enum definierten Liste von EingabestrGmen. Diese EingabestrGme, die durch Nummerierung erzeugt werden, werden der Reihe nach aus dem SequenceInputStream ausgelesen. Nachdem ein Strom ausgelesen wurde, wird dieser durch Aufrufen der entsprechenden Methode close() geschlossen. Der zweite Konstruktor benGtigt eine Nummerierung durch ein Objekt, das die Schnittstelle Enumeration implementiert. Der einfachste Weg, eine Nummerierung zu erzeugen, besteht darin, alle StrGme einem Vektoren hinzuzuf.gen und den Vektoren dann eine Nummerierung seiner Elemente zur.ckgeben zu lassen. Der folgende Code konstruiert zum Beispiel eine Sequenz, die aus den EingabestrGmen s1, s2, s3, s4 und s5 besteht: Vector vec = new Vector(); vec.addElement(s1); vec.addElement(s2); vec.addElement(s3); vec.addElement(s4); vec.addElement(s5); SequenceInputStream sis = new SequenceInputStream(vec.elements());
312
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klassen der High-Level-StrBme
Die Klassen InputStreamReader und OutputStreamWriter Die Klasse InputStreamReader liest Bytes aus einem Eingabestrom und wandelt diese in Zeichen um, wenn die Klasse an eine Unterklasse von InputStream gekettet wird. Die Klasse OutputStreamWriter liest Zeichen aus einem Eingabestrom und wandelt diese in Bytes um, wenn die Klasse an eine Unterklasse von OutputStream gekettet wird. Diese Umwandlung wird in der folgenden Abbildung illustriert.
Abb. 11.5 Umwandeln mit InputStreamReader und OutputStreamWriter
Beide Klassen m.ssen wissen, wie zwischen 16-Bit-Zeichen und 8-Bit-Bytes umgewandelt wird. Vorgaben f.r die Wandlung werden durch die Namen der Zeichenketten definiert. Jede der Klassen kann mit einem optionalen Argument konstruiert werden, mit dem von der Vorgabe abweichende Wandlungsvorschriften spezifiziert werden. Wenn Sie die Dokumentation zu JDK 1.3 besitzen, kGnnen Sie eine Liste der Kodierungen in docs/guide/intl/encoding.doc.html finden. Tipp Folgende Konstruktoren gibt es f.r InputStreamReader: InputStreamReader(InputStream source) Konstruiert einen Leser f.r einen Eingabestrom, dessen Datenquelle source ist. Beachten Sie, dass der empfohlene Weg, von der Standardeingabe zu lesen, darin besteht, diesen Konstruktor zu verwenden und damit einen InputStreamReader zu erzeugen, der von System.in liest.
313
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
InputStreamReader(InputStream source, String encodingName) throws UnsupportedEncodingException Konstruiert einen Eingabestrom, dessen Datenquelle source ist. Bytes werden entsprechend des durch encodingName vorgegebenen Schemas umgewandelt. Die Konstruktoren f.r OutputStreamWriter bieten dieselben Kombinationen von Optionen an: OutputStreamWriter(OutputStream out) Konstruiert einen Schreiber f.r einen Ausgabestrom, der mit out verbunden ist. OutputStreamWriter(OutputStream out, String encodingName) throws UnsupportedEncodingException Konstruiert einen Schreiber eines Ausgabestroms, der mit out verbunden ist. Bytes werden entsprechend des durch encodingName vorgegebenen Schemas umgewandelt.
Die Klassen der Nicht-Str1me Das Paket java.io enth$lt einige Klassen, die keine StrGme sind. Diese enthalten File, FileDescriptor, RandomAccessFile und StreamTokenizer. File und FileDescriptor sind problemlos in der Anwendung und werden ausreichend in der Suns Dokumentation der API beschrieben. Die n$chsten beiden Abschnitte befassen sich mit den Klassen RandomAccessFile und StreamTokenizer.
Die Klasse RandomAccessFile Die Klasse RandomAccessFile unterst.tzt das Lesen und Schreiben von Dateien und die Positionierung des FilePointers. Da die Klasse eine Datei nicht als sortierte Sequenz reiner Eingabe oder reiner Ausgabe behandelt, operiert sie außerhalb des Modells der StrGme. Es gibt zwei Konstruktoren f.r RandomAccessFile: RandomAccessFile(String path, String permissions) throws FileNotFoundException Erzeugt eine random-access-Datei, die mit der Datei path verbunden ist. Ein neues FileDescriptor Objekt wird erzeugt, um die Verbindung zu dieser Datei anzuzeigen. Die Zugriffsrechte werden durch die Zeichenkette permissions beschrieben, die entweder „r“ (f.r „nur lesen“) oder „rw“ (f.r „lesen und schreiben“) lauten muss.
314
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klassen der Nicht-StrBme
RandomAccessFile(File file, String permissions) throws IOException Wie oben, aber der Dateipfad wird aus einer Instanz der Klasse File gebildet und ein neues Objekt FileDescriptor wird erzeugt, das diese Verbindung zur Datei repr$sentiert. Die Zugriffsrechte werden durch die Zeichenkette permissions beschrieben, die entweder „r“ (f.r „nur lesen“) oder „rw“ (f.r „lesen und schreiben“) lauten muss. Random-access-Dateien unterst.tzen eine große Vielfalt an Methoden f.r das Lesen und Schreiben unterschiedlicher Datentypen. Diese Methoden haben dieselben Namen wie die unter DataInputStream und DataOutputStream beschriebenen Klassen, die bereits vorher erkl$rt wurden. UNIX-Programmierer, die bereits mit der Standard-Ein-/Ausgabebibliothek vertraut sind, sind auch schon damit vertraut, einen FilePointer relativ zum Anfang oder Ende der Datei oder relativ zur gegenw$rtigen Position des FilePointers zu positionieren. Die Klasse RandomAccessFile bietet eine einzige seek()-Methode an, die in Bezug auf den Anfang und das Ende der Datei positioniert. Um die anderen beiden Methoden des Suchens benutzen zu kGnnen, muss die L$nge der Datei oder die aktuelle Position ermittelt werden sowie eine explizite Subtraktion durchgef.hrt werden. Die folgenden Methoden unterst.tzen Positionierungen: void seek(long newPosition) Setzt die Position des Pointers auf newPosition. long length() Liefert die aktuelle L$nge der Datei in Bytes zur.ck. Long getFilePointer() Liefert die aktuelle Position des Pointers zur.ck. FileDescriptor getFD() Liefert das undurchsichtige Objekt des File Descriptors zur.ck, der mit diesem Strom assoziiert ist.
Die Klasse StreamTokenizer Die Klasse StreamTokenizer ist ein Parser, der n.tzlich f.r das Analysieren von Eingaben ist, deren Format Java-, C- oder C++-Quellcode entspricht. Der erste Schritt f.r das Verwenden eines StreamTokenizers besteht darin, diesen zu konstruieren: StreamTokenizer(Reader reader) Konstruiert einen StreamTokenizer, der seine Eingaben aus reader entnimmt. In der Version 1.1 des JDK bestand der bevorzugte Weg, einen Eingabestrom in Tokens zu wandeln, darin, diesen mit StreamTokenizer(InputStream is) in einen Zeichenstrom zu wandeln. Das Verwenden dieser Klasse ist seit JDK 1.2 missbilligt.
Hinweis 315
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
Der n$chste Schritt besteht darin, die Eingabe zu parsen. Das Parsen wird normalerweise in einer while-Schleife durchgef.hrt, in der jeweils die Methode nextToken() aufgerufen wird, bis das Ende der Eingabe erreicht ist. Die Methode nextToken() liefert eine Integerzahl zur.ck, die den Typ des n$chsten Tokens beschreibt. Es gibt vier mGgliche R.ckgabewerte f.r nextToken(): StreamTokenizer.TT_NUMBER Zeigt an, dass das gerade gelesene Token eine Zahl war. Der Wert der Zahl kann aus der Instanzvariablen nval ausgelesen werden, die vom Typ String ist. StreamTokenizer.TT_WORD Zeigt an, dass das gerade gelesene Token ein nicht-nummerisches Wort (zum Beispiel ein Identifier) war. Das Wort kann aus der Instanzvariablen nval ausgelesen werden, die vom Typ String ist. StreamTokenizer.TT_EOL Zeigt an, dass das gerade gelesene Zeichen ein end-of-line-Zeichen war. StreamTokenizer.TT_EOF Zeigt an, dass das Ende des Eingabestroms erreicht wurde. Ein Tokenizer eines Stroms kann dahingehend angepasst werden, dass f.r den Aufrufer spezifische Zeichen, wie zum Beispiel Leerzeichen, Kommandobegrenzer, Begrenzer von Zeichenketten und andere formatspezifische Werte erkannt werden. Die entsprechenden Methoden kGnnen Sie der folgenden Tabelle entnehmen.
Methoden von StreamTokenizer
316
Methode
Beschreibung
void commentChar (int comment)
Spezifiziert, dass comment als erstes Zeichen eines einzeiligen Kommentars verwendet wird.
void quoteChar (int quote)
Spezifiziert, dass quote verwendet wird, um Anfang und Ende von Stringkonstanten zu kennzeichnen.
void whitespaceChars (in low, int high)
Spezifiziert, dass alle Zeichen im Bereich zwischen low und high (inklusive) als Leerzeichen interpretiert werden sollen
void wordChars (int low, int high)
Spezifiziert, dass alle Eingabezeichen im Bereich zwischen low und high (inklusive) als Zeichen eines Wortes interpretiert werden sollen.
void ordinaryChar (int ord)
Spezifiziert, dass ord ein normales Zeichen ohne spezielle Bedeutung ist. Dies deaktiviert alle eventuell vergebenen besonderen Bedeutungen f.r dieses Zeichen.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klassen der Nicht-StrBme
Methoden von StreamTokenizer (Forts.)
Methode
Beschreibung
void ordinaryChars (int low, int high)
Spezifiziert, dass alle Zeichen zwischen low und high (inklusive) als normale Zeichen ohne besondere Bedeutung interpretiert werden sollen.
void eolIsSignificant (boolean flag)
Ist flag true, veranlasst dies den Parser, Zeilenendezeichen als Tokens zu interpretieren. Ist flag false, werden Zeilenendezeichen nicht erkannt.
void slashstarComments (bollean flag)
Ist flag true, wird der Parser Kommentare im C-Stil erkennen und diese nicht interpretieren. Ist flag false, werden Kommentare im C-Stil nicht erkannt. Kommentare im C-Stil beginnen mit „/*“ und enden mit „*/“. Solche Kommentare kGnnen mehrzeilig sein.
void slashslash-Comments (boolean flag)
Ist flag true, wird der Parser Kommentare im C++-Stil erkennen und diese nicht interpretieren. Ist flag false, werden Kommentare im C++-Stil nicht erkannt. Kommentare im C++-Stil beginnen mit „//“ und enden mit „//“. Solche Kommentare kGnnen mehrzeilig sein.
void resetSyntax()
Setzt die Syntax auf die urspr.nglichen Einstellungen zur.ck.
int lineno()
Liefert die aktuelle Zeilennummer zur.ck.
void lowerCaseMode (boolean flag)
Bestimmt, ob Tokens aus WGrtern automatisch in Kleinbuchstaben gewandelt werden.
int nextToken()
Parst das n$chste Token des Eingabestroms. Die R.ckgabewerte wurden weiter oben vorgestellt.
string toString()
Liefert die Repr$sentation des aktuellen Tokens des Stroms als Zeichenkette zur.ck. Tabelle 11.2
Beachten Sie, dass die Werte der Anpassungen der Methoden von StreamTokenizer wie zum Beispiel comment, quote, low und high, immer Integerzahlen sind. Allerdings sollten diese Werte als Zeichen repr$sentiert werden. Mit den in der vorangegangenen Tabelle gezeigten Methoden kann die Klasse StreamTokenizer als flexibler und einigermaßen m$chtiger Parser f.r einen Eingabestrom verwendet werden, dessen Format an Java-, C- oder C++-Quellcode erinnert. Parsing noch generischerer Formate w.rde eine lexikalische Analyse erfordern, wie diese zum Beispiel in den UNIX-Programmen lex und yacc zu finden sind.
Hinweis
317
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Dateiein-/-ausgabe und StrBme
Ausblick In Kapitel 12: Benutzerdefinierte Komponenten wird .ber die Verwendung der .blichen Java-Komponenten hinausgehen und das Entwerfen und Erzeugen eigener Komponenten diskutieren.
318
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Das Modell der Ereignisdelegation
320
Strategien zum Entwickeln von benutzerdefinierten Komponenten
324
Unterklassen von Component: die Komponente Polar
327
Gruppierung: die Komponente ThreeWay
345
Unterklassen von Standardkomponenten: Textfelder mit Gltigkeitsprfung
364
Ausblick
374
12
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Aus „Java 2 Developer’s Handbook“ von Philip Heller und Simon Roberts, ISBN 0-7821-2179-9, 1248 Seiten, $59,99
Das Java-AWT (Abstract Windowing Toolkit) stellt einen kleinen, aber ausreichenden Satz von Komponenten f.r Benutzerschnittstellen zur Verf.gung. Durch die in der Version 2 eingef.hrten JFC (Java Foundation Classes) wurde das Toolkit wesentlich erweitert (die neuen JFC-Komponenten werden in Kapitel 13: JFC-Swing-Komponenten behandelt). Es kGnnte aber sein, dass die Standardkomponenten nicht die Funktionalit$t bieten, die Sie f.r bestimmte Anwendungen benGtigen. F.r solche F$lle m.ssen Sie eigene Komponenten entwickeln. Ein Hauptaspekt bei der Entwicklung von benutzerdefinierten Komponenten ist die Ereignisbehandlung. Dieses Kapitel beginnt mit einem kurzen
Das Modell der Ereignisdelegation Das Modell der Ereignisdelegation basiert auf dem Konzept der EreignisempfGnger (Event Listener). Als Ereignisempf$nger werden Objekte bezeichnet, die benachrichtigt werden, sobald ein GUI-Ereignis stattfindet. Es gibt zehn Kategorien von GUIEreignissen, die jeweils von einer eigenen Klasse repr$sentiert werden. Die Ereignistypen entsprechen nahezu eins-zu-eins den Ereignisempf$ngertypen. Diese Entsprechung gilt nicht f.r Mausereignisse (siehe weiter unten in diesem Kapitel). Die folgende Abbildung zeigt die Hierarchie der Ereignisklassen.
Abb. 12.1 Die Hierarchie der Ereignisklassen
320
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Modell der Ereignisdelegation
An der Spitze der Hierarchie steht die Klasse EventObject. EventObject klingt vielleicht etwas unbeholfen, aber der Name Event wurde bereits f.r die Klasse java. awt.Eventvergeben, die nun veraltet ist. Die Oberklasse EventObject soll allen erdenklichen Ereignistypen .bergeordnet sein. AWT-Ereignisse (Ereignisse, die von den AWT-Komponenten ausgelGst werden) sind nur eine Untermenge aller mGglichen Ereignistypen; AWT-Ereignisse werden durch Unterklassen von java.awt.AWTEvent repr$sentiert. Zu den weiteren Ereignisfamilien z$hlen Bean-Ereignisse und anwendungsspezifische Ereignisse. Die Unterklassen von java.awt.AWTEvent gehGren zum java.awt.Event-Package. Dieses Kapitel bezieht sich auf das Modell der Ereignisdelegation, das mit der JavaVersion 1.1 eingef.hrt wurde. Das Verbreitungsmodell der Version 1.0 ist bereits veraltet.
Hinweis
Alle Ereignisunterklassen erben von EventObject die Methode getSource(). Diese Methode gibt das Objekt zur.ck, das das Ereignis erzeugt hat. Bei den AWTEventUnterklassen liefert getSource() die Komponente, in der die Benutzereingabe stattfand.
Schnittstellen und Methoden von Empf5ngern Wenn ein Benutzer in eine GUI-Komponente .ber die Tastatur oder per Mausklick eine Eingabe vornimmt, wird ein Methodenaufruf an alle Objekte gesendet, die bei der Komponente als Empf$nger f.r den Typ des ausgelGsten Ereignisses registriert sind. Die Methode verf.gt .ber einen Parameter vom Typ einer der Unterklassen von java.awt.Event. Zus$tzliche Informationen .ber das ausgelGste Ereignis kGnnen .ber Methodenaufrufe an das Ereignis ermittelt werden. Die Kategorie Tastaturereignis wird beispielsweise von KeyEvent repr$sentiert. Wenn ein Benutzer ein Tastaturereignis durch Dr.cken einer Taste auslGst, w$hrend sich die Maus in einer Zeichenfl$che befindet, erzeugt das System eine Instanz von java.awt.event.KeyEvent und ruft keyPressed() mit dem Tastaturereignis als Parameter bei allen Tastenempf$ngern der Zeichenfl$che auf. Jede Implementierung von keyPressed(), die an den Daten des Ereignisses interessiert ist – z.B. um zu ermitteln, welche Taste gedr.ckt wurde –, kann getKeyCode() f.r das Tastaturereignis aufrufen. Um als Ereignisempf$nger f.r einen bestimmten Ereignistyp in Frage zu kommen, muss ein Objekt eine Schnittstelle implementieren, die dem gew.nschten Ereignistyp entspricht. F.r ein Ereignis vom Typ XXX lautet der Name der entsprechenden Schnittstelle xxxListener. Zum Registrieren eines Objekts als Ereignisempf$nger von xxx einer bestimmten Komponente wird die Methode addxxxListener() der Komponente aufgerufen und der Ereignisempf$nger als Methodenargument .bergeben. Es gibt zwei Ereignisempf$ngerschnittstellen, die Mausereignisse verarbeiten: MouseListener und MouseMotionListener. Ein Ereignisempf$nger eines Mausereignisses (MouseListener) wird benachrichtigt, wenn eine Maustaste gedr.ckt wird
Hinweis 321
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
oder wenn der Mauscursor in eine Komponente bewegt oder daraus entfernt wird. Ein Ereignisempf$nger eines Mausbewegungsereignisses (MouseMotionListener) wird benachrichtigt, wenn die Maus verschoben wird. Die folgende Tabelle enth$lt die verschiedenen Typen von Empf$ngerschnittstellen, Schnittstellenmethoden und add-Methoden.
Ereignisempf5ngerschnittstellen
322
Schnittstelle
Schnittstellenmethoden
add-Methode
ActionListener
actionPerformed (ActionEvent)
addActionListener (ActionListener)
AdjustmentListener
adjustmentValuechanged (AdjustmentEvent)
addAdjustmentListener (AdjustmentListener)
ComponentListener
componentHidden (ComponentEvent) componentMoved (ComponentEvent) componentResized (ComponentEvent) componentShown (ComponentEvent)
addComponentListener (ComponentListener)
ContainerListener
componentAdded (ContainerEvent) componentRemovec (ContainerEvent)
addContainerListener (ContainerListener)
FocusListener
focusGained(FocusEvent) focusLost(FocusEvent)
addFocusListener (FocusListener)
InputMethodListener
caretPositionChanged (InputMethodEvent) InputMethodTextChanged (InputMethodEvent)
addInputMethodListener (InputMethodListener)
ItemListener
itemStateChanged (ItemEvent)
addItemListener (ItemListener)
KeyListener
keyPressed(KeyEvent) keyReleased(KeyEvent) keyTyped(KeyEvent)
addKeyListener (KeyListener)
MouseListener
addMouseListener mouseClicked(MouseEvent) mouseEntered(MouseEvent) (MouseListener) mouseExited(MouseEvent) mousePressed(MouseEvent) mouseReleased(MouseEvent)
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Modell der Ereignisdelegation
Ereignisempf5ngerschnittstellen (Forts.)
Schnittstelle
Schnittstellenmethoden
add-Methode
MouseMotionListener
mouseDragged (MouseEvent) mouseMoved (MouseEvent)
addMouseMotionListener (MouseMotionListener)
TextListener
textValueChanged (TextEvent)
addTextListener (TextListener)
WindowListener
windowActivated (WindowEvent) windowClosed (WindowEvent) windowClosing (WindowEvent) windowDeactivated (WindowEvent) windowDeiconified (WindowEvent) windowIconified (WindowEvent) windowOpened (WindowEvent)
addWindowListener (WindowListener)
AWTEventListener
eventDispatched (AWTEvent event)
addAWTEventListener (AWTEventListener, long) Tabelle 12.1
Ereignisse explizit erkennen Als Alternative zum Delegieren von Ereignissen einer Komponente kann auch die Technik des expliziten Erkennens von Ereignissen verwendet werden. Komponenten verf.gen .ber eine Methode namens enableEvents(). Diese Methode kann aufgerufen werden, um Ereignisse vor der Benachrichtigung der Ereignisempf$nger aufzusp.ren. Das Argument des enableEvents()-Aufrufs ist ein int-Wert, der den Typ oder die Typen der zu erkennenden Ereignisse festlegt; Konstanten f.r diese Typen sind in der Klasse AWTEvent definiert. Wenn f.r eine Komponente die explizite Ereigniserkennung f.r den Typ xxx festgelegt wurden, lGst ein xxx-Ereignis einen Aufruf der Methode processxxxEvent (xxxevent) der Komponente aus. Standardm$ßig werden alle registrierten Ereignisempf$nger der Komponente von diesen Methoden benachrichtigt. Unterklassen der Komponente kGnnen die Methoden processxxxEvent() .berschreiben, um die gew.nschte Ereignisbehandlung auszuf.hren. 323
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Um beispielsweise Aktionsereignisse explizit zu erkennen, kann im Konstruktor einer Unterklasse von Button die Methode enableEvents(AWTEvent.ACTION_ EVENT_MASK) aufgerufen und processActionEvent(ActionEvent) .berschrieben werden. Um sowohl Tastatur- als auch Aktionsereignisse explizit zu erkennen, kann im Konstruktor einer Unterklasse von TextField die Methode enableEvents(AWTEvent.KEY_EVENT_MASK | AWTEvent.ACTION_EVENT_MASK) aufgerufen und sowohl processActionEvent(ActionEvent) als auch processKeyEvent(KeyEvent) .berschrieben werden. Auf jeden Fall sollte die Methodenversion der Unterklasse die Methode der Oberklasse aufrufen, damit registrierte Ereignisempf$nger benachrichtigt werden. Die Methode processKeyEvent() sollte beispielsweise etwa folgendermaßen aussehen: public void processKeyEvent(KeyEvent e) { // Hier die spezifische Verarbeitung f:r die // Unterklasse einf:gen super.processKeyEvent(e); } Wie Sie weiter unten in diesem Kapitel noch ausf.hrlich erfahren werden, stattet die explizite Ereigniserkennung benutzerdefinierte Komponenten mit einem komfortablen Mechanismus zur Ereignisbehandlung aus.
Strategien zum Entwickeln von benutzerdefinierten Komponenten Die Entscheidung, eine benutzerdefinierte Komponentenklasse zu erzeugen, sollte nicht unbedacht getroffen werden. Die mit Abstand wichtigste
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Strategien zum Entwickeln von Komponenten
Sie kGnnen Unterklassen von nicht-Container-Komponenten wie etwa Button oder Checkbox ableiten. Die Unterklasse erweitert hierbei das geerbte Verhalten. F.r jede Methode gibt es ein F.r und Wider. Bei der Beurteilung einer Methode ist die Haupt.berlegung die Einfachheit der Bedienung. Benutzer sollten schnell herausfinden kGnnen, wie die neue Komponente verwendet wird und die Komponente sollte Benutzer produktiv unterst.tzen. Jede Funktion einer Komponente, die schwer zu verstehen oder schwer anzuwenden ist, sollte als ernsthafte Belastung angesehen werden.
Unterklassen der Klasse Component Im Allgemeinen sind Komponenten, die anhand der ersten Strategie – dem Ableiten von Unterklassen der Klasse Component –, f.r den Benutzer die schwierigsten. Weil das Aussehen und die Bedienung dieser Komponenten vGllig neu sind, hat der Benutzer keine Erfahrung mit vergleichbaren Komponenten, die die Interaktion mit den neuen erleichtern kGnnte. Da Benutzer den Umgang mit der neuen Komponente erst erlernen m.ssen, sollten diese mGglichst intuitiv zu verwenden sein. Die Komponente sollte der Vorstellung des Benutzers von den einzugebenden Daten so genau wie mGglich entsprechen. Das Bilden von Unterklassen von Component ist besonders dann zu empfehlen, wenn der Benutzer eine neue Datenart eingeben muss, die nicht als Text, als Status eines Kontrollfeldes oder als Position eines Rollbalkens repr$sentiert werden kann. Als Beispiel f.r das Erstellen einer benutzerdefinierten Komponente mit dieser Methode wird eine neue Komponente zur Eingabe eines Wertes in Polarkoordinaten entwickelt (siehe den Abschnitt „Unterklassen von Component: die Komponente Polar“ weiter unten in diesem Kapitel).
Gruppierung Mit der zweiten Strategie – der Gruppierung – kGnnen Sie hGchstwahrscheinlich eine Komponente entwickeln, deren Bedienung der Benutzer m.helos erlernen kann. Alle Komponenten, die der Container enth$lt, sind bereits gut bekannt. Nur die Interaktionen zwischen den Unterkomponenten in dem Container sind neu. Da Benutzer die Verwendung von Kontrollfeldern, Textfeldern und Rollbalken kennen, m.ssen sie wenig Neues erlernen. Bei dieser Methode profitieren Sie von den aufwendigen Analysen zur Benutzerfreundlichkeit, die im Laufe der Jahre f.r die Neudefinition von Unterkomponenten durchgef.hrt wurden. Diese Strategie ist besonders f.r Komponenten empfehlenswert, die Eingabe- und Ausgabefunktionen oder mehrere EingabemGglichkeiten bereitstellen sollen. 325
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Als Beispiel f.r das Erstellen einer benutzerdefinierten Komponente mit der Gruppierungsmethode wird eine Komponente entwickelt, die drei Optionen f.r die Eingabe eines nummerischen Wertes anbietet. Das Beispiel enth$lt Kontrollfelder, ein Textfeld und einen Rollbalken f.r die Eingabe eines Wertes (siehe den Abschnitt „Gruppierung: die Komponente ThreeWay“ weiter unten in diesem Kapitel).
Unterklassen von Standardkomponenten Die dritte Strategie verbindet ein bekanntes Aussehen mit einer neuen Bedienung. Wenn Sie von einer Komponente eine Unterklasse ableiten, erkennt der Benutzer die Komponente wieder und erwartet, dass sie wie die Standardklasse arbeitet. Daher m.ssen Sie den Benutzer darauf hinweisen, dass dies nicht der Fall ist. Diese Strategie eignet sich am besten zur Beschr$nkung der mGglichen, g.ltigen Eingaben in eine Komponente. Als Beispiel werden zwei Versionen einer Textfeldkomponente mit G.ltigkeitspr.fung entwickelt (siehe den Abschnitt „Unterklassen von Standardkomponenten: Textfelder mit Gltigkeitsprfung“ weiter unten in diesem Kapitel).
Designberlegungen Bevor Sie sich f.r eine Strategie – Unterklassen der Klasse Component, Gruppierung in einem Container oder Unterklassen von Standardkomponenten – entscheiden, m.ssen Sie die folgenden beiden Fragen beantworten: Wie sollen Werte in der Komponente angezeigt werden? Wie soll der Benutzer neue Werte festlegen? Nachdem Sie die Fragen entschieden haben, ist meistens die beste Designstrategie schon offensichtlich. Die folgende Liste kann Sie dabei unterst.tzen, Ihre Gedanken zu ordnen und fundierte Designentscheidungen zu treffen: Wie kann der Programmierer das Aussehen der Komponente $ndern? Welche Begrenzungen soll es f.r die mGglichen Werte der Komponente geben? Wie sollen die Werte gespeichert, gesetzt und ermittelt werden? Welche Ereignisse soll die Komponente senden? Sind benutzerdefinierte Ereignistypen und Empf$ngerschnittstellen erforderlich? Arbeitet die Komponente in einer Multithread-Umgebung ordnungsgem$ß? Verh$lt sich die Komponente bei einer GrGßen$nderung korrekt?
326
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Component: die Komponente Polar
Beim Nachvollziehen der Beispiele f.r benutzerdefinierte Komponenten in diesem Kapitel werden Sie feststellen, dass die Antworten auf diese Fragen die Basis f.r einen fundierten Designplan bilden.
Unterklassen von Component: die Komponente Polar Im ersten Beispiel wird eine Komponente mit dem Namen Polar entwickelt, die zur Eingabe eines Punktes in Polarkoordinaten verwendet werden soll. Polarkoordinaten beschreiben einen Punkt als seine Entfernung vom Ursprung (961; oder Rho f.r Radius) und dem Winkel (952; oder Theta), den er mit der nach rechts weisenden Horizontalen bildet. Polarkoordinaten sind eine Alternative zum Beschreiben eines Punktes anhand zweier Zahlen in einem zweidimensionalen Raum. Die Polarkoordinaten (961;, 952;) lassen sich folgendermaßen in das bekannte, rechteckige kartesische Koordinatensystem (x,y) umrechnen: x = r cos q; y = r sin q.
Hinweis
Das Aussehen und die Bedienung der Komponente Polar Wie weiter oben bereits beschrieben m.ssen Sie vor der Entwicklung einer benutzerdefinierten Komponente .berlegen, wie die Komponente angezeigt und verwendet werden soll.
Werte anzeigen Die erste
327
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Abb. 12.2 Eine g7ngige Vorstellung von Polarkoordinaten
Die Polar-Komponente sollte der obigen Abbildung so genau wie mGglich entsprechen. Aus Gr.nden der einfacheren Handhabung werden sich aber einige Modifikationen und Kompromisse ergeben. Der aktuelle Punkt sollte auf jeden Fall durch einen kreisfGrmigen Punkt dargestellt werden. In der Praxis w.rde die Darstellung einer Pfeilspitze am Ende der Linie die Sichtbarkeit des Punktes beeintr$chtigen, daher wird keine Pfeilspitze angezeigt. Der Benutzer sollte darauf hingewiesen werden, dass diese Komponente in einem polaren Bereich arbeitet und nicht im kartesischen. Deshalb sollte ein Kreis und kein Rechteck gezeichnet werden. Wenn ein Kreis vorhanden sein soll, wo soll er sich befinden? Wenn Sie einen Kreis zeichnen, der den aktuellen Punkt schneidet, wird der Benutzer zus$tzlich .ber den Abstand vom Mittelpunkt informiert. Das Ergebnis dieser
Abb. 12.3 Die Polar-Komponente
Werte festlegen Wie soll der Benutzer neue Werte festlegen? Die Polar-Komponente ist eindeutig zum Anklicken vorgesehen.
328
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Component: die Komponente Polar
Die sehr einfache Schnittstelle erlaubt es dem Benutzer, den Kreis zu fassen, indem er die Maustaste an einer beliebigen Position innerhalb der Komponente dr.ckt und festh$lt. Der aktuelle Punkt und der Kreis werden an die Position des Mauscursors verschoben. So lange die Maustaste gedr.ckt wird, folgt der Wert den Mausbewegungen. L$sst der Benutzer die Maustaste los, wird der Wert nicht mehr ver$ndert. Wenn der Benutzer die Maus zieht, werden der Punkt, die Linie und der Kreis in Blau angezeigt. Dadurch wird dem Benutzer verdeutlicht, dass die Komponente die Eingaben empf$ngt. Wenn die Komponente nicht reagiert (was meistens der Fall ist), werden diese Elemente in Schwarz dargestellt. Weil sich die Achsen nie $ndern und auch keine Informationen .ber den aktuellen Wert mitteilen, erhalten sie eine eigene konstante Farbe. Eine
Eine Strategie fr das Ableiten der Unterklasse w5hlen Da nun das Aussehen und die Bedienung definiert sind, m.ssen Sie sich f.r eine Strategie zum Erstellen der Unterklasse entscheiden. Da es keine Standardkomponente gibt, die einen Kreis oder einen Pfeil bereitstellt, scheidet die Gruppierung und die Ableitung einer Unterklasse von einer Standardkomponente aus. Zum Erstellen der Polar-Komponente muss eine Unterklasse von Component abgeleitet und der gesamte
Das Design der Komponente Polar Nun kGnnen Sie mit der Entwicklung der Komponente Polar fortfahren, indem Sie sich den folgenden Designanforderungen zuwenden: dem Sndern der Darstellung der Komponente, dem Beschr$nken von Werten, der Verarbeitung von Werteingaben, die Behandlung von Ereignissen und dem Vorsehen von Verhalten f.r Multithread-Umgebungen und bei GrGßen$nderungen.
Das Aussehen der Komponente 5ndern Wie kann ein Programmierer das Aussehen der Komponenten modifizieren? Ber.cksichtigen Sie jene Aspekte der Darstellung, deren Snderung nahe liegen. Die Anzeige des nummerischen Wertes ist hierf.r ein gutes Beispiel – Sie kGnnten sie deaktivieren oder die Schriftart $ndern. Außerdem sollten die Farben der einzelnen Elemente und die Hintergrundfarbe der Komponente ver$nderbar sein. In fast allen F$llen kGnnen Sie die Kontrolle .ber diese Aspekte dem Programmierer ohne zus$tzlichen Aufwand zur Verf.gung stellen. 329
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Jede Komponente verf.gt .ber eine Hintergrundfarbe, eine Vordergrundfarbe und eine Schriftart. Wenn die Methode repaint() einer Komponente aufgerufen wird, ruft der GUI-Thread die Methode update() der Komponente auf; update() leert die Komponente anhand der Hintergrundfarbe, setzt die aktuelle Farbe auf die Vordergrundfarbe und ruft paint() auf. Da die Methode paint() davon ausgeht, dass die Komponente mit einer geeigneten Hintergrundfarbe geleert wurde, kGnnen Sie den Hintergrund einer Polar-Komponente durch den Aufruf ihrer Methode setBackground() $ndern. Da diese Methode von der Klasse Component vererbt wird, haben Programmierer keinen zus$tzlichen Lernaufwand. Der Kreis, der Punkt und die Linie werden – vergleichbar mit dem oben beschriebenen Vorgehen bei der Hintergrundfarbe – in der Vordergrundfarbe .bergeben. Daher kGnnen Sie zum Sndern der Zeichenfarbe einfach die Methode setForeground() aufrufen. Wenn im Code der Methode paint() die Schriftart f.r den Textwert i nicht explizit gesetzt wird, wird standardm$ßig die eigene Schriftart der Komponente verwendet. Somit kGnnen Sie also die Schriftart durch einen Aufruf der Methode setFont() der Komponente setzen. Das Einzige, was nicht mit den geerbten Methoden der Komponente modifiziert werden kann, sind die w$hrend des Ziehens angezeigten Farben und das Vorhandensein bzw. Nichtvorhandensein von Textmeldungen. F.r diese Modifikationen sind die beiden als public deklarierten Methoden setDragColor() und setShowText() erforderlich.
Werte beschr5nken Als N$chstes m.ssen Sie .berlegen, ob die Komponente Wertbeschr$nkungen enthalten soll. Theta ist naturgem$ß auf einen Bereich von 0 bis 360 Grad eingegrenzt. F.r Rho dagegen ist eine Beschr$nkung unerl$sslich. Es muss einen Maximalwert f.r Rho geben, der durch einen Kreis dargestellt wird, welcher die gesamte Komponente ausf.llt. Dazu ist es erforderlich, dass die Entfernung in Pixel in Werte f.r Rho konvertiert wird. Dieser Maximalwert erscheint als Argument im Konstruktor der Komponente Polar und wird in einer Instanzvariablen mit dem Namen maxRho gespeichert. Theoretisch sollte der maxRho-Wert einen Kreis erzeugen, dessen Durchmesser (in Pixel) gleich dem kleineren der beiden Werte f.r die Breite oder die HGhe der Komponente ist. In der Praxis wirkt aber ein Kreis, der an den Rand der Komponente anstGßt, ziemlich unansehnlich, daher sollte maxRho tats$chlich einem Kreis entsprechen, der zehn Prozent kleiner als die kleinste Dimension der Komponente ist. In der Polar-Komponente gibt es – besonders an den Ecken – viele Punkte, die ung.ltige Werte repr$sentieren, weil Rho dann grGßer als maxRho w$re. Wenn der Benutzer den Kreis in diese Bereiche ziehen will, sollte sich der Kreis nicht bewegen, sondern in seiner maximal zul$ssigen GrGße angezeigt werden. 330
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Component: die Komponente Polar
Werte speichern, setzen und abrufen Die n$chste Frage lautet, wie der Wert gespeichert, gesetzt und abgerufen werden soll. Die Werte f.r Rho und Theta sollten als float- oder double-Typen definiert werden. Werte vom Typ double eignen sich besser, weil die Komponente zahlreiche Aufrufe von trigonometrischen Methoden der Klasse Math ausf.hren muss, die den Typ double verwendet. Programmierer, die mit der Klasse Polar arbeiten, werden mit dem Ergebnis hGchstwahrscheinlich auch trigonometrische Berechnungen durchf.hren und daher ebenfalls Werte des Typs double bevorzugen. Um doubleWerte f.r Rho und Theta zu kapseln, wird die Hilfsklasse PolarPoint definiert. Beachten Sie bei der Verwendung der Klasse Polar, dass alle trigonometrischen Funktionen von Java mit dem Bogenmaß arbeiten. Zur Konvertierung von oder nach Grad definiert die Klasse Math der Wert PI vom Typ staticfinal double.
Hinweis
Nun kann die Klasse PolarPoint definiert werden. Das folgende Listing enth$lt den Quellcode der Klasse PolarPoint. /* * Die Klasse PolarPoint beschreibt einen Punkt in *Polarkoordinaten. Sie wird von der Komponenten*klasse Polar zum Speichern ihres Wertes *verwendet. */
Listing 12.1 PolarPoint.java
public class PolarPoint { private double rho; private double theta; public PolarPoint(double rho, double theta) { this.rho = rho; this.theta = theta; } public PolarPoint(PolarPoint p) { this.rho = p.getRho(); this.theta = p.getTheta(); } public double getRho() { return rho; } public void setRho(double rho) { this.rho = rho; } public double getTheta() { return theta; } public void setTheta(double theta) { this.theta = theta; } public String toString() { return 8243;RHO = 8243; + rho + 8243; ... THETA = 8243; + 331
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
theta; } } Diese Klasse speichert Rho und Theta als private double mit als public deklarierten Zugriffs- und Modifikationsmethoden. Die Namensvergabe f.r die Zugriffs- und Modifikationsmethoden (oder die Getter- und Setter-Methoden) stimmt mit den Namenskonventionen von JavaBeans .berein. Siehe (Kapitel 18: JavaBean-Eigenschaften). Nun kGnnen der Klasse Polar die private Instanzvariable value des Typs PolarPoint und public-Methoden zum Ermitteln und Setzen des Wertes zugewiesen werden. Die Namen dieser Methoden lauten getPolarValue() und setPolarValue(), wieder in
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Component: die Komponente Polar
// Falls gezogen wird, wird f:r das folgende // Zeichnen die Ziehfarbe verwendet if (dragging) { g.setColor(dragColor); } // Den Kreis zeichnen g.drawOval(ulx, uly, 2*radiusPix, 2*radiusPix); // Den Punkt zeichnen int arrowTipX = centerX + (int)(radiusPix * Math.cos(value.getTheta())); int arrowTipY = centerY - (int)(radiusPix * Math.sin(value.getTheta())); g.fillOval(arrowTipX-3, arrowTipY-3, 7, 7); // Die Linie von der Mitte zum Punkt zeichnen // und Leerr[ume zulassen if (radiusPix > 5) { g.drawLine(centerX, centerY, arrowTipX, arrowTipY); } } Mouse und MouseMotion aktualisieren die Instanzvariablen value und dragging. Die Klasse Polar dient als ihr eigener Ereignisempf$nger von MouseListener und MouseMotionListener. Das folgende Codefragment der Klassendefinition enth$lt den Abschnitt mit der Ereignisbehandlung. Beachten Sie, dass die Klassendeklaration ge$ndert wurde, damit die Implementierung der beiden Empf$ngerschnittstellen deklariert werden konnte. public class Polar extends Component implements MouseListener, MouseMotionListener { private PolarPoint value; private double maxRho; private double unitsPerPixel; private boolean dragging = false; private Color dragColor = Color.blue; private boolean showTextValue = true; { value = xyToPolar(e.getX(), e.getY()); repaint(); } public void mouseDragged(MouseEvent e)
333
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
{ dragging = true; value = xyToPolar(e.getX(), e.getY()); repaint(); } public void mouseReleased(MouseEvent e) { dragging = false; value = xyToPolar(e.getX(), e.getY()); repaint(); } private PolarPoint xyToPolar(int x, int y) { double newRho; double newTheta; int deltaX = x - getSize().width/2; int deltaY = getSize().height/2 - y; double deltaLen = Math.sqrt(deltaX*deltaX + deltaY*deltaY); double rho = unitsPerPixel * deltaLen; rho = Math.min(rho, maxRho); double theta = Math.atan2(deltaY, deltaX); while (theta < 0.0) theta += 2*Math.PI; return new PolarPoint(rho, theta); } public public public public
void void void void
mouseClicked(MouseEvent e) mouseMoved(MouseEvent e) mouseEntered(MouseEvent e) mouseExited(MouseEvent e)
{ } { } { } { }
} Die Methode xyToPolar() konvertiert Pixelkoordinaten in Polarkoordinaten und gibt eine Instanz von PolarPoint zur.ck. Die vier leeren Methoden am Ende des Listings gew$hrleisten, dass die beiden Schnittstellen vollst$ndig implementiert werden. An diesem Punkt verf.gt die neue Komponentenklasse .ber ein Erscheinungsbild und einen Wert. Beides wird entsprechend angepasst, wenn der Benutzer eine Mauseingabe vornimmt. Jetzt kGnnen Sie sich der Ereignisbehandlung zuwenden.
Ereignisse behandeln Als N$chstes soll das Ereignis behandelt werden, das die Komponente Polar sendet, wenn sie von einem Benutzer aktiviert wird. 334
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Component: die Komponente Polar
Zweckm$ßig w$re, wenn die benutzerdefinierte Komponente einen Standardereignistyp senden w.rde. Da eine Polar-Komponente eine Art Rollbalken – nur rund und zweidimensional – darstellt, w$re ein denkbarer Typ das Ereignis Adjustment. Leider sind Adjustment-Ereignisse nicht gut geeignet, da die Methode getValue() der Klasse AdjustmentEvent einen Wert vom Typ int zur.ckgibt, w$hrend der Wert der Komponente Polar den Typ PolarPoint hat. Da es keinen wirklich passenden Ereignistyp gibt, m.ssen Sie einen neuen Typ entwickeln: PolarEvent. Im Folgenden finden Sie das Listing f.r die Klasse PolarEvent. import java.awt.AWTEvent;
Listing 12.2
public class PolarEvent extends AWTEvent { private PolarPoint polarValue;
PolarEvent.java
public PolarEvent(Polar source, PolarPoint p) { super(source, RESERVED_ID_MAX+1); polarValue = p; } public PolarPoint getPolarValue() { return polarValue; } public void setPolarValue(PolarPoint p) { polarValue = p; } } Der zweite Parameter, der in der ersten Zeile des Konstruktors an den Konstruktor der Oberklasse .bergeben wird, ist die ID des Ereignisses. Werte bis einschließlich AWTEvent.RESERVED_ID_MAX sollten von Programmierern nicht f.r neue Ereignisklassen verwendet werden. Die Klasse PolarEvent ist zwar ziemlich einfach, dennoch ist f.r die Entwicklung eines neuen Ereignistyps zus$tzliche Programmierarbeit erforderlich. Zuerst muss eine Empf$ngerschnittstelle hinzugef.gt werden. Der Einfachheit halber wird diese Schnittstelle PolarListener genannt. Die Schnittstelle soll nur eine einzige Methode, n$mlich polarValueChanged(), haben, deren Argument eine Instanz von PolarEvent sein soll. Im Folgenden finden Sie das Listing f.r die Schnittstelle PolarListener. public interface PolarListener extends java.util.EventListener { public void polarValueChanged(PolarEvent pe); }
Listing 12.3 PolarListener.java
335
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Nun kann jedes Objekt, das Benachrichtigungen von einer Polar-Komponente empfangen will, die Schnittstelle PolarListener implementieren und die Methode polarValueChanged() bereitstellen. Die Klasse Polar muss mit den Methoden addPolarListener() und removePolarListener() ausgestattet werden. Ein Vektor stellt einen komfortablen Weg dar, .ber Empf$nger auf dem Laufenden zu bleiben. Daher wird der Klasse Polar eine Vektorinstanzvariable mit dem Namen listeners hinzugef.gt (siehe das folgende Codefragment). public class Polar extends Component implements MouseListener, MouseMotionListener { private Vector listeners = new Vector(); public void addPolarListener(PolarListener listener) { if (!listeners.contains(listener)) listeners.addElement(listener); } public void removePolarListener(PolarListener listener) { listeners.removeElement(listener); } . . . } Wenn eine Maustaste gedr.ckt oder die Maus gezogen wird, sollten alle Empf$nger von den Ereignisbehandlungsroutinen (die bereits den Wert aktualisiert und repaint() aufgerufen haben) benachrichtigt werden. Dies geschieht durch den Aufruf der folgenden Methode, die eine Instanz von PolarEvent erzeugt und anschließend polarValueChanged() f.r alle registrierten Empf$nger von Polar aufruft. private void notifyListeners() { // Den Vektor der Empf[nger klonen, falls // polarValueChanged eines Empf[ngers den // urspr:nglichen Vektor ver[ndert Vector copyOfListeners = (Vector)(listeners.clone()); // Ein Polar-Ereignis erzeugen, das den // aktuellen Wert kapselt PolarPoint pp = new PolarPoint(value); PolarEvent event = new PolarEvent(this, pp); 336
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Component: die Komponente Polar
// Alle Empf[nger benachrichtigen Enumeration enum = copyOfListeners.elements(); while (enum.hasMoreElements()) { PolarListener listener = (PolarListener)enum.nextElement(); listener.polarValueChanged(event); } } Bitte beachten Sie, dass die Benachrichtigung durch das Klonen des Vektors der Empf$nger durchgef.hrt und mit dem Klon anstelle des Originals weitergearbeitet wird. Im n$chsten Abschnitt werden die Gr.nde daf.r erl$utert. Bis jetzt wurden die folgenden Aspekte bearbeitet: die Behandlung des Wertes und des Erscheinungsbildes durch die neue Komponentenklasse sowie die Benachrichtigung der Empf$nger. Dies sind die zentralen Aspekte, durch deren Ber.cksichtigung Sie eine Klasse erhalten, die in den meisten Situationen fehlerfrei arbeitet. Die verbleibenden beiden
Verhalten in einer Multithread-Umgebung Eine Multithread-Umgebung stellt immer eine potenzielle Bedrohung f.r gemeinsam genutzte Daten dar (Daten, auf die von zwei unterschiedlichen Threads simultan zugegriffen werden kann). In dem vorliegenden Beispiel gibt es zwei Bereiche von gemeinsam genutzten Daten, die ber.cksichtigt werden m.ssen: Das einzelne Objekt PolarEvent, das als Argument an alle Empf$nger von Polar gesendet wird. Dieses Objekt enth$lt eine Referenz auf PolarPoint, das den aktuellen Wert angibt. Der Vektor der Empf$nger. Empf$nger von Polar kGnnen sich bei einer PolarKomponente asynchron registrieren und die Registrierung ebenso asynchron wieder aufheben. PolarEvent wird von der Klasse Polar erzeugt und geschrieben und von den registrierten Empf$ngern gelesen. Die Gefahr besteht, dass das Ereignis von einem Empf$nger verf$lscht werden kGnnte. Dies ist ein genereller Nachteil des Ereignisverbreitungsmodells. Konventionsgem$ß wird dieses Problem nur gelGst, indem Programmierer von potenziell problematischen Empf$ngermethoden darauf hingewiesen werden. Empf$ngermethoden, die Ereignisse ver$ndern, sollten das Ereignis kopieren, mit den Kopien arbeiten und die Originale unver$ndert lassen. Da dies normalerweise eine tragf$hige Praktik ist, ist es nicht wirklich erforderlich, weitere Vorsichtsmaßnahmen zu treffen. Andererseits muss dem Vektor der Empf$nger besondere Aufmerksamkeit geschenkt werden. Wenn ein Benutzer die Komponente modifiziert, wird dieser Vektor durchlaufen, so dass die Empf$nger benachrichtigt werden kGnnen. Die Hauptgefahr dabei ist, dass die Methode polarValueChanged() eines Empf$ngers
337
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
die Registrierung des Empf$ngers aus der Empf$ngerliste der Komponente entfernen kGnnte. Wenn dies passiert, w.rden die Vektordurchl$ufe verf$lscht. Es ist auch vorstellbar, dass unabh$ngige Threads andere Empf$nger registrieren oder deren Registrierung aufheben kGnnten, was wiederum zu einem fehlerhaften Durchlaufen des Vektors f.hren kGnnte. Die sicherste LGsung stellt das Klonen des Vektors der Empf$nger und das Benachrichtigen durch Durchlaufen des Klons anstelle des Originals dar.
Hinweis
Alle Methoden der Klasse Vector, die interne Daten $ndern, sind synchronisiert. Es besteht also keine Gefahr, dass der Vektor außer bei der Ereignisbenachrichtigung verf$lscht werden kGnnte.
Verhalten bei GrBßen5nderungen Die letzte relevante Frage betrifft das Vorgehen bei GrGßen$nderungen der Komponente. Weiter oben wurde bereits entschieden, dass im Konstruktor ein Maximalwert f.r Rho festgelegt werden soll, der den Skalierungsfaktor f.r die Konvertierung der Einheiten von Rho in Pixel definiert. Dieser Konvertierungsfaktor wird immer herangezogen, wenn sich der Wert $ndert. Daher sollte er als eine Instanzvariable gespeichert werden. Eine stimmige Namensvergabe f.r Variablen ist immer wichtig, aber achten Sie in diesem Fall auf Folgendes: Wenn Sie den Konvertierungsfaktor conversionFactor nennen, m.ssen Entwickler, die den Code pflegen, selber herausfinden, ob f.r die Konvertierung von Pixel in Einheiten eine Multiplikation mit oder eine Division durch conversionFactor erforderlich ist. Eine Verwechslung an dieser Stelle kGnnte einen sehr subtilen Fehler produzieren. Es ist viel besser, eine mGgliche Verwirrung auszuschließen, indem Sie die Instanzvariable entweder pixelsPerUnit oder unitsPerPixel nennen. Hier wird unitsPerPixel verwendet, weil im vorliegenden Beispiel Pixel in Einheiten konvertiert werden und daher eine Multiplikation und keine Division erforderlich ist. Die Operanden einer Multiplikation kGnnen vertauscht werden, daher besteht keine Gefahr, dass sich zuk.nftig bei der Pflege des Codes ein Fehler einschleicht, weil die Operanden in der falschen Reihenfolge aufgef.hrt werden. Wenn die GrGße der Komponente ver$ndert wird, wird unitsPerPixel neu berechnet: private void adjustScale(int w, int h) unitsPerPixel = 2.0 * maxRho / Math.min(w, h); unitsPerPixel *= 1.1; } Wenn die Komponente den maximal zul$ssigen Wert f.r Rho repr$sentiert, wird durch die Multiplikation mit 1,1 sichergestellt, dass der Kreis nur ungef$hr 90 Prozent der gesamten Komponente umfasst und nie so nah an den R$ndern dargestellt werden kann, dass die Komponente schwer lesbar oder benutzbar wird.
338
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Component: die Komponente Polar
public void setBounds(int x, int y, int w, int h){ adjustScale(w, h); super. setBounds(x, y, w, h); }
Die Komponentenklasse Polar und das Test-Applet Im Folgenden finden Sie das vollst$ndige Listing der Klasse Polar. import java.awt.*; import java.awt.event.*; import java.util.*;
Listing 12.4 Polar.java
/* * Die Komponente Polar zum Festlegen eines Punktes * in Polarkoordinaten. Gibt fortlaufend grafische * und textliche R:ckmeldungen :ber den aktuellen * Wert. Sendet das Ereignis PolarEvent an alle * Empf[nger von Polar, wenn die Maus gezogen oder * die Maustaste losgelassen wird. */ public class Polar extends Component implements MouseListener, MouseMotionListener { private Vector listeners = new Vector(); private PolarPoint value; private double maxRho; private double unitsPerPixel; private boolean dragging = false; private Color dragColor = Color.blue; private boolean showTextValue = true; public Polar() { this(100.0, 0.0, 100.0); } public Polar(double initRho, double initTheta, double maxRho) { value = new PolarPoint(initRho, initTheta); this.maxRho = maxRho; setBackground(Color.white); addMouseListener(this); addMouseMotionListener(this); } public void setDragColor(Color c) 339
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
{ dragColor = c; } public void setShowText(boolean b) { showTextValue = b; } public void setValue(PolarPoint newValue) { value = newValue; repaint(); } public PolarPoint getValue() { return value; } public void addPolarListener(PolarListener listener) { if (!listeners.contains(listener)) listeners.addElement(listener); } public void removePolarListener(PolarListener listener) { listeners.removeElement(listener); }
private void notifyListeners() { // Den Vektor der Empf[nger klonen, falls // polarValueChanged eines Empf[ngers den // urspr:nglichen Vektor ver[ndert Vector copyOfListeners = (Vector) (listeners.clone()); // Ein Polar-Ereignis erzeugen, das den // aktuellen Wert kapselt PolarPoint pp = new PolarPoint(value); PolarEvent event = new PolarEvent(this, pp);
340
// Alle Empf[nger benachrichtigen Enumeration enum = copyOfListeners.elements(); while (enum.hasMoreElements())
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Component: die Komponente Polar
{ PolarListener listener = (PolarListener)enum.nextElement(); listener.polarValueChanged(event); } }
/* * Skalierung anpassen, damit der grIßte, zul[ssige * Wert nicht die gesamte Komponente ausf:llt */ private void adjustScale(int w, int h) { unitsPerPixel = 2.0 * maxRho / Math.min(w, h); unitsPerPixel *= 1.1; }
/* * Wenn die GrIße der Komponente ge[ndert wird, * muss die Skalierung angepasst werden. Beachten * Sie, dass setSize() nicht :berschrieben werden * muss, weil setSize() setBounds() aufruft. */ public void setBounds(int x, int y, int w, int h) { adjustScale(w, h); super.setBounds(x, y, w, h); } public void paint(Graphics g) { int radiusPix; int centerX; int centerY; Dimension size; radiusPix = (int)(value.getRho() / unitsPerPixel); size = getSize(); centerX = size.width / 2; centerY = size.height / 2; int ulx = centerX - radiusPix; int uly = centerY - radiusPix; // Achsen in Hellgrau zeichnen g.setColor(Color.lightGray); g.drawLine(centerX, 0, centerX, size.height); g.drawLine(0, centerY, size.width, centerY);
341
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
// Den Textwert in der oberen, linken Ecke // anzeigen g.setColor(getForeground()); if (showTextValue) { g.drawString((value.getRho() + 8243;, 8243; + value.getTheta()), 5, size.height-5); } // Falls gezogen wird, wird f:r das folgende // Zeichnen die Ziehfarbe verwendet if (dragging) { g.setColor(dragColor); } // Den Kreis zeichnen g.drawOval(ulx, uly, 2*radiusPix, 2*radiusPix); // Den Punkt zeichnen int arrowTipX = centerX + (int)(radiusPix * Math.cos(value.getTheta())); int arrowTipY = centerY (int)(radiusPix * Math.sin(value.getTheta())); g.fillOval(arrowTipX-3, arrowTipY-3, 7, 7); // Die Linie von der Mitte zum Punkt zeichnen // und Leerr[ume zulassen { g.drawLine(centerX, centerY, arrowTipX, arrowTipY); } } public void mousePressed(MouseEvent e) { value = xyToPolar(e.getX(), e.getY()); repaint(); }
342
public void mouseDragged(MouseEvent e) { dragging = true; value = xyToPolar(e.getX(), e.getY()); repaint(); notifyListeners();
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Component: die Komponente Polar
} public void mouseReleased(MouseEvent e) { dragging = false; value = xyToPolar(e.getX(), e.getY()); repaint(); notifyListeners(); }
private PolarPoint xyToPolar(int x, int y) { double newRho; double newTheta; int deltaX = x - getSize().width/2; int deltaY = getSize().height/2 - y; double deltaLen = Math.sqrt(deltaX*deltaX + deltaY*deltaY); double rho = unitsPerPixel * deltaLen; rho = Math.min(rho, maxRho); double theta = Math.atan2(deltaY, deltaX); while (theta < 0.0) theta += 2*Math.PI; return new PolarPoint(rho, theta); }
public void mouseClicked(MouseEvent e) { } public void mouseMoved(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public Dimension getMinimumSize() { return getPreferredSize(); } public Dimension getPreferredSize() { return new Dimension(50, 50); } } Die Komponentenklasse Polar kann nun wie jede andere Komponente verwendet werden. Im Folgenden finden Sie den Quellcode f.r das Testprogramm PolarTest. 343
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Listing 12.5
import java.awt.*; import java.applet.Applet;
PolarTest.java
public class PolarTest extends Applet implements PolarListener { private TextField tf; public void init() { setLayout(new BorderLayout()); Polar polar = new Polar(); polar.setShowText(true); polar.addPolarListener(this); add(polar, BorderLayout.CENTER); tf = new TextField(8243;RHO = 0.0 ... THETA = 0.08243;); tf.setEditable(false); add(tf, BorderLayout.SOUTH); } public void polarValueChanged(PolarEvent e) { tf.setText(e.getPolarValue().toString()); } } Listing 12.6 PolarTest.html
<TITLE>Polar Applet <APPLET code=PolarTest.class width=500 height=500> Erstellen Sie zum Kompilieren und Ausf.hren des Applets PolarTest die folgenden Dateien in einem beliebigen Texteditor: PolarPoint.java (Listing 12.1), PolarEvent.java (Listing 12.2), PolarListener.java (Listing 12.3), Polar.java (Listing 12.4), PolarTest.java (Listing 12.5) und PolarTest.html (Listing 12.6). Im Anschluss finden Sie ein Beispielskript f.r die Kompilierung. Bitte beachten Sie, dass beim Kompilieren von Polar.java die folgenden vier Klassen erzeugt werden: Polar.class, PolarEvent.class, PolarListener.class und PolarPoint.class. Nach der Kompilierung der Polar-Klassen kGnnen Sie das Applet PolarTest kompilieren und im Applet Viewer (siehe folgende Abbildung 12.4) ausf.hren.
344
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Gruppierung: die Komponente ThreeWay
C:\JavaDevHdbk\Ch03>javac Polar.java C:\JavaDevHdbk\Ch03>javac PolarTest.java C:\JavaDevHdbk\Ch03>appletviewer PolarTest.html
Abb. 12.4 Das Programm zum Testen von Polar
Gruppierung: die Komponente ThreeWay Manchmal ist beim Festlegen von Zahlen eine absolute Genauigkeit erforderlich, zuweilen gen.gt ein N$herungswert. Die Komponente Polar, die im vorherigen Abschnitt beschrieben wurde, ist naturgem$ß ungenau, weil sie mit Mauseingaben arbeitet und weil ihre AuflGsung auf eine RadiuserhGhung in Schritten von einem Pixel beschr$nkt ist. Im n$chsten Beispiel werden dem Benutzer drei unterschiedliche Genauigkeitsebenen f.r die Eingabe eines int-Wertes angeboten: die absolute Genauigkeit eines Eingabefeldes, die mittlere Genauigkeit eines Rollbalkens und die vage Genauigkeit einer Optionsfeldergruppe. Die weniger genauen Komponenten sind einfacher zu verwenden. Die zu entwickelnde Komponente heißt ThreeWay. Die Klasse ThreeWay geht etwas .ber die herkGmmlichen nummerischen Rollbalken mit einer Beschriftung und/oder einem schreibgesch.tzten Eingabefeld zur Anzeige des eingestellten Werts hinaus. Die neue Komponente soll drei MGglichkeiten, eine Zahl zu schreiben und drei MGglichkeiten, eine Zahl zu lesen, anbieten. Auch hier werden die weiter oben in diesem Kapitel beschriebenen Design.berlegungen als Ausgangspunkt herangezogen. 345
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Das Aussehen und die Bedienung der Komponente ThreeWay Da f.r die Komponente ThreeWay bekannte Elemente verwendet werden, sind die Entscheidungen, wie die Komponente angezeigt und wie ihre Werte festgelegt werden sollen, einfacher zu treffen als bei der Komponente Polar.
Werte anzeigen Zuerst muss .berlegt werden, wie die Komponente ThreeWay ihren Wert anzeigen soll. Unter den drei Elementen – Rollbalken, Eingabefeld und Optionsfeldergruppe – ist der Rollbalken das visuell vorherrschendste. Das Eingabefeld wird rechts neben dem Rollbalken angezeigt, um Bildschirmplatz zu sparen. Oberhalb des Rollbalkens befindet sich eine Zeile mit f.nf Optionsfeldern. Wenn der Wert der Komponente gleich oder beinahe gleich dem Minimalwert ist, wird das linke Optionsfeld aktiviert. Dieses Optionsfeld soll oberhalb des linken Endes des Rollbalkens positioniert werden. Das linke Ende stellt nat.rlich auch den Minimalwert des Rollbalkens dar. Das rechte Optionsfeld wird genau .ber dem rechten Ende des Rollbalkens angezeigt. Es wird aktiviert, wenn der Wert der Komponente gleich oder beinahe gleich dem Maximalwert ist. Die drei Optionsfelder in der Mitte sind f.r die Werte Halb, Viertel bzw. Dreiviertel des Bereichs der Komponente vorgesehen. Jede Unterkomponente gibt also auf bestimmte Weise den aktuellen Wert der Komponente ThreeWay wieder. Die folgende Abbildung 12.5 zeigt die Komponente ThreeWay.
Abb. 12.5 Die Komponente ThreeWay
Hinweis
346
Die obige Abbildung stammt von einem Windows-95-Rechner. Beachten Sie bitte, dass Rollbalken und Optionsfelder auf verschiedenen Plattformen sehr unterschiedlich dargestellt werden.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Gruppierung: die Komponente ThreeWay
Besonderheiten bei Rollbalken Auf Motif-Plattformen, wie z.B. Sun-Workstations, k"nnen sich die Schaltflchen mit den dreieckigen Pfeilen endlos vergr"ßern. Ein sehr hoher, horizontaler Rollbalken hat sehr hohe Pfeilschaltflchen. Die Schaltflchen sind gleichseitige Dreiecke, daher betrgt der Wert fr die Breite nur ungefhr 9/10 von der H"he. Dadurch wird der Platz belegt, der sonst fr den Regler zur Verfgung stehen wrde. Ein hoher Rollbalken k"nnte – unabhngig von seiner Breite – nutzlos sein, weil der Bereich des Reglers nur einige wenige Pixel umfasst. Daher ist es wichtig, die vertikale Ausdehnung eines horizontalen Rollbalkens zu begrenzen. Auf Windows-Plattformen werden die Pfeile des Rollbalkens vernnftig dargestellt. Sie befinden sich innerhalb von rechteckigen Schaltflchen. Auch wenn die Schaltflchen gr"ßer werden, bleibt die Pfeilgr"ße gleich. Ein hoher Rollbalken hat hohe, schmale Schaltflchen, die kleine Dreiecke enthalten.
Werte festlegen Nachdem die Art und Weise festgelegt ist, wie in der Komponente ThreeWay Werte angezeigt werden, kGnnen Sie nun entscheiden, wie Benutzer mit der neuen Komponente interagieren sollen. F.r dieses Beispiel bietet sich eine einfache LGsung an: Benutzer sollen den Rollbalken, das Eingabefeld und die Optionsfelder manipulieren kGnnen.
Eine Strategie fr das Ableiten der Unterklasse w5hlen Als N$chstes muss die Strategie f.r das Bilden der Unterklasse bestimmt werden. F.r dieses Beispiel war die beste Strategie von Anfang an klar: Von der Klasse Container wird eine Unterklasse abgeleitet, in die Standardunterkomponenten eingef.gt werden. Diese Gruppierungsstrategie ermGglicht es Benutzern, mit vertrauten Elementen zu arbeiten. Die Programmierung profitiert dabei von der Wiederverwendbarkeit von Objekten. Die Hauptanforderung ist, daf.r zu sorgen, dass die Unterkomponenten fehlerfrei miteinander interagieren und dies erfordert mehr, als vielleicht auf den ersten Blick zu erkennen ist. Die Komponenten werden durch Instanzvariablen mit den Namen textfield, scrollbar und checkboxes[] referenziert.
Das Design der Komponente ThreeWay Im Folgenden werden die verbleibenden Designanforderungen behandelt, anhand derer die Entwicklung der benutzerdefinierten Komponente ThreeWay fortgesetzt wird. 347
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Das Aussehen der Komponente 5ndern Als Erstes stellt sich die Frage, ob das Aussehen der Komponente programmtechnisch modifiziert werden kann. Genau wie in dem Beispiel f.r die Klasse Polar kGnnen die Methoden setForeground(), setBackground() und setFont() auch f.r Clients zug$nglich gemacht werden. Um setForeground() und setBackground() zu unterst.tzen, m.ssen einige Vorarbeiten geleistet werden. Da f.r das Layout der Unterkomponenten von ThreeWay am besten Panel-Komponenten eingesetzt werden, ergibt sich eine einfache Containerhierarchie, in der aber f.r jede Panel-Komponente eine eigene Vorder- und Hintergrundfarbe gesetzt werden muss. Bedenken Sie, dass dabei setForeground() .berschrieben werden muss. Man kGnnte setForeground() auch einfach f.r jede Komponente einzeln aufrufen. Bei diesem Vorgehen riskiert man jedoch Fehler bei der Programmpflege. Jemand kGnnte die Containerstruktur $ndern, eine neue Panel-Komponente einf.hren und vergessen, setForeground() entsprechend zu $ndern. Daher wird ein rekursiver Algorithmus f.r den Aufruf von setForeground() f.r alle Komponenten verwendet: public void setForeground(Color color) { super.setForeground(color); setForegndRecursive(this, color); } private void setForegndRecursive(Container parent, Color color) { Component children[] = parent.getComponents(); for (int i=0; i
Werte beschr5nken Als N$chstes m.ssen Sie .berlegen, wie die Eingabewerte eingegrenzt werden sollen. Die Klasse ThreeWay unterscheidet sich nicht sehr von der Klasse Scrollbar; deshalb muss es nat.rlich eine Ober- und eine Untergrenze geben. 348
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Gruppierung: die Komponente ThreeWay
Im Konstruktor von Scrollbar erscheint der Anfangswert vor dem Minimalwert, der wiederum vor dem Maximalwert steht. Diese Reihenfolge ist auch f.r den Konstruktor von ThreeWay angebracht. Die Aufrufsequenz des Konstruktors lautet: ThreeWay(int value, int minValue, int maxValue).
Werte speichern, setzen und abrufen Die n$chste Frage lautet, wie der Wert der Komponente gespeichert, gesetzt und abgerufen werden soll. Das Speichern und Abrufen des Werts ist recht einfach. Es gibt eine private Instanzvariable namens value und die public-Zugriffsmethode getValue(): private int value; . . . public int getValue() { return(value); } Wenn der Wert gesetzt wird, m.ssen die sichtbaren Unterkomponenten aktualisiert werden. Der Rollbalken und das Eingabefeld lassen sich sehr einfach setzen. F.r die Optionsfelder ist eine Instanzvariablen vom Typ float namens spreadPerRadio erforderlich. Anhand dieser Instanzvariable wird festgestellt, welches Optionsfeld aktiviert werden muss. Da es f.nf Optionsfelder gibt, lautet die Auswertung von spreadPerRadio (die im Konstruktor durchgef.hrt wird) folgendermaßen: spreadPerRadio = (float)(maxValue-minValue) / 4.0f; Die Methoden zum Aktualisieren der sichtbaren Komponenten lauten wie folgt: private void reviseScrollbar() { scrollbar.setValue(value); } private void reviseTextfield() { textfield.setText((new Integer(value)). toString()); } private void reviseRadios() { float f = (value - minValue) / spreadPerRadio; int nth = Math.round(f); if (nth < 0) { nth = 0; } else if (nth > 4)
349
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
{ nth = 4; } cbgroup.setSelectedCheckbox(checkboxes[nth]); }
Ereignisse behandeln Als N$chstes m.ssen Sie sich die Ereignisbehandlung .berlegen. Wenn eine der Unterkomponenten aktiviert wird, sollte die Komponente ThreeWay ein Ereignis an ihre Empf$nger senden. Bei der Komponente Polar des vorherigen Beispiels musste ein neuer Ereignistyp (zusammen mit einer neuen Empf$ngerschnittstelle) entwickelt werden, da das Ereignis Adjustment nicht ganz passend war. F.r die Komponente ThreeWay eignet sich das Ereignis Adjustment aber hervorragend, daher muss kein benutzerdefinierter Ereignistyp entwickelt werden. Die Klasse Container, von der ThreeWay abgeleitet ist, stellt jedoch keine Unterst.tzung f.r das Hinzuf.gen, Entfernen oder Benachrichtigen von Empf$ngern des Ereignisses Adjustment bereit. Daher muss diese Unterst.tzung explizit programmiert werden, $hnlich wie die Unterst.tzung f.r das Hinzuf.gen, Entfernen oder Benachrichtigen von Empf$ngern des Ereignisses Polar der Komponente Polar hinzugef.gt wurde. Den Code zur Ereignisunterst.tzung der Klasse ThreeWay finden Sie im Folgenden. Er ist fast identisch mit der Ereignisunterst.tzung f.r die Klasse Polar, der einzige Unterschied besteht nur darin, dass anstelle von Polar-Ereignissen Adjustment-Ereignisse gesendet werden. public void addAdjustmentListener( AdjustmentListener listener) { if (!listeners.contains(listener)) listeners.addElement(listener); }
public void removeAdjustmentListener( AdjustmentListener listener) { listeners.removeElement(listener); }
private void notifyListeners() { AdjustmentListener listener;
350
AdjustmentEvent event = new AdjustmentEvent( this, AWTEvent.RESERVED_ID_MAX+1,
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Gruppierung: die Komponente ThreeWay
AdjustmentEvent.ADJUSTMENT_VALUE_CHANGED, value); Vector copyOfListeners = (Vector)(listeners.clone()); Enumeration enum = copyOfListeners.elements(); while (enum.hasMoreElements()) { listener = (AdjustmentListener )enum.nextElement(); listener.adjustmentValueChanged(event); } } Wenn in einer Komponente eine Eingabe vorgenommen wird, m.ssen auch alle anderen Komponenten aktualisiert werden. Den Code zur Unterst.tzung dieses Verhaltens finden Sie im Folgenden. In diesem Code ist das einzig Bemerkenswerte die Methode ActionPerformed(), die aufgerufen wird, wenn der Benutzer im Eingabefeld die Taste dr.ckt. Da nicht-nummerische Eingaben ohne Bedeutung sind, .berpr.ft der Code, ob die Eingabe g.ltig ist. Falls die Eingabe kein positiver Integerwert ist, wird der vorherige Wert wiederhergestellt. private void reviseScrollbar() { bar.setValue(value); } private void reviseTextfield() { textfield.setText((new Integer(value) ).toString()); } private void reviseRadios() { float f = (value - minValue) / spreadPerRadio; int nth = Math.round(f); if (nth < 0) { nth = 0; } else if (nth > 4) { nth = 4; } cbgroup.setSelectedCheckbox(checkboxes[nth]); }
//
351
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
// Wird aufgerufen, wenn der Rollbalken bewegt // wird // public synchronized void adjustmentValueChanged( (AdjustmentEvent e) { value = e.getValue(); reviseRadios(); reviseTextfield(); notifyListeners(); }
// // Wird aufgerufen, wenn ein Optionsfeld // angeklickt wird // public synchronized void itemStateChanged( ItemEvent e) { int newValue = 0; // Nur auf das markierte Optionsfeld reagieren if (e.getStateChange() != ItemEvent.SELECTED) return; // Neuen Wert feststellen for (int i=0; i
// // Wird aufgerufen, wenn der Benutzer im // Eingabefeld die Taste ENTER dr:ckt // public synchronized void actionPerformed( ActionEvent e) { 352
// Nur g:ltige nummerische Eingaben zulassen
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Gruppierung: die Komponente ThreeWay
int newValue = 0; try { newValue = Integer.parseInt(textfield .getText()); } catch (NumberFormatException x) { textfield.setText(8243;8243; + value); return; } // Innerhalb der Begrenzungen ausrichten newValue = Math.min(newValue, maxValue); newValue = Math.max(newValue, minValue); value = newValue; reviseScrollbar(); reviseRadios(); reviseTextfield(); notifyListeners(); } Die abschließenden
Verhalten in einer Multithread-Umgebung Bei der Komponente ThreeWay wird genauso wie bei der Komponente Polar der Vektor der Empf$nger geklont und die Benachrichtigung .ber den Klon statt .ber das Original vorgenommen. Außer dem Vektor der Empf$nger gibt es keine anderen Daten, die von externen Objekten ver$ndert werden (und Fehler produzieren) kGnnten, daher sind in einer Multithread-Umgebung keine Probleme zu erwarten.
Verhalten bei GrBßen5nderungen Was soll bei einer GrGßen$nderung der Komponente ausgef.hrt werden? Bei der Klasse Polar musste der Skalierungsfaktor angepasst werden. Hier gibt es keinen vergleichbaren internen Zustand, der aktualisiert werden m.sste. Die Bildlaufleiste verf.gt .ber einen internen Skalierungsfaktor, der aber von der Klasse Scrollbar automatisch verwaltet wird. Die einzig verbleibende Aufgabe ist, daf.r zu sorgen, dass das Layout unabh$ngig von der GrGße der Komponente ThreeWay ansprechend bleibt. Das bedeutet, dass setSize() nicht .berschrieben werden muss, aber es muss eine robuste Containerhierarchie im Konstruktor erstellt werden. Wie zu Beginn dieses Abschnitts erw$hnt sollte die vertikale Ausdehnung von Bildlaufleisten beschr$nkt werden. Wenn die Komponente ThreeWay dagegen horizontal vergrGßert wird, m.ssen die neuen Pixel an die Bildlaufleiste .bergeben werden. Das Eingabefeld ist immer breit genug und es bringt keinen Vorteil, es zu vergrG353
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
ßern. Diese Vorgaben legen den LayoutmanagerBorderLayout nahe, wobei die Bildlaufleiste im unteren Bereich (S.den) positioniert werden soll. Andererseits muss die Zeile mit den Optionsfeldern .ber der Bildlaufleiste ausgerichtet werden und das spricht f.r ein Gitterlayout mit zwei Zeilen in einer Spalte. Wie meistens gibt es mehrere gangbare LGsungen. In diesem Beispiel werden die Optionsfelder in eine Panel-Komponente (namens cboxPanel) eingef.gt und cboxPanel wird oberhalb des Rollbalkens in einem Gitter mit zwei Zeilen und einer Spalte platziert. In Kapitel 4: Textfelder, KontrollkGstchen und Layoutmanager verwenden finden Sie n$here Informationen zu Layoutmanagern. cboxPanel verwendet zum Positionieren der Optionsfelder die Klasse GridBagLayout. Diese bewirkt, dass die Optionsfelder jeweils zentriert im unteren Bereich der Zelle platziert werden. An dieser Stelle entspricht die Struktur der folgenden Abbildung 12.6.
Abb. 12.6 Die Struktur von cboxSbarPanel
Der obigen Abbildung kGnnen Sie eine neue informelle Namensvergabe entnehmen. Jeder Container ist an der oberen linken Seite beschriftet. Die Beschriftung hat das folgende Format: name @ position u layout, wobei name das Handle der Container-Instanzvariablen ist, position die Position innerhalb des eigenen Containers (z.B. N f.r North in einem Rahmen oder 2,1 f.r Spalte 2 und Zeile 1 in einem Gitter) ist, u als Abk.rzung von uses verwendet wird und layout den Typ des vom Container verwendeten Layoutmanagers (B f.r BorderLayout, G f.r GridLayout, F f.r FlowLayout, GB f.r GridBagLayout oder C f.r CardLayout) angibt. Die Beschriftung von nicht-Container-Komponenten wird zentriert dargestellt, sofern sie .berhaupt beschriftet werden. 354
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Gruppierung: die Komponente ThreeWay
cboxSbarPanel stellt sicher, dass die Optionsfelder oberhalb der Bildlaufleiste positioniert bleiben. Die Optionsfelder werden im unteren Bereich (South) einer anderen Panel-Komponente angezeigt, deren einzige Aufgabe darin besteht, eine vertikale GrGßen$nderung von cboxSbarPanel zu verhindern. Diese neue Komponente heißt restrictorPanel und enth$lt nur ein einziges untergeordnetes Objekt. restrictorPanel wird in die Mitte der ThreeWay-Komponente gesetzt, so dass eine GrGßen$nderung in beide Richtungen mGglich bleibt. Eine horizontale GrGßen$nderung wird an cboxSbarPanel .bergeben. Bei einer vertikalen GrGßen$nderung wird die HGhe von restrictorPanel, aber nicht von btnSbarPanel angepasst. btnSbar Panel verbleibt am unteren Rand von restrictorPanel. Die sich aus diesen
Abb. 12.7 Die detaillierte Struktur von cboxSbarPanel
Das Eingabefeld sollte im rechten Bereich (East) angezeigt werden, damit dessen GrGße horizontal eingegrenzt wird. Es w$re aber auch gut, wenn es etwas weiter unten (South) dargestellt w.rde, damit es horizontal mit der Bildlaufleiste ausgerichtet ist. Dazu ist eine weitere Panel-Komponente, n$mlich tfPanel, erforderlich. Falls tfPanel mehrere Komponenten enthalten soll, w$re ein GridBagLayout-Manager angebracht, aber in diesem einfachen Fall gen.gt ein Rahmen mit dem Eingabefeld im unteren Bereich von tfPanel. Damit ist das Design der Containerhierarchie (siehe folgende Abbildung 12.8) der Komponente ThreeWay abgeschlossen.
355
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Abb. 12.8 Die vollst7ndige Containerstruktur von ThreeWay
Die Komponente ThreeWay und das Test-Applet Im Folgenden finden Sie das vollst$ndige Listing f.r das Beispiel ThreeWay.java: Listing 12.7 ThreeWay.java
import java.awt.*; import java.awt.event.*; import java.util.*; public class ThreeWay extends Container implements ActionListener, AdjustmentListener, ItemListener, Adjustable { private Vector listeners = new Vector(); private Scrollbar bar; private TextField textfield; private CheckboxGroup cbgroup; private Checkbox checkboxes[]; private int minValue; private int maxValue; private int value; private float spreadPerRadio;
356
public ThreeWay(int value, int minValue, int maxValue) { this.minValue = minValue; this.maxValue = maxValue;
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Gruppierung: die Komponente ThreeWay
this.value = value; setLayout(new BorderLayout()); // Eingabefeld erstellen und hinzuf:gen Panel tfPanel = new Panel(); tfPanel.setLayout(new BorderLayout()); textfield = new TextField(8243; 8243; + value); textfield.addActionListener(this); tfPanel.add(textfield, BorderLayout.SOUTH); add(tfPanel, BorderLayout.EAST); // Optionsfelder erstellen und hinzuf:gen Panel cboxPanel = new Panel(); GridBagLayout gbl = new GridBagLayout(); cboxPanel.setLayout(gbl); GridBagConstraints gbc = new GridBagConstraints(); gbc.gridwidth = gbc.gridheight = 1; gbc.weightx = gbc.weighty = 1; gbc.gridy = 0; gbc.fill = GridBagConstraints.NONE; gbc.anchor = GridBagConstraints.SOUTH; checkboxes = new Checkbox[5]; cbgroup = new CheckboxGroup(); for (int i=0; i<5; i++) { checkboxes[i] = new Checkbox(8243;", (i==0), cbgroup); checkboxes[i].addItemListener(this); gbc.gridx = i; if (i > 2) gbc.anchor = GridBagConstraints.SOUTHEAST; gbl.setConstraints(checkboxes[i], gbc); cboxPanel.add(checkboxes[i]); } spreadPerRadio = (float)(maxValue-minValue) / 4.0f; // Rollbalken erstellen und hinzuf:gen Panel cboxSbarPanel = new Panel(); cboxSbarPanel.setLayout(new GridLayout(2, 1)); cboxSbarPanel.add(cboxPanel); bar = new Scrollbar(Scrollbar.HORIZONTAL, value, 0, minValue, maxValue); bar.addAdjustmentListener(this); cboxSbarPanel.add(bar); Panel restrictorPanel = new Panel(); restrictorPanel.setLayout(new BorderLayout()); restrictorPanel.add(cboxSbarPanel, BorderLayout.SOUTH); add(restrictorPanel, BorderLayout.CENTER);
357
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
// Alle untergeordneten Komponenten mit dem // aktuellen Wert aktualisieren reviseScrollbar(); reviseTextfield(); reviseRadios(); } public int getValue() { return(value); } public synchronized void setValue(int newValue) { value = newValue; reviseScrollbar(); reviseTextfield(); reviseRadios(); notifyListeners(); } public void addAdjustmentListener( AdjustmentListener listener) { if (!listeners.contains(listener)) listeners.addElement(listener); } public void removeAdjustmentListener( AdjustmentListener listener) { listeners.removeElement(listener); } private void notifyListeners() { AdjustmentListener listener;
358
AdjustmentEvent event = new AdjustmentEvent( this, 0, 0, value); Vector copyOfListeners = (Vector)(listeners.clone()); Enumeration enum = copyOfListeners.elements(); while (enum.hasMoreElements()) { listener = (AdjustmentListener) enum.nextElement(); listener.adjustmentValueChanged(event); }
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Gruppierung: die Komponente ThreeWay
} private void reviseScrollbar() { bar.setValue(value); } private void reviseTextfield() { textfield.setText((new Integer(value)). toString()); } private void reviseRadios() { float f = (value - minValue) / spreadPerRadio; int nth = Math.round(f); if (nth < 0) { nth = 0; } else if (nth > 4) { nth = 4; } cbgroup.setSelectedCheckbox(checkboxes[nth]); }
// // Wird aufgerufen, wenn der Rollbalken bewegt // wird // public synchronized void adjustmentValueChanged( AdjustmentEvent e) { value = e.getValue(); reviseRadios(); reviseTextfield(); notifyListeners(); } // // Wird aufgerufen, wenn ein Optionsfeld // angeklickt wird // public synchronized void itemStateChanged( ItemEvent e) {
359
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
// Nur auf das aktivierte Optionsfeld reagieren if (e.getStateChange() != ItemEvent.SELECTED) return; // Neuen Wert feststellen int newValue = minValue; for (int i=0; i
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Gruppierung: die Komponente ThreeWay
/* * Hintergrundfarbe aller Komponenten setzen, * indem sie in diesem Container und rekursiv * f:r alle untergeordneten Komponenten gesetzt * wird */ public void setBackground(Color color) { super.setBackground(color); setBackgndRecursive(this, color); } private void setBackgndRecursive(Container parent, Color color) { Component children[] = parent.getComponents(); for (int i=0; i
361
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
} } } public void setFont(Font font) { textfield.setFont(font); } // // Methoden der Schnittstelle Adjustable // public int getBlockIncrement() { return (int)spreadPerRadio; } public int getMaximum() { return maxValue; } public int getMinimum() { return minValue; } public int getOrientation() { return Scrollbar.HORIZONTAL; } public int getUnitIncrement() { return bar.getUnitIncrement(); } public int getVisibleAmount() { return bar.getVisibleAmount(); } public void setBlockIncrement(int b) { } public void setMaximum(int m) {} public void setMinimum(int m) {} public void setUnitIncrement(int m) { } public void setVisibleAmount(int v) { bar.setVisibleAmount(v); } } Da die gesamte Komplexit$t innerhalb von ThreeWay gekapselt wurde, ist die Klasse sehr einfach einzusetzen. Der folgende Applet-Code aktualisiert – jedes Mal, wenn der Benutzer die Schaltfl$che Show anklickt – das Eingabefeld mit dem Wert der ThreeWay-Komponente. Das Applet ist in der folgenden Abbildung 12.9 dargestellt.
Abb. 12.9 Ein einfaches Applet, das die Komponente ThreeWay verwendet
362
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Gruppierung: die Komponente ThreeWay
import java.awt.*; import java.awt.event.*; import java.applet.Applet;
Listing 12.8 ThreeWayTest.java
public class ThreeWayTest extends Applet implements ActionListener { ThreeWay threeway; Button button; TextField textfield; public void init() { setLayout(new BorderLayout()); threeway = new ThreeWay(23, 0, 100); threeway.setFont(new Font(8243;Helvetica8243;, Font.ITALIC, 14)); add(threeway, BorderLayout.NORTH); Panel panel = new Panel(); button = new Button(8243;Show8243;); button.addActionListener(this); panel.add(button); textfield = new TextField(8243;Value = 238243;, 15); panel.add(textfield); add(panel, BorderLayout.SOUTH); } public void actionPerformed(ActionEvent e) { textfield.setText(8243;Value = 8243; + threeway.getValue()); } }
<TITLE>Three Way Test Applet <APPLET code=ThreeWayTest.class width=500 height=500>
Listing 12.9 ThreeWayTest.html
Kompilieren Sie das folgende Ausgabelisting:
363
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
C:\JavaDevHdbk\Ch03>javac ThreeWay.java C:\JavaDevHdbk\Ch03>javac ThreeWayTest.java C:\JavaDevHdbk\Ch03>appletviewer ThreeWayTest.html
Unterklassen von Standardkomponenten: Textfelder mit G,ltigkeitspr,fung Bei vielen Programmen ist es erforderlich, die Benutzereingaben auf deren G.ltigkeit zu .berpr.fen. Beispielsweise kann es sein, dass die Eingaben nummerisch sein sollen, in einem bestimmten Bereich liegen oder mit einer bestimmten Eingabemaske .bereinstimmen m.ssen. Da in der Standardklasse AWT TextField keine G.ltigkeitspr.fungen vorgesehen sind, m.ssen Sie zum Hinzuf.gen dieser Funktion eine benutzerdefinierte Komponente erstellen. Zu diesem Zweck werden im folgenden Beispiel zwei Alternativen entwickelt: eine einfache IntTextField-Komponente zur
Das Aussehen und die Bedienung der Komponente IntTextField Werte in Eingabefeldern mit G.ltigkeitspr.fung sollten genau so wie in herkGmmlichen Eingabefeldern angezeigt werden und Benutzer sollten neue Werte durch Eintippen eingeben kGnnen. Daher liegt die Strategie zur Bildung der Unterklasse auf der Hand: Eine einfache TextField-Komponente muss erweitert werden. Als Erstes wird von TextField eine Unterklasse namens IntTextField abgeleitet, die nur positive, ganzzahlige Eingabewerte akzeptiert.
Das Design der Komponente IntTextField Im Folgenden wird – genau wie bei den vorhergehenden Beispielen – die Eingabekomponente mit G.ltigkeitspr.fung anhand der Liste der Design.berlegungen entwickelt.
Das Aussehen der Komponente 5ndern Das programmtechnische Modifizieren der Darstellung einer IntTextField-Komponente ist unkompliziert. Da die IntTextField-Komponente eine einfache Unterklasse ist, erbt sie von TextField die Methoden setForeground(), setBackground() und setFont(). Diese Methoden arbeiten ohne zus$tzliche Anpassungen auch bei der Komponente IntTextField ordnungsgem$ß.
364
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Standardkomponenten
Werte beschr5nken Die n$chste Frage ist, wie Werte beschr$nkt werden sollen. Vorerst soll die einzige Beschr$nkung darin bestehen, dass die Klasse nur positive, ganzzahlige Werte zul$sst. Wenn der Benutzer die Taste dr.ckt, wird die Eingabe .berpr.ft. Falls der eingegebene Wert nicht mit den Pr.fkriterien .bereinstimmt, wird die Komponente auf den vorherigen Wert zur.ckgesetzt.
Werte speichern, setzen und abrufen Die Komponente muss jeweils den letzten g.ltigen Wert speichern, damit sie auf diesen Wert zur.ckgesetzt werden kann, falls der Benutzer eine ung.ltige Eingabe macht. Der letzte g.ltige Wert wird in einer Instanzvariablen vom Typ int mit dem Namen intValue gespeichert. Der Wert der Komponente kann entweder als Ganzzahl oder als Textstring gesetzt werden. Zum Setzen des Wertes als Ganzzahl verf.gt die Klasse .ber die Methode setIntValue(), die ein Argument des Typs int .bernimmt und keine Aktion ausf.hrt, wenn das Argument negativ ist. Zum Setzen des Wertes als String wird die Methode setText() .berschrieben; wiederum f.hrt die Methode nichts aus, wenn das Argument nicht g.ltig ist. Beide Methoden aktualisieren den Text der Komponente als auch die Instanzvariable intValue. Im Folgenden finden Sie den Code der beiden Methoden: public void setText(String s) { // Auf g:ltige, positive Ganzzahl pr:fen int newintval = 0; try { newintval = Integer.parseInt(s); } catch (NumberFormatException nfex) { return; } if (newintval < 0) { return; } // Bei g:ltiger Eingabe, Wert und Eingabefeld // aktualisieren intValue = newintval; super.setText(8243;8243; + newintval); } public void setIntValue(int newintval) { if (newintval < 0) 365
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
{ return; } intValue = newintval; super.setText(8243;8243; + newintval); } Der Wert kann durch Aufruf der geerbten Methode getText() als String abgerufen werden. Wahrscheinlich w.rde dieser String aber sofort in einen int-Wert umgewandelt, daher muss eine weitere Zugriffsmethode, n$mlich getIntValue(), vorgesehen werden: public int getIntValue() { return intValue; }
Ereignisse behandeln Nun muss der Typ des Ereignisses, das die Komponente IntTextField senden soll, festgelegt werden. Programmierer erwarten, dass Eingabefelder Action-Ereignisse senden, wenn der Benutzer die Taste dr.ckt. Daher sollte die Komponente IntTextField auch so arbeiten. Die neue Komponente sollte eigene Aktionsereignisse erkennen und deren Inhalte auf G.ltigkeit pr.fen. Wenn die Inhalte zul$ssig sind, m.ssen alle Empf$nger von Action benachrichtigt werden. Wenn die Inhalte ung.ltig sind, muss der Wert zur.ckgesetzt werden. Dies ist eine gute Gelegenheit, den Mechanismus der expliziten Ereigniserkennung einzusetzen. Die Klasse IntTextField kann Action-Ereignisse erkennen und die Benachrichtigung der Empf$nger nur f.r den Fall anfordern, dass die Eingabe g.ltig ist. Die Anforderung der Empf$ngerbenachrichtigung wird einfach durch einen Aufruf der geerbten Version der Ereignisbehandlungsmethode ausgef.hrt. Als Erstes muss die Erkennung von Action-Ereignissen im Konstruktor ermGglicht werden. Die Klasse IntTextField stellt zwei Konstruktoren zur Verf.gung: einen der nur einen Anfangswert und einen der sowohl einen Anfangswert als auch eine Breitenangabe festlegt: public IntTextField(int val) { super((new Integer(val<0 ? 0 : val)).toString()); intValue = val; enableEvents(AWTEvent.KEY_EVENT_MASK); } public IntTextField(int val, int width) { 366
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Standardkomponenten
super((new Integer(val<0 ? 0 : val)).toString(), width); intValue = val; enableEvents(AWTEvent.ACTION_EVENT_MASK); } Wenn der abgefragte Wert negativ ist, setzen beide Konstruktoren den Anfangswert auf Null. Die
367
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Verhalten in Multithread-Umgebungen und bei GrBßen5nderungen F.r dieses Beispiel ist das Verhalten der Komponente in Multithread-Umgebungen und bei GrGßen$nderungen belanglos. Der Vorteil des Vererbens von Verhaltensweisen von einer vorgefertigten Oberklasse liegt gerade darin, dass f.r diese Thematik bereits Vorkehrungen getroffen wurden. Sie brauchen sich darum also nicht mehr zu k.mmern.
Die Komponente IntTextField Im Folgenden finden Sie das vollst$ndige Listing des Beispiels IntTextField.java: Listing 12.10
import java.awt.*; import java.awt.event.*;
IntTextField.java
public class IntTextField extends TextField { private int intValue; public IntTextField(int val) { super((new Integer(val<0 ? 0 : val)).toString()); intValue = val; enableEvents(AWTEvent.KEY_EVENT_MASK); } public IntTextField(int val, int width) { super((new Integer(val<0 ? 0 : val)).toString(), width); intValue = val; enableEvents(AWTEvent.ACTION_EVENT_MASK); } public synchronized void setText(String s) { // Auf g:ltige, positive Ganzzahl pr:fen int newintval = 0; try { newintval = Integer.parseInt(s); } catch (NumberFormatException nfex) { return; } if (newintval < 0) { 368
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Standardkomponenten
return; } // Bei g:ltiger Eingabe, Wert und Eingabefeld // aktualisieren intValue = newintval; super.setText(8243;8243; + newintval); } public synchronized void setIntValue(int newintval) { if (newintval < 0) { return; } intValue = newintval; super.setText(8243;8243; + newintval); } public int getIntValue() { return intValue; } public void processActionEvent(ActionEvent e) { int newintval = 0; boolean trouble = false; try { newintval = Integer.parseInt(getText()); if (newintval < 0) { trouble = true; } } catch (NumberFormatException nfex) { trouble = true; } // Bei ung:ltigem Zeichen, r:ckg[ngig machen if (trouble) { setText(8243;8243; + intValue); } // Wenn alles in Ordnung ist
369
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
else { intValue = newintval; super.processActionEvent(e); } } }
Externe Gltigkeitsprfung: die Komponente ValidatingTextField Die Unterklasse IntTextField akzeptiert nur positive, ganzzahlige Werte. Das n$chste Beispiel zeigt eine Variation davon, die auch andere Eingabeformate .berpr.fen kann. F.r die Komponente ValidatingTextField wird die G.ltigkeitspr.fung von einem externen Objekt bereitgestellt. Dazu wird eine weitere Variable im Konstruktor .bergeben, anhand derer die G.ltigkeitspr.fung vorgenommen wird, sobald der Benutzer die Taste dr.ckt. Verliert das den Fokus, zum Beispiel durch Anklicken eines anderen Textfeldes, wird die Validierung nicht durchgef.hrt. Das
public interface Validator { public boolean validate(String s); } Die Klasse ValidatingTextField entspricht der Klasse IntTextField, weist aber die folgenden Unterschiede auf: Die neue Klasse verf.gt .ber eine private-Instanzvariable vom Typ Validator, die im Konstruktor bereitgestellt wird. Wenn ein Action-Ereignis erkannt wird, wird dieses Objekt hinzugezogen. Es wird kein int-Wert mitgef.hrt, da die Klasse mGglicherweise keinen intWert repr$sentiert. Stattdessen verf.gt die Klasse .ber die Stringvariable lastValidTextValue, die nur zum Wiederherstellen des vorherigen Wertes verwendet wird, falls eine ung.ltige Eingabe vorliegt. Im Folgenden finden Sie den Quellcode der Klasse ValidatingTextField.
Listing 12.12
import java.awt.*; import java.awt.event.*;
ValidatingTextField.java
public class ValidatingTextField extends TextField 370
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Standardkomponenten
{ private Validator validator; private String lastValidTextValue; public ValidatingTextField(String value, int width, Validator validator) { super(value, width); lastValidTextValue = value; this.validator = validator; if (!validator.validate(value)) { lastValidTextValue = 8243;8243;; setText(lastValidTextValue); } enableEvents(AWTEvent.ACTION_EVENT_MASK); } public void processActionEvent(ActionEvent e) { String textValue = getText(); if (!validator.validate(textValue)) { // Ung:ltige Eingabe. Text zur:cksetzen, // Empf[nger nicht benachrichtigen setText(lastValidTextValue); return; } // G:ltige Eingabe. Empf[nger benachrichtigen lastValidTextValue = textValue; super.processActionEvent(e); } }
Eingabefelder auf Gltigkeit prfen: Test-Applet und die Validator-Klassen Die folgende Abbildung zeigt ein Applet, das drei benutzerdefinierte Komponenten enth$lt. Die oberste ist eine Instanz von IntTextField. Die anderen beiden Komponenten sind Instanzen von ValidatingTextField. Das mittlere Eingabefeld .berpr.ft die G.ltigkeit des Formats einer US-Sozialversicherungsnummer (wie z.B. 123-45-6789). Das untere Eingabefeld pr.ft die Eingabe einer Gleitkommazahl.
371
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
Abb. 12.10 Drei Unterklassen von TextField
Die drei folgenden Listings enthalten den Quellcode, der die Eingabefelder der obigen Abbildung erzeugt, und die beiden Pr.fklassen SSNValidator.java (Listing 12.14) sowie FloatValidator.java (Listing 12.15). Außerdem sind dazu auch die Listings IntTextField.java (Listing 12.10), Validator.java (Listing 12.11), ValidatingTextField.java (Listing 12.12) und ValTextFieldTest.html (Listing 12.16) erforderlich. Listing 12.13
import java.awt.*; import java.applet.Applet;
ValTextFieldTest.java
public class ValTextFieldTest extends Applet { public void init() { setSize(420, 120); setLayout(new GridLayout(3, 1)); Panel p = new Panel(); p.add(new Label(8243;INT TEXTFIELD:8243;)); p.add(new IntTextField(4321, 6)); add(p); p = new Panel(); p.add(new Label(8243;SSN TEXTFIELD:8243;)); Validator val = new SSNValidator(); p.add(new ValidatingTextField(8243;111-11-11118243;, 12, val)); add(p); p = new Panel(); p.add(new Label(8243;FLOAT TEXTFIELD:8243;)); val = new FloatValidator(); p.add(new ValidatingTextField(8243;123.45678243;, 12, val)); add(p); } } 372
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Unterklassen von Standardkomponenten
/* * Eine Routine, die :berpr:ft, ob das Format * eines Strings einer g:ltigen US-Sozialver* sicherungsnummer entspricht. Das Format lautet: * drei Ziffern, ein Bindestrich, zwei Ziffern, * ein Bindestrich und vier Ziffern, 123-45-6789. */
Listing 12.14 SSNValidator.java
public class SSNValidator implements Validator { public boolean validate(String s) { char ch; if (s.length() != 11) // L[nge des Strings // :berpr:fen return false; for (int i=0; i<11; i++) { ch = s.charAt(i); if (i == 3 || i == 6) // Bindestrich erwartet { if (ch != '-') { return(false); // Kein Bindestrich } } else if (!Character.isDigit(ch)) // Ziffer // erwartet { return(false); // Keine Ziffer } } return true; } }
// G:ltig
/* * Eine Routine, die :berpr:ft, ob ein String * einen g:ltigen Gleitkommawert darstellt. */ public class FloatValidator implements Validator { public boolean validate(String s) {
Listing 12.15 FloatValidator.java
373
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Benutzerdefinierte Komponenten
try { Float.valueOf(s); } catch (NumberFormatException x) { return false; // Ung:ltig } return true; } }
Listing 12.16 ValTextFieldTest.html
// G:ltig
<TITLE>Validating Text Field Test Applet <APPLET code=ValTextFieldTest.class width=500 height=500> Kompilieren Sie das folgende Ausgabelisting: C:\JavaDevHdbk\Ch03>javac IntTextField.java.java C:\JavaDevHdbk\Ch03>javac ValidatingTextField.java C:\JavaDevHdbk\Ch03>javac ValTextFieldTest.java C:\JavaDevHdbk\Ch03>appletviewer ValTextFieldTest.html
Ausblick In Kapitel 13: JFC-Swing-Komponenten werden die Komponenten von Java-Swing einschließlich von Frames, Men.s, Labels, Schaltfl$chen, Passwortfeldern und Symbolleisten behandelt.
374
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
Ein Beispielprogramm mit Swing-Komponenten
376
Erweiterte Komponenten
384
Neue Komponenten
390
Das Programm SwingDemo
396
Ausblick
404
13
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
Aus „Java 2 Developer’s Handbook“ von Philip Heller und Simon Roberts, ISBN 0-7821-2179-9, 1248 Seiten, $59,99
Der Funktionsumfang der Java Foundation Classes (JFC) umfasst ein Ereignisverbreitungsmodell, die Unterst.tzung von Drag&Drop und von Eingabehilfen f.r kGrperbehinderte Menschen, eine Vielzahl von r$tselhaften Spezialfunktionen sowie ein Paket mit Komponenten, die Swing-Komponenten oder Swing Set genannt werden. Das Swing Set f.r sich genommen ist schon $ußerst umfangreich. Das Package javax.swing enth$lt neun untergeordnete Packages, .ber 20 Schnittstellen und mehr als 75 Klassen. Nat.rlich kGnnen im Rahmen dieses Buches nicht alle Einzelheiten behandelt werden; dazu w$ren mehrere Hundert Seiten erforderlich! Das Wichtigste, was Sie sich bei Swing-Komponenten merken sollten, ist, dass sie nur Komponenten sind. Sie sind kompliziert, sie haben zahlreiche Funktionen und es gibt viele davon, aber es handelt sich nur um Komponenten. Als Java-Programmierer verf.gen Sie bereits .ber eine ausgiebige Erfahrung im Umgang mit Komponenten. Dieses Kapitel stellt die folgenden allgemeinen Swing-Komponenten vor: JFrame JToggleButton JSlider JTabbedPane JcheckBox JPasswordField JTextField JRadioButton JToolbar JButton JComboBox JTable JLabel
Ein Beispielprogramm mit Swing-Komponenten Als Einf.hrung in die Programmierung mit Swing-Komponenten wird ein einzelnes großes Programm untersucht. Die Anwendung SwingDemo zeigt mehrere Komponententypen an, die in acht Registerseiten organisiert sind. Die Registerseiten erscheinen in einem Frame (siehe folgende Abbildung 13.1). Als Erstes f$llt an dem SwingDemo-Fenster auf, dass es anscheinend einen vollkommen normalen Frame zeigt, das wiederum ein vollkommen normales Eingabefeld zu enthalten scheint. Tats$chlich ist das Frame aber eine Instanz der Klasse JFrame und das Textfeld eine Instanz der Klasse JTextField. Alle gel$ufigen AWT-Komponenten haben entsprechende JFC-Klassen. Im Allgemeinen gibt es vier Unterschiede zwischen AWT- und JFC-Klassen:
376
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ein Beispielprogramm mit Swing-Komponenten
Abb. 13.1 Das Programm SwingDemo
Die Namen der JFC-Klassen beginnen mit dem Buchstaben J. JFC-Klassen stellen normalerweise mehr Funktionen als die entsprechenden AWT-Klassen zur Verf.gung. Die JFC-Klassen befinden sich in dem Package javax.swing. Die JFC-Klassen umgehen den AWT-Peer-Mechanismus. Beispielsweise ist die JFC-Entsprechung der Klasse java.awt.Button die Klasse javax..swing.JButton. Diese Klasse hat keinen Peer. JButton interagiert direkt mit der Maus und dem Bildschirm und hat auf allen Plattformen dasselbe Aussehen und dasselbe Verhalten.
JFC-Eingabefelder, -Frames und -Mens Der Konstruktor des SwingDemo-Programms f.gt dem Frame eine JTextField-Komponente hinzu. Die Methode main und der Konstruktor lauten folgendermaßen: public static void main(String[] args) { SwingDemo that = new SwingDemo(); that.setVisible(true); } public SwingDemo() { super(8243;Swing Demo8243;); setSize(450, 350); 377
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); textfield = new JTextField(); getContentPane().add(textfield, BorderLayout.SOUTH); addMenu(); tabbedPane = new JTabbedPane(); populateTabbedPane(); getContentPane().add(tabbedPane, BorderLayout.CENTER); } Beachten Sie, dass die Klasse JFrame eine Containerebene mehr als die AWT-Klasse Frame aufweist. JFrame enth$lt einen untergeordneten Container, den so genannten ContentPane, auf den durch Aufruf der Methode getContentPane() zugegriffen werden kann. Untergeordnete Komponenten sollten immer in den ContentPane statt direkt in JFrame eingef.gt werden. Nach einem JTextField wird dem Frame ein JTappedPane (Registerbereich) hinzugef.gt und es werden im Konstruktor zwei Methoden aufgerufen, die ein Men. einf.gen und einen Registerbereich f.llen. Beide Methoden erscheinen im SwingDemo-Programm direkt unterhalb des Konstruktors. Die Methode addMenu() f.gt das Men. File mit zwei Optionen hinzu (siehe folgende Abbildung 13.2):
Abb. 13.2 Das Men1 File mit zwei Optionen
378
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ein Beispielprogramm mit Swing-Komponenten
Der Code f.r die Methode addMenu() lautet wie folgt: private void addMenu() { JMenuBar mbar = new JMenuBar(); JMenu menu = new JMenu(8243;File8243;); menu.add(new JCheckBoxMenuItem(8243;Check Me8243;)); menu.addSeparator(); JMenuItem item = new JMenuItem(8243;Exit8243;); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } }); menu.add(item); mbar.add(menu); setJMenuBar(mbar); } Auch wenn Sie nichts .ber JFC-Komponenten wissen, ist dieser Code leicht verst$ndlich. Wenn Sie sich die Buchstaben J aus den Klassennamen wegdenken, erhalten Sie klassischen AWT-Code. Beachten Sie, dass auch JMenuItem – genau wie MenuItem – einen ActionListener-Empf$nger aufnehmen kann. Dem Men.eintrag JCheckBoxMenuItem kGnnte auch der Empf$nger ItemListener wie bei CheckBoxMenuItem zugewiesen werden.
JFC-Registerseiten Der SwingDemo-Konstruktor ruft die Methode addTabbedPane() auf. Diese Methode erzeugt und installiert JTabbedPane. Der Code f.r addTabbedPane() lautet wie folgt: private void populateTabbedPane() { // addTab(Beschriftung, Symbol, Komponente, // Kurzhinweis) tabbedPane.addTab(8243;Button8243;, new TabIcon(), new ButtonPanel(textfield), 8243;Click here for Button demo8243;); tabbedPane.addTab(8243;Label8243;, null, new LabelPanel(), 8243;Click here for Label demo8243;); tabbedPane.addTab(8243;Toggles8243;, 379
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
null, new TogglePanel(textfield), 8243;Click here for Toggle demo8243;); tabbedPane.addTab(8243;Combo Box8243;, null, new ComboPanel(textfield), 8243;Click here for Combo Box demo8243;); tabbedPane.addTab(8243;Slider8243;, null, new SliderPanel(textfield), 8243;Click here for Slider demo8243;); tabbedPane.addTab(8243;Password8243;, null, new PasswordPanel(), 8243;Click here for Password Field demo8243;); tabbedPane.addTab(8243;Toolbar8243;, null, new ToolbarPanel(), 8243;Click here for Toolbar demo8243;); tabbedPane.addTab(8243;Table8243;, null, new TablePanel(), 8243;Click here for Table demo8243;); }
Register hinzufgen Eine JTabbedPane-Komponente ist eine Art Container, dessen Inhalt auf einer Registerseite dargestellt wird. Die Methode addTab() wird .berladen. Die f.r dieses Beispiel verwendete Version erh$lt vier Argumente: einen String, der als Beschriftung der hinzuzuf.genden Registerseite angezeigt wird ein optionales Symbol (null gibt an, dass kein Symbol angezeigt werden soll) die Komponente, die angezeigt wird, wenn die Registerseite geGffnet wird einen String, der als Kurzhinweis erscheint
380
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ein Beispielprogramm mit Swing-Komponenten
Einem Register ein Symbol hinzufgen Im Code f.r die Registerseiten wird nur im ersten Aufruf der Methode addTab() ein Symbol festgelegt. Das Symbol wird auf dem Reiter der Seite Button (siehe die Abbildung 13.1) angezeigt. Ein Symbol ist eine Instanz der Klasse, die die Schnittstelle Icon implementiert. Diese verf.gt .ber drei Methoden: public int getIconWidth() – gibt die Breite des Symbols in Pixel zur.ck public int getIconHeight() – gibt die HGhe des Symbols in Pixel zur.ck public void paintIcon(Component c, Graphics g, int x, int y) – zeichnet das Symbol beginnend bei (x,y) innerhalb der Komponente c unter Verwendung der Oberklasse Graphics Das Symbol, das im Reiter Button erscheint, ist eine Instanz von TabIcon, einer inneren Klasse der Klasse SwingDemo: class TabIcon implements Icon { public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } public void paintIcon(Component c, Graphics g, int x, int y) { g.setColor(Color.black); g.fillRect(x+4, y+4, getIconWidth()-8, getIconHeight()-8); g.setColor(Color.cyan); g.fillRect(x+6, y+6, getIconWidth()-12, getIconHeight()-12); } }
Die Klasse SwingDemo Im Folgenden finden Sie den Quellcode f.r die Klasse SwingDemo. Das vollst$ndige Programm ist am Ende dieses Kapitels abgedruckt. import javax.swing.*; import java.awt.*; import java.awt.event.*;
Listing 13.1 SwingDemo.java
public class SwingDemo extends JFrame { private JTextField textfield; private JTabbedPane tabbedPane; public static void main(String[] args) {
381
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
SwingDemo that = new SwingDemo(); that.setVisible(true); } public SwingDemo() { super(8243;Swing Demo8243;); setSize(450, 350); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); textfield = new JTextField(); getContentPane().add(textfield, BorderLayout.SOUTH); // tabbedPane enth[lt die Panels f:r die // verschiedenen J-Komponenten tabbedPane = new JTabbedPane(); populateTabbedPane(); getContentPane().add(tabbedPane, BorderLayout. CENTER); addMenu(); } private void addMenu() { JMenuBar mbar = new JMenuBar(); JMenu menu = new JMenu(8243;File8243;); menu.add(new JCheckBoxMenuItem(8243;Check Me8243;)); menu.addSeparator(); JMenuItem item = new JMenuItem(8243;Exit8243;); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } }); menu.add(item); mbar.add(menu); setJMenuBar(mbar); } private void populateTabbedPane() { 382
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ein Beispielprogramm mit Swing-Komponenten
// addTab(Beschriftung, Symbol, Komponente, // Kurzhinweis) tabbedPane.addTab(8243;Button8243;, new TabIcon(), new ButtonPanel(textfield), 8243;Click here for Button demo8243;); tabbedPane.addTab(8243;Label8243;, null, new LabelPanel(), 8243;Click here for Label demo8243;); tabbedPane.addTab(8243;Toggles8243;, null, new TogglePanel(textfield), 8243;Click here for Toggle demo8243;); tabbedPane.addTab(8243;Combo Box8243;, null, new ComboPanel(textfield), 8243;Click here for Combo Box demo8243;); tabbedPane.addTab(8243;Slider8243;, null, new SliderPanel(textfield), 8243;Click here for Slider demo8243;); tabbedPane.addTab(8243;Password8243;, null, new PasswordPanel(), 8243;Click here for Password Field demo8243;); tabbedPane.addTab(8243;Toolbar8243;, null, new ToolbarPanel(), 8243;Click here for Toolbar demo8243;); tabbedPane.addTab(8243;Table8243;, null, new TablePanel(), 8243;Click here for Table demo8243;); } class TabIcon implements Icon { public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } public void paintIcon(Component c, Graphics g,
383
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
int x, int y) { g.setColor(Color.black); g.fillRect(x+4, y+4, getIconWidth()-8, getIconHeight()-8); g.setColor(Color.cyan); g.fillRect(x+6, y+6, getIconWidth()-12, getIconHeight()-12); } } } In den restlichen Abschnitten dieses Kapitels werden die verschiedenen Registerseiten untersucht, die im Programm SwingDemo erscheinen. Jede Registerseite illustriert eine oder mehrere JFC-Komponenten. Einige der Komponenten auf den Registerseiten melden Status$nderungen an das Feld JTextField, das im unteren Bereich des Frames angezeigt wird.
Erweiterte Komponenten Dieser Abschnitt behandelt Labels und einige Variationen von Schaltfl$chen: einfache Schaltfl$chen, Kippschalter und Kontrollfelder. Dazu werden die Registerseiten Label, Button und Toggles des Programms SwingDemo n$her betrachtet.
JFC-Labels Die Registerseite Label (siehe folgende Abbildung 13.3) ist extrem einfach aufgebaut. Sie enth$lt nur eine einzelne Instanz der Klasse JLabel, wie Sie dem folgenden Listing entnehmen kGnnen.
Abb. 13.3 Die Registerseite Label des Programms SwingDemo
384
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Erweiterte Komponenten
public class LabelPanel extends JPanel { public LabelPanel() { add(new JLabel(8243;Just a label8243;)); } }
Listing 13.2 LabelPanel.java
In dieser einfachsten Form zeigt eine JLabel-Komponente nur eine Textmeldung an. Die Klasse JLabel ermGglicht aber auch, ein optionales Symbol hinzuzuf.gen und die relativen Positionen des Textes und des Symbols festzulegen.
JFC-Schaltfl5chen Die Registerseite Button (siehe Abbildung 13.1) demonstriert, dass die Klasse JButton mehr Funktionen als die Klasse java.awt.Button zur Verf.gung stellt. Im Folgenden finden Sie den Quellcode f.r ButtonPanel. import javax.swing.*; import java.awt.*; import java.awt.event.*;
Listing 13.3 ButtonPanel.java
public class ButtonPanel extends JPanel { private JTextField log; public ButtonPanel(JTextField tf) { this.log = tf; // Schaltfl[che erstellen JButton btn = new JButton(8243;Push Me8243;, new BoxIcon(Color.blue, 2)); // Alternative Symbole setzen btn.setRolloverIcon(new BoxIcon(Color.cyan, 3)); btn.setPressedIcon(new BoxIcon(Color.yellow, 4)); // Text links vom Symbol setzen btn.setHorizontalTextPosition(JButton.LEFT); // Rahmen setzen btn.setBorder(BorderFactory. createEtchedBorder()); // Empf[nger setzen btn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) 385
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
{ log.setText(8243;Button was pressed.8243;); } }); // Visueller Hinweis: Ende der anonymen, // inneren Klasse // Schaltfl[che in die Registerseite einf:gen add(btn); } class BoxIcon implements Icon { private Color color; private int borderWidth; BoxIcon(Color color, int borderWidth) { this.color = color; this.borderWidth = borderWidth; } public int getIconWidth() { return 20; } public int getIconHeight() { return 20; } public void paintIcon(Component c, Graphics g, int x, int y) { g.setColor(Color.black); g.fillRect(x, y, getIconWidth(), getIconHeight()); g.setColor(color); g.fillRect(x + borderWidth, y + borderWidth, getIconWidth() - 2*borderWidth, getIconHeight() - 2*borderWidth); } } } Die innere KlasseBoxIcon ist eine weitere Implementierung der Schnittstelle Icon, die zuvor in diesem Kapitel besprochen wurde. Der Konstruktor BoxIcon .bernimmt zwei Argumente: die Farbe und die Rahmenbreite. Der in diesem Beispiel verwendete Konstruktor JButton enth$lt zwei Argumente: einen Textstring und ein Standardsymbol. Das Standardsymbol ist blau mit einem zwei Pixel breiten schwarzen Rahmen. Der Konstruktoraufruf von JButton lautet: JButton btn = new JButton("Push Me", new BoxIcon(Color.blue, 2)); 386
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Erweiterte Komponenten
F.r eine JButton-Schaltfl$che kGnnen alternative Symbole f.r den jeweiligen Zustand der Schaltfl$che festgelegt werden. Der Beispielcode stellt zwei alternative Symbole bereit: ein Symbol, das auf der Schaltfl$che angezeigt wird, wenn der Cursor .ber die Schaltfl$che bewegt wird, und ein weiteres, das eingeblendet wird, wenn die Schaltfl$che gedr.ckt wird. Der Code zur Festlegung dieser Symbole lautet: // Alternative Symbole festlegen btn.setRolloverIcon(new BoxIcon(Color.cyan, 3)); btn.setPressedIcon(new BoxIcon(Color.yellow, 4)); Der Text der Schaltfl$che soll links neben dem Symbol angezeigt werden. Dies wird durch den folgenden Aufruf erreicht: btn.setHorizontalTextPosition(JButton.LEFT); Die Klasse JButton verf.gt .ber mehrere Konstanten, mit denen die Position des Textes relativ zum Symbol festgelegt werden kann. Die Schaltfl$che soll einen gewGlbten Rahmen erhalten. Dieser Rahmen wird mit folgendem Aufruf erzeugt: btn.setBorder(BorderFactory.createEtchedBorder()); Die Klasse BorderFactory enth$lt mehrere createXXXBorder()-Methoden zum Festlegen von verschiedenen Rahmenarten. Beachten Sie bitte den Kommentar am Ende des Konstruktors: // Visueller Hinweis: Ende der anonymen // inneren Klasse Anonyme innere Klassen stellen eine n.tzliche Technik zum Hinzuf.gen von leichtgewichtigen (lightweight) Empf$ngern zu einer Komponente dar. Die Syntax mutet zun$chst etwas eigent.mlich an. Es gibt zwei MGglichkeiten eine anonyme innere Klasse zu erkennen: Die Klassendefinition erscheint an der Position, an der Sie eine Objektreferenz erwarten w.rden. Anstelle einer Referenz steht new Typename(), wobei Typename entweder ein Name einer Klasse oder einer Schnittstelle ist. Die Klassendefinition endet mit });. Diese Kombination kommt in Java nur am Ende der Definition einer anonymen inneren Klasse vor.
JFC-Kippschalter und Kontrollfelder Die Registerseite Toggles (siehe folgende Abbildung 13.4) zeigt die Schaltfl$che Toggle, ein einfaches Kontrollfeld und drei Kontrollfelder, die als Optionsfeldergruppe angeordnet sind. 387
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
Abb. 13.4 Die Registerseite Toggles des Programms SwingDemo
Der vollst$ndige Code f.r die Klasse TogglePanel lautet folgendermaßen: Listing 13.4
import java.awt.event.*; import javax.swing.*;
TogglePanel.java
public class TogglePanel extends JPanel { private JTextField log; public TogglePanel(JTextField tf) { this.log = tf; // Schaltfl[che Toggle JToggleButton tog = new JToggleButton(8243;Toggle8243;); ItemListener listener = new ItemListener() { public void itemStateChanged(ItemEvent e) { AbstractButton src = (AbstractButton)(e.getSource()); log.setText(8243;Toggle: 8243; + src.getText()); } }; tog.addItemListener(listener); add(tog); // Kontrollfeld JCheckBox cbox = new JCheckBox(8243;Checkbox8243;); cbox.addItemListener(listener); 388
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Erweiterte Komponenten
add(cbox); // Optionsfelder ButtonGroup btngroup = new ButtonGroup(); for (int i=1; i<=3; i++) { JRadioButton radio = new JRadioButton(8243;Radio 8243; + i); btngroup.add(radio); radio.addItemListener(listener); add(radio); } } } Status$nderungen werden von JToggleButtons .ber das Ereignis ItemEvent an ItemListener gesendet. Die Schaltfl$che Toggle wird mit dem folgenden Code erzeugt: // Schaltfl[che Toggle JToggleButton tog = new JToggleButton(8243;Toggle8243;); ItemListener listener = new ItemListener() { public void itemStateChanged(ItemEvent e) { AbstractButton src = (AbstractButton)(e.getSource()); log.setText(8243;Toggle: 8243; + src.getText()); } }; tog.addItemListener(listener); add(tog); Die Komponente JCheckbox unterscheidet sich von der Komponente JToggleButton nur in ihrem Aussehen und in der Darstellung der Status$nderung. Eine JToggleButton-Komponente sieht wie eine Schaltfl$che aus und der Status der Komponente wird durch eine Snderung der Darstellung der gesamten Schaltfl$che angezeigt. Die JCheckbox-Komponenten besteht aus einer Beschriftung neben einem Ankreuzk$stchen. Der Status dieser Komponente wird durch das Anzeigen eines H$kchens in dem Ankreuzk$stchen bzw. durch das Entfernen dieses H$kchens dargestellt. Der folgende Code erzeugt die erste JCheckbox-Komponente auf der Registerseite: // Kontrollfeld JCheckBox cbox = new JCheckBox(8243;Checkbox8243;); cbox.addItemListener(listener); add(cbox);
389
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
Die drei JRadioButton-Komponenten auf der rechten Seite der Registerseite Toggles sind als Optionsfeldergruppe organisiert. Die Gruppe wird von folgendem Code erzeugt: // Optionsfelder ButtonGroup btngroup = new ButtonGroup(); for (int i=1; i<=3; i++) { JRadioButton radio = new JRadioButton(8243;Radio 8243; + i); btngroup.add(radio); radio.addItemListener(listener); add(radio); }
Neue Komponenten Das restliche Kapitel befasst sich mit vier neuen JFC-Komponenten, die keine Entsprechung zu AWT-Komponenten haben: einem Kombinationsfeld, das die Funktionalit$t der AWT-Komponente Choice erweitert einem Schieberegler, der die Funktionalit$t der AWT-Komponente Scrollbar erweitert einem Passwortfeld, das eine sichere MGglichkeit der Eingabe von Textdaten bereitstellt einer Symbolleiste, die weitere Komponenten enth$lt und die Positionierung zur Laufzeit unterst.tzt Alle diese Komponententypen erscheinen in der Anwendung SwingDemo auf einer eigenen Registerseite.
JFC-Kombinationsfelder Die Klasse JComboBox entspricht weitgehend der Klasse java.awt.Choice. Der Benutzer wird aufgefordert, einen Eintrag aus einer Liste auszuw$hlen, wobei die Liste nur w$hrend des Ausw$hlens eingeblendet wird. Genau wie die Klasse Choice benachrichtigt die Klasse JComboBox die ItemListener-Empf$nger .ber Aktivit$ten durch Senden des Ereignisses ItemEvent. Der Hauptunterschied besteht darin, dass der Programmierer bei der Klasse JComboBox die Anzahl der sichtbaren Eintr$ge in der Liste festlegen kann. Wenn die Liste mehr als die Anzahl der sichtbaren Eintr$ge enth$lt, wird sie mit einer Bildlaufleiste angezeigt. 390
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Neue Komponenten
Die Anzahl der sichtbaren Eintr$ge in einer JComboBox-Komponenten wird nach deren Erzeugung durch Aufruf der Methode setMaximumRowCount() gesetzt. Die folgende Abbildung zeigt ein Kombinationsfeld mit f.nf sichtbaren Eintr$gen und einer Bildlaufleiste.
Abb. 13.5 Die Registerseite Combo Box des Programms SwingDemo
Im Folgenden finden Sie den Quellcode f.r die Registerseite Combo Box des Programms SwingDemo. import java.awt.event.*; import javax.swing.*;
Listing 13.5 ComboPanel.java
public class ComboPanel extends JPanel { private JTextField log; final static String[] treasure = { 8243;Gold8243;, 8243;Silver8243;, 8243;Diamonds8243;, 8243;Rubies8243;, 8243;Emeralds8243;, 8243;Sapphires8243;, 8243;Chocolate8243;}; public ComboPanel(JTextField tf) { this.log = tf; final JComboBox combo = new JComboBox(treasure); combo.setMaximumRowCount(5); combo.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) 391
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
{ log.setText(8243;Combo: 8243; + combo.getSelectedItem()); } }); add(combo); } }
JFC-Schieberegler Die Klasse JSlider erweitert die Funktionalit$t der Klasse java.awt.Scrollbar betr$chtlich. Die Klasse JSlider unterst.tzt beispielsweise sowohl große als auch kleine Schrittweiten. Die Schrittweite (in Pixel) kann durch Aufruf der Methoden setMajorTickSpacing(int) und setMinorTickSpacing(int) gesetzt werden. Die Anzeige von Schrittweiten m.ssen Sie durch Aufruf der Methode setPaintTicks(true) explizit aktivieren.
Tipp
Nat.rlich verf.gen JSlider-Komponenten .ber Funktionen, die weit .ber das Setzen der Schrittweite hinausgehen. Ausf.hrliche Informationen .ber die Funktionen von JSlider- und die anderen Swing-Komponenten finden Sie in den jeweiligen KlassenAPIs. Wenn ein Benutzer den Regler einer JSlider-Komponente bewegt, sendet die Komponente ChangeEvent-Ereignisse an die ChangeListener-Empf$nger. Der Code f.r die Klasse SliderPanel erzeugt die in der folgenden Abbildung 13.6 dargestellte Registerseite.
Abb. 13.6 Die Komponente JSlider
392
Im Folgenden finden Sie den Quellcode f.r die Klasse SliderPanel.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Neue Komponenten
import java.awt.event.*; import javax.swing.*; import javax.swing.event.*;
Listing 13.6 SliderPanel.java
public class SliderPanel extends JPanel { JTextField log; JSlider slider; public SliderPanel(JTextField tf) { this.log = tf; slider = new JSlider(JSlider.HORIZONTAL, -50, 50, 35); slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); slider.setPaintTicks(true); slider.setPaintLabels(true); add(slider, BorderLayout.NORTH); slider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { log.setText(8243;Slider: 8243; + slider.getValue()); } }); } }
JFC-Passwortfelder Ein JPasswordField-Feld ist einfach ein JTextField-Feld, das unabh$ngig von den Eingaben des Benutzers immer dasselbe Zeichen anzeigt. Dieses Zeichen wird Echozeichen genannt. Es kann durch Aufruf der Methode setEchoChar(char) gesetzt werden. Die folgende Abbildung zeigt JPasswordField-Feld, f.r das als Echozeichen das Pfundzeichen (#) festgelegt wurde. Im Folgenden finden Sie den Quellcode f.r die Klasse PasswordPanel: import javax.swing.*;
Listing 13.7
public class PasswordPanel extends JPanel { public PasswordPanel() { // Ein zehn Zeichen breites Feld erstellen
PasswordPanel.java
393
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
JPasswordField pwf = new JPasswordField(10); pwf.setEchoChar('#'); add(pwf); } }
Abb. 13.7 Die Komponente JPasswordField
JFC-Symbolleisten Eine JToolBar-Komponente ist ein rechteckiger Bereich, der andere Komponenten enthalten kann. Benutzer kGnnen eine JToolBar-Komponente von dem Fenster, in dem sie platziert ist, abkoppeln und als eigenst$ndiges Fenster auf den Desktop verlagern. Eine JToolBar-Komponente kann auch in einem anderen Bereich des urspr.nglichen Fensters positioniert werden. Die folgende Abbildung 13.8 zeigt eine JFC-Symbolleiste, die drei Kontrollfelder enth$lt. Jedes Kontrollfeld sendet das Ereignis ActionEvent an einen ActionListener-Empf$nger, der das Ereignis in der JTextField-Komponente protokolliert. Den Code f.r die Klasse ToolbarPanel finden Sie im Folgenden.
394
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Neue Komponenten
Abb. 13.8 Die Komponente JToolBar mit drei Kontrollfeldern
Die n$chste Abbildung 13.9 zeigt dieselbe Symbolleiste, nachdem sie von einem Benutzer abgekoppelt wurde.
Abb. 13.9 Eine abgekoppelte JToolBar-Komponente
import javax.swing.*; import java.awt.*;
Listing 13.8 ToolbarPanel.java
public class ToolbarPanel extends JPanel { public ToolbarPanel() { setLayout(new BorderLayout()); JToolBar toolbar = new JToolBar(); // Drei Kontrollfelder (JCheckBox) in die // Symbolleiste einf:gen for (int i=1; i<4; i++) { toolbar.add(new JCheckBox("Checkbox #" + i)); }
395
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
add(toolbar, BorderLayout.NORTH); } }
Das Programm SwingDemo Im Folgenden finden Sie das vollst$ndige Listing f.r die Anwendung SwingDemo. Listing 13.9 Beispielprogramm SwingDemo
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class SwingDemo extends JFrame { private JTextField textfield; private JTabbedPane tabbedPane; public static void main(String[] args) { SwingDemo that = new SwingDemo(); that.setVisible(true); } public SwingDemo() { super("Swing Demo"); setSize(450, 350); // _ber die Titelleiste gegebenen Schließen// Befehl beachten addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // Im Textfeld im unteren Bereich werden // Stausmeldungen angezeigt. Die J-Komponenten // verwenden es f:r Meldungen von Ereignissen. textfield = new JTextField(); getContentPane().add(textfield, BorderLayout.SOUTH); // tabbedPane enth[lt die Registerseiten f:r // die verschiedenen J-Komponenten tabbedPane = new JTabbedPane();
396
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Programm SwingDemo
populateTabbedPane(); getContentPane().add(tabbedPane, BorderLayout.CENTER); addMenu(); } private void addMenu() { JMenuBar mbar = new JMenuBar(); JMenu menu = new JMenu("File"); menu.add(new JCheckBoxMenuItem("Check Me")); menu.addSeparator(); JMenuItem item = new JMenuItem("Exit"); item.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.exit(0); } }); menu.add(item); mbar.add(menu); setJMenuBar(mbar); } private void populateTabbedPane() { // addTab(Beschriftung, Symbol, Komponente, // Kurzhinweis) tabbedPane.addTab("Button", new TabIcon(), new ButtonPanel(textfield), "Click here for Button demo"); tabbedPane.addTab("Label", null, new LabelPanel(), "Click here for Label demo"); tabbedPane.addTab("Toggles", null, new TogglePanel(textfield), "Click here for Toggle demo"); tabbedPane.addTab("Combo Box", null, new ComboPanel(textfield), "Click here for Combo Box demo"); tabbedPane.addTab("Sliders", null,
397
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
new SliderPanel(textfield), "Click here for Slider demo"); tabbedPane.addTab("Password", null, new PasswordPanel(), "Click here for Password Field demo"); tabbedPane.addTab("Toolbar", null, new ToolbarPanel(), "Click here for Toolbar demo"); tabbedPane.addTab("Table", null, new TablePanel(), "Click here for Table demo"); } // // Dieses Symbol wird auf dem Reiter Button // angezeigt // class TabIcon implements Icon { public int getIconWidth() { return 16; } public int getIconHeight() { return 16; } public void paintIcon(Component c, Graphics g, int x, int y) { g.setColor(Color.black); g.fillRect(x+4, y+4, getIconWidth()-8, getIconHeight()-8); g.setColor(Color.cyan); g.fillRect(x+6, y+6, getIconWidth()-12, getIconHeight()-12); } } }
Listing 13.10 ButtonPanel.java
import javax.swing.*; import java.awt.*; import java.awt.event.*; public class ButtonPanel extends JPanel { private JTextField log; public ButtonPanel(JTextField tf)
398
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Programm SwingDemo
{ this.log = tf; // Schaltfl[che erstellen JButton btn = new JButton("Push Me", new BoxIcon(Color.blue, 2)); // Alternative Symbole setzen btn.setRolloverIcon(new BoxIcon(Color.cyan, 3)); btn.setPressedIcon(new BoxIcon(Color.yellow, 4)); // Text links vom Symbol setzen btn.setHorizontalTextPosition(JButton.LEFT); // Rahmen setzen btn.setBorder(BorderFactory. createEtchedBorder()); // Empf[nger setzen btn.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { log.setText("Button was pressed."); } }); // Visueller Hinweis: Ende der anonymen, // inneren Klasse // Schaltfl[che in die Registerseite einf:gen add(btn); }
// // Die innere Klasse erzeugt ein 20x20 Pixel // breites Symbol mit einem schwarzen Rahmen. Die // Farbe des Rahmens und der Innenfl[che werden // im Konstruktor festgelegt. // class BoxIcon implements Icon { private Color color; private int borderWidth; BoxIcon(Color color, int borderWidth) { this.color = color; this.borderWidth = borderWidth; } 399
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
public int getIconWidth() { return 20; } public int getIconHeight() { return 20; } public void paintIcon(Component c, Graphics g, int x, int y) { g.setColor(Color.black); g.fillRect(x, y, getIconWidth(), getIconHeight()); g.setColor(color); g.fillRect(x + borderWidth, y + borderWidth, getIconWidth() - 2*borderWidth, getIconHeight() - 2*borderWidth); } } }
Listing 13.11
import javax.swing.*; LabelPanel.java
public class LabelPanel extends JPanel { public LabelPanel() { add(new JLabel("Just a label")); } }
Listing 13.12
import java.awt.event.*; import javax.swing.*;
TogglePanel.java
public class TogglePanel extends JPanel { private JTextField log; public TogglePanel(JTextField tf) { this.log = tf; // Schaltfl[che Toggle JToggleButton tog = new JToggleButton("Toggle"); // Empf[nger f:r alle drei Variationen ItemListener listener = new ItemListener() { public void itemStateChanged(ItemEvent e) { AbstractButton src = 400
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Programm SwingDemo
(AbstractButton)(e.getSource()); log.setText("Toggle: " + src.getText()); } }; tog.addItemListener(listener); add(tog); // Kontrollfeld JCheckBox cbox = new JCheckBox("Checkbox"); cbox.addItemListener(listener); add(cbox); // Optionsfelder ButtonGroup btngroup = new ButtonGroup(); for (int i=1; i<=3; i++) { JRadioButton radio = new JRadioButton("Radio " + i); btngroup.add(radio); radio.addItemListener(listener); add(radio); } } }
import java.awt.event.*; import javax.swing.*;
Listing 13.13 ComboPanel.java
public class ComboPanel extends JPanel { private JTextField log; // Initialisierungsstrings final static String[] treasure = { "Gold", "Silver", "Diamonds", "Rubies", "Emeralds", "Sapphires", "Chocolate"}; public ComboPanel(JTextField tf) { this.log = tf; // Erstellen. Muss final sein, damit die // innere Klasse darauf zugreifen kann final JComboBox combo = new JComboBox(treasure); combo.setMaximumRowCount(5); // Das Kombinationsfeld entspricht // java.awt.Choice: es sendet ItemEvent
401
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
combo.addItemListener(new ItemListener() { public void itemStateChanged(ItemEvent e) { log.setText("Combo: " + combo.getSelectedItem()); } }); add(combo); } }
Listing 13.14 SliderPanel.java
import java.awt.*; import javax.swing.*; import javax.swing.event.*; public class SliderPanel extends JPanel { private JTextField log; private JSlider slider; public SliderPanel(JTextField tf) { this.log = tf; setLayout(new BorderLayout()); slider = new JSlider(JSlider.HORIZONTAL, -50, 50, 35); // Große und kleine Schrittweiten zeichnen slider.setMajorTickSpacing(20); slider.setMinorTickSpacing(5); slider.setPaintTicks(true); slider.setPaintLabels(true); add(slider, BorderLayout.NORTH); slider.addChangeListener(new ChangeListener() { public void stateChanged(ChangeEvent e) { log.setText("Value = " + slider.getValue()); } }); } }
Listing 13.15 PasswordPanel.java
402
import javax.swing.*; public class PasswordPanel extends JPanel { public PasswordPanel() { // Ein zehn Zeichen breites Feld erstellen
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das Programm SwingDemo
JPasswordField pwf = new JPasswordField(10); pwf.setEchoChar('#'); add(pwf); } }
import javax.swing.*; import java.awt.*;
Listing 13.16 ToolbarPanel.java
public class ToolbarPanel extends JPanel { public ToolbarPanel() { setLayout(new BorderLayout()); JToolBar toolbar = new JToolBar(); // Drei Kontrollfelder (JCheckBox) in die // Symbolleiste einf:gen for (int i=1; i<4; i++) { toolbar.add(new JCheckBox("Checkbox #" + i)); } add(toolbar, BorderLayout.NORTH); } }
import java.awt.event.*; import javax.swing.*;
Listing 13.17 TablePanel.java
public class TablePanel extends JPanel { public TablePanel() { String[] columnTitles = { "col1", "col2", "col3" }; String[][] rows = { { "AAA", "Bbb", "Ccc" }, { "ddd", "EeE", "FfF" }, { "GGG", "HHH", "iii" }, { "jjj", "KKk", "LLL" }, { "Mmm", "NNN", "OoO" } };
403
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JFC-Swing-Komponenten
add(new JTable(rows, columnTitles)); } }
Ausblick In Kapitel 14: Threads und Multithreading werden Threads und Multithreads einschließlich des Erzeugens und Steuerns von Threads, der Kommunikation zwischen Threads, der Thread-Synchronisierung, der Thread-Priorit$t und der Zuteilung von Rechenzeiten behandelt.
404
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
4berblick ber das Multithreading
406
Thread-Grundlagen
410
Multithreading fr Fortgeschrittene
423
Ausblick
451
14
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
Aus „Mastering Java 2“ von John Zukowski, ISBN 0-7821-2180-2, 1280 Seiten, $49,99
Alle bisherigen Beispielprogramme bestanden nur aus einem einzigen Thread (single-threaded), d.h., es gab nur einen Ausf.hrungsstrang. Wenn die Programmausf.hrung blockiert ist, w$hrend auf die Beendigung einer I/O-Operation gewartet wird, kann auch kein anderer Teil des Programms ausgef.hrt werden. Benutzer von modernen Betriebssystemen sind heutzutage aber daran gewGhnt, mehrere Programme zu starten und gleichzeitig auszuf.hren, selbst wenn f.r die Ausf.hrung aller Anwendungen nur eine CPU zur Verf.gung steht. Durch das so genannte Multithreading ist es mGglich, mehrere Tasks zugleich innerhalb eines einzelnen Programms auszuf.hren. Multithreading bietet einen zweifachen Vorteil. Erstens nutzen Programme mit mehreren Threads die Systemressourcen – einschließlich der CPU – im Allgemeinen besser aus, weil ein anderer Ausf.hrungsstrang auf die CPU zugreifen kann, falls der erste blockiert ist. Zweitens kGnnen mehrere Threads viele Probleme besser lGsen. Wie kGnnten Sie beispielsweise ein Einzel-Thread-Programm schreiben, das gleichzeitig Animationen zeigen, Musik wiedergeben, Dokumente anzeigen und Dateien aus dem Netzwerk herunterladen kann? Java wurde von Anfang an f.r das Multithreading entwickelt: Zum einen ist die Unterst.tzung von Multithreading in die Java-Sprache integriert und ermGglicht somit das einfache Erstellen von robusten Multithread-Anwendungen. Zum anderen ist auch die virtuelle Java-Maschine auf das Multithreading angewiesen, um einer Anwendung gleichzeitig mehrere Dienste – z.B. zur Speicherbereinigung – zur Verf.gung zu stellen. In diesem Kapitel werden Sie erfahren, wie Sie Ihre Java-Programme Multithread-f$hig machen kGnnen.
8berblick ,ber das Multithreading Ein Thread ist ein einzelner Ablauf innerhalb eines Programms. Er wird manchmal auch Ausfhrungskontext genannt, weil jeder Thread .ber eigene Ressourcen – wie Programmz$hler und Ausf.hrungsstack – als Kontext f.r die Ausf.hrung verf.gen muss. Trotzdem nutzen alle Threads in einem Programm viele Ressourcen gemeinsam, wie z.B. den Arbeitsspeicher und geGffnete Dateien. Daher kann ein Thread auch als leichtgewichtiger Prozess (Lightweight Process) bezeichnet werden. Ein Thread stellt wie ein Prozess (oder ein laufendes Programm) einen einzigen Ablaufstrang dar, ist aber einfacher zu erzeugen und freizugeben als ein Prozess, da weniger Ressourcen verwaltet werden m.ssen.
Tipp
Die Begriffe parallel und gleichzeitig tauchen in der Computerliteratur h$ufig auf und der Unterschied zwischen ihnen wird oft nicht deutlich. Wenn zwei Threads parallel laufen, werden sie zur selben Zeit in verschiedenen CPUs ausgef.hrt. Zwei gleichzeitige Threads dagegen befinden sich beide in der Bearbeitung oder versuchen, zugleich CPU-Zeit f.r die Ausf.hrung zu erhalten, werden aber nicht notwendigerweise simultan auf verschiedenen CPUs ausgef.hrt. Ein Programm kann einen Großteil seiner Ausf.hrungszeit mit Warten verbringen. Es kGnnte z.B. bei einer I/O-Operation auf das Freiwerden von Ressourcen oder auf eine Unterbrechung warten, damit mit dem Zeichnen der n$chsten Szene einer
406
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
4berblick ber das Multithreading
Animationsfolge begonnen werden kann. Um die Auslastung der CPU zu verbessern, kGnnen alle Tasks mit potenziell langen Wartezeiten als separate Threads ausgef.hrt werden. Wenn ein Task auf etwas Bestimmtes wartet, kann die Java-Runtime-Maschine ein anderer ausf.hrbarer Task f.r die Ausf.hrung ausw$hlen. Das folgende Beispiel demonstriert den Unterschied zwischen einem Programm mit einem einzelnen Thread und dessen Multithread-Gegenst.ck. Im ersten Programm wird die Methode run() in der Klasse NoThreadPseudoIO erzeugt, um eine zehnsek.ndige I/O-Operation zu simulieren. Das Hauptprogramm wird zun$chst die simulierte I/O-Operation ausf.hren und dann eine andere Task starten. Die Methode showElapsedTime() zeigt die seit dem Programmstart vergangene Zeit in Sekunden zusammen mit einer Meldung an. Die Methode currentTimeMillis() der Klasse System des java.lang-Packages gibt einen Integerwert des Typs long in Millisekunden f.r die Zeitdifferenz zwischen der aktuellen Zeit und dem 1. Januar 1970, 00:00:00 GMT zur.ck. Im Folgenden finden Sie das Programm mit dem EinzelThread. import java.io.*; public class WithoutThread { static PrintWriter out = new PrintWriter (System.out, true);
Listing 14.1 WithoutThread.java
public static void main (String args[]) { // Erste Task: eine Pseudo-I/O-Operation NoThreadPseudoIO pseudo = new NoThreadPseudoIO(); pseudo.run(); // Zweite Task: eine zuf[llige Task showElapsedTime ("Andere Task gestartet"); } static long baseTime = System.currentTimeMillis(); // Die seit dem Programmstart vergangene // Zeit anzeigen static void showElapsedTime (String message) { long elapsedTime = System.currentTimeMillis() - baseTime; out.println (message + " nach " + (elapsedTime / 1000.0) + " Sekunden"); } } // Pseudo-I/O-Operation wird im aufrufenden Thread // ausgef:hrt class NoThreadPseudoIO { int data = -1; NoThreadPseudoIO() { // Konstruktor WithoutThread.showElapsedTime ( "NoThreadPseudoIO erzeugt"); }
407
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
public void run() { WithoutThread.showElapsedTime ( "NoThreadPseudoIO gestartet"); try { Thread.sleep (10000); // 10 Sekunden data = 999; // die Daten sind fertig WithoutThread.showElapsedTime ( "NoThreadPseudoIO beendet"); } catch (InterruptedException e) {} } } Auch wenn die zweite Task keine von der Pseudo-I/O-Operation erzeugten oder modifizierten Daten benGtigt, kann sie erst starten, wenn die I/O-Operation beendet ist. Bei den meisten I/O-Operationen wird die CPU grGßtenteils unt$tig auf eine Reaktion des Peripherieger$ts warten, was eine Verschwendung der wertvollen CPU-Zyklen darstellt. Im Folgenden finden Sie eine Beispielausgabe des Programms (die Zeiten variieren abh$ngig von dem Rechnertyp und der Rechnerumgebung): C:\MasteringJava\Ch08>javac WithoutThread.java C:\MasteringJava\Ch08>java WithoutThread NoThreadPseudoIO erzeugt nach 1.642 Sekunden NoThreadPseudoIO gestartet nach 2.113 Sekunden NoThreadPseudoIO beendet nach 10.044 Sekunden Andere Task gestartet nach 10.044 Sekunden Im zweiten Programm mit Multithreading wird die Klasse f.r die Pseudo-I/O-Operation als eine Unterklasse der Klasse Thread deklariert: class ThreadedPseudoIO extends Thread { Nachdem der Thread erzeugt wurde, wird die I/O-Operation mit der Methode start() der Klasse Thread gestartet: ThreadedPseudoIO pseudo = new ThreadedPseudoIO(); pseudo.start(); Die Methode start() des Threads ruft ihrerseits die Methode run() der Unterklasse auf.
Tipp
408
Bis zur Version 1.0.2 des JDK gab es unter Windows 95/98 und NT einen Fehler bei der Ausf.hrung mehrerer Threads: Programme, die mehrere Threads starten, wurden nicht automatisch beendet. Um diesen Fehler zu umgehen, kGnnen Sie entweder festlegen, dass der letzte ausgef.hrte Thread die Methode System.exit() aufruft oder dass ein Thread andere Threads .berwacht, indem die join()-Methoden der .berwachten Threads aufgerufen werden. exit() ist eine Klassenmethode, die in der Klasse System des java.lang-Packages zum Beenden von Java-Runtime definiert ist. Aus Sicherheitsgr.nden d.rfen Applets exit() nicht aufrufen. Wenn exit() explizit
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
4berblick ber das Multithreading
von einem Applet aufgerufen wird, wird die Exception SecurityException ausgelGst. Dieses Vorgehen ist f.r andere Plattformen und f.r Java 1.1 oder Java 2 nicht erforderlich. Im Folgenden finden Sie das Listing f.r das Multithread-Programm: import java.io.*;
Listing 14.2
public class WithThread { static PrintWriter out = new PrintWriter( System.out, true);
WithThread.java
public static void main (String args[]) { // Erste Task: eine Pseudo-I/O-Operation ThreadedPseudoIO pseudo = new ThreadedPseudoIO(); pseudo.start(); // Zweite Task: eine zuf[llige Task showElapsedTime ("Andere Task gestartet"); } static long baseTime = System.currentTimeMillis(); // Die seit dem Programmstart vergangene // Zeit anzeigen static void showElapsedTime (String message) { long elapsedTime = System.currentTimeMillis() - baseTime; out.println (message + " nach " + (elapsedTime / 1000.0) + " Sekunden"); } } // Die Pseudo-I/O-Operation wird in einem // separaten Thread ausgef:hrt class ThreadedPseudoIO extends Thread { int data = -1; ThreadedPseudoIO() { // Konstruktor WithThread.showElapsedTime ( "ThreadedPseudoIO erzeugt"); } public void run() { WithThread.showElapsedTime ( "ThreadedPseudoIO gestartet"); try { Thread.sleep (10000); // 10 Sekunden data = 999; // die Daten sind fertig WithThread.showElapsedTime ( "ThreadedPseudoIO beendet");
409
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
} catch (InterruptedException e) {} } } Die Ausgabe des Multithread-Programms ist im Folgenden abgedruckt. Sie werden bemerken, dass die zweite Task sogar vor der Pseudo-I/O-Operation beginnt – das ist bei nur einer CPU, die zwei Threads ausf.hrt, selbstverst$ndlich. Die Methode run() des neu erzeugten Thread wird erst ausgef.hrt, wenn der aktuell ausgef.hrte Thread die Programmkontrolle freigibt. C:\MasteringJava\Ch08>javac WithThread.java C:\MasteringJava\Ch08>java WithThread ThreadedPseudoIO erzeugt nach 0.11 Sekunden Andere Task gestartet nach 0.25 Sekunden ThreadedPseudoIO gestartet nach 0.27 Sekunden ThreadedPseudoIO beendet nach 10.025 Sekunden
Thread-Grundlagen Die folgenden Abschnitte erl$utern die Grundlagen f.r die Arbeit mit Threads einschließlich des Erzeugens und Ausf.hrens von Threads, der Steuerung der ThreadAusf.hrung, der Ermittlung von Informationen .ber Threads und Thread-Gruppen sowie der Lebensdauer von Threads und Thread-Gruppen.
Threads erzeugen und ausfhren F.r das gleichzeitige Ausf.hren mehrerer Tasks stehen Ihnen zwei MGglichkeiten zur Verf.gung: Leiten Sie entweder eine neue Klasse von der Klasse Thread ab oder deklarieren Sie eine Klasse, die die Schnittstelle Runnable implementiert.
Eine Unterklasse der Klasse Thread verwenden Wenn Sie eine Unterklasse von der Klasse Thread ableiten, sollte in dieser Unterklasse eine eigene run()-Methode definiert werden, die die run()-Methode der Klasse Thread .berschreibt. In dieser run()-Methode wird der Task ausgef.hrt. Genau wie die Methode main() die erste benutzerdefinierte Methode darstellt, die zum Starten einer Anwendung von Java-Runtime aufgerufen wird, ist die Methode run() die erste benutzerdefinierte Methode, die Java-Runtime zum Starten eines Threads aufruft. Anschließend erzeugt die Anweisung new eine Instanz dieser Unterklasse und ruft die Methode start() des Threads auf, damit die Methode run() ausgef.hrt werden kann. Genauso wurde im vorherigen Beispiel mit der Klasse ThreadedPseudoIO verfahren.
410
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Thread-Grundlagen
Die Schnittstelle Runnable implementieren F.r die Schnittstelle Runnable ist nur die Implementierung einer Methode – der Methode run() – erforderlich. Zuerst wird mit der Anweisung new eine Instanz der Klasse erstellt, die Runnable implementiert, anschließend mit einer weiteren newAnweisung eine Instanz von Thread erzeugt und schließlich die start()-Methode dieser Thread-Instanz aufgerufen, um die Ausf.hrung des in der run()-Methode definierten Tasks zu starten. Eine Instanz einer Klasse, in der die Methode run() definiert ist, muss beim Erstellen der Thread-Instanz als Argument .bergeben werden, um Java-Runtime dar.ber zu informieren, welche run()-Methode auszuf.hren ist, wenn die Methode start() dieser Thread-Instanz aufgerufen wird. Diese Alternative zum Erzeugen eines Threads erweist sich als n.tzlich, wenn die Klasse, die die Methode run() definiert, eine Unterklasse einer anderen Klasse sein muss. Die Klasse kann alle Daten und Methoden der Oberklasse erben und die erzeugte Thread-Instanz l$sst sich zur Thread-Steuerung einsetzen. Das vorherige Multithread-Beispiel kann auch mit der Schnittstelle Runnable programmiert werden. Dazu wird zun$chst die Klassendefinition ver$ndert, um die Schnittstelle Runnable zu implementieren, anstatt eine Unterklasse von der Klasse Thread abzuleiten: class RunnablePseudoIO implements Runnable { Dann wird eine Instanz der Klasse erzeugt und an eine neu erstellte Thread-Instanz .bergeben. Anschließend wird die Methode start() aufgerufen, um die Methode run() auszuf.hren: RunnablePseudoIO pseudo = new RunnablePseudoIO(); Thread thread = new Thread (pseudo); thread.start(); Im Folgenden finden Sie das vollst$ndige Listing des Programms: import java.io.*;
Listing 14.3
public class RunnableThread { static PrintWriter out = new PrintWriter (System.out, true);
RunnableThread.java
public static void main (String args[]) { // Erste Task: eine Pseudo-I/O-Operation RunnablePseudoIO pseudo = new RunnablePseudoIO(); Thread thread = new Thread (pseudo); thread.start(); // Zweite Task: eine zuf[llige Task showElapsedTime ("Andere Task gestartet"); } static long baseTime = System.currentTimeMillis(); // Die seit dem Programmstart vergangene
411
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
// Zeit anzeigen static void showElapsedTime (String message) { long elapsedTime = System.currentTimeMillis() - baseTime; out.println (message + " nach " + (elapsedTime / 1000.0) + " Sekunden"); } } // Die Pseudo-I/O-Operation wird in einem // separaten Thread ausgef:hrt class RunnablePseudoIO implements Runnable { int data = -1; RunnablePseudoIO() { // Konstruktor RunnableThread.showElapsedTime( "RunnablePseudoIO erzeugt"); } public void run() { RunnableThread.showElapsedTime( "RunnablePseudoIO gestartet"); try { Thread.sleep (10000); // 10 Sekunden data = 999; // die Daten sind fertig RunnableThread.showElapsedTime ( "RunnablePseudoIO beendet"); } catch (InterruptedException e) {} } } Die Programmausgabe entspricht weitgehend der des vorherigen Programms: C:\MasteringJava\Ch08>javac WithThread.java C:\MasteringJava\Ch08>java WithThread RunnablePseudoIO erzeugt nach 0.01 Sekunden Andere Task gestartet nach 0.11 Sekunden RunnablePseudoIO gestartet nach 0.11 Sekunden RunnablePseudoIO beendet nach 10.145 Sekunden
Methoden zur Thread-Steuerung Viele der in der Klasse java.lang.Thread definierten Methoden dienen der Ausf.hrungssteuerung von Threads. Einige dieser Methoden sollten in Java 2 aber nicht verwendet werden, um Dateninkonsistenzen oder Deadlocks zu verhindern. Wenn Sie gerade beginnen mit Java 2 zu arbeiten, sollten Sie diese Methoden von vornherein vermeiden und die weiter unten in diesem Kapitel beschriebenen $quivalenten Methoden benutzen. Wenn Sie dagegen Programme von Java 1.0 oder 1.1 por412
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Thread-Grundlagen
tieren wollen, m.ssen Sie diese eventuell im Programmcode verwendeten Methoden $ndern. Im Folgenden finden Sie eine Liste der am h$ufigsten vorkommenden Methoden, die modifiziert werden m.ssen: void start() throw IllegalThreadStateException – wird verwendet, um die Ausf.hrung des in der Methode run() definierten Thread-Rumpfs zu starten. Die Programmkontrolle wird sofort an den Aufrufer zur.ckgegeben und ein neuer Thread wird vom Scheduler angewiesen, die Methode run() gleichzeitig mit dem Thread des Aufrufers auszuf.hren. void stop() – veraltet. Wird verwendet, um die Ausf.hrung des Threads abzubrechen, unabh$ngig davon, was der Thread gerade bearbeitet. Da der Thread dann als „tot“ (dead) angesehen wird, werden die internen Zust$nde des Threads gelGscht und die zugewiesenen Ressourcen erneut beansprucht. Diese Methode kann mGglicherweise dazu f.hren, dass Daten in einem inkonsistenten Zustand zur.ckbleiben und sollte deshalb vermieden werden. void suspend() – veraltet. Wird verwendet, um die Ausf.hrung des Threads vor.bergehend zu unterbrechen. Alle Zust$nde und Ressourcen des Threads werden beibehalten. Der Thread kann sp$ter von einem anderen Thread durch Aufruf der Methode resume() erneut gestartet werden. Diese Methode kann unter Umst$nden zu Deadlocks f.hren und sollte deshalb vermieden werden. Stattdessen sollten Sie die weiter unten beschriebene Methode Object.wait() verwenden. void resume() – veraltet. Wird verwendet, um die Ausf.hrung eines unterbrochenen Thread fortzusetzen. Der unterbrochene Thread wird zur Ausf.hrung vorgesehen. Wenn er eine hGhere Priorit$t als der gerade ausgef.hrte Thread hat, wird dieser blockiert. Andernfalls wird der gerade fortgesetzte Thread in die Warteschlange gestellt. Diese Methode kann potenziell zu Deadlocks f.hren und sollte deshalb vermieden werden. Stattdessen sollten Sie die weiter unten beschriebene Methode Object.notify() verwenden. static void sleep(long sleepTimeInMilliseconds) throws InterruptedException – eine Klassenmethode, die bewirkt, dass Java-Runtime den aufrufenden Thread f.r die angegebene Zeitspanne deaktiviert. W$hrend ein Thread inaktiv ist oder immer, wenn er mit interrupt() unterbrochen wird, kGnnte die Exception InterruptedException ausgelGst werden. Zur Behandlung dieser Exception muss entweder die Anweisung try-catch definiert werden oder die umschließende Methode muss diese Exception in der throws-Klausel aufweisen. void join() throws InterruptedException – wird f.r den Thread des Aufrufers verwendet, damit er darauf wartet, bis dieser Thread – beispielsweise am Ende der Methode run() – beendet wird.
413
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
static void yield() – eine Klassenmethode, die den Thread des Aufrufers vor.bergehend unterbricht und an das Ende der Warteschlange setzt. Hier wartet der Thread auf seine erneute Ausf.hrung. Diese Methode wird verwendet, um sicherzustellen, dass andere Threads mit derselben Priorit$t, eine Ausf.hrungsmGglichkeit erhalten.
Tipp
Alle in der Klasse Thread definierten Klassenmethoden, wie z.B. sleep() und yield(), wirken sich auf den Thread des Aufrufers aus. D.h., es ist immer der Thread des Aufrufers, der eine Zeit lang inaktiv ist oder anderen Threads Platz macht. Dies liegt daran, dass eine Klassenmethode nur auf die Daten einer Instanz oder eines Methodenelements zugreifen kann, wenn die Instanz als Argument .bergeben, innerhalb einer Methode erzeugt oder in einer f.r die Methode sichtbaren Klassenvariablen gespeichert wird. Das folgende Beispiel demonstriert, wie die oben aufgef.hrten Methoden eingesetzt werden. Der Haupt-Thread erstellt zwei Threads, wartet dann darauf, dass der erste Thread beendet wird, indem er die Methode join() des ersten Threads aufruft. Der erste Thread ruft die Methode sleep() auf, um zehn Sekunden inaktiv zu sein. In der Zwischenzeit ruft der zweite Thread seine eigene wait()-Methode auf, um sich selbst so lange zu unterbrechen, bis der Haupt-Thread die Methode notify() aufruft. Nachdem der erste Thread beendet wurde, setzt der Haupt-Thread seine Ausf.hrung fort, ruft die Methode resume() des zweiten Threads auf und wartet, bis auch der zweite Thread zum Abschluss gelangt, indem er die Methode join() des zweiten Threads aufruft. Das Programm lautet folgendermaßen:
Listing 14.4 MethodTest.java
414
import java.io.*; public class MethodTest { static PrintWriter out = new PrintWriter (System.out, true); public static void main (String args[]) { FirstThread first = new FirstThread(); SecondThread second = new SecondThread(); first.start(); second.start(); try { out.println ( "Auf das Ende des ersten Thread warten..."); first.join(); out.println ("Eine lange Wartezeit!"); out.println ("Zweiten Thread aktivieren..."); synchronized (second) { second.notify(); } out.println ( "Auf das Ende des zweiten Thread warten..."); second.join(); } catch (InterruptedException e) { } out.println("Bereit f:r den Schluss");
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Thread-Grundlagen
} } class FirstThread extends Thread { public void run() { try { MethodTest.out.println ( " Erster Thread beginnt mit der Ausf:hrung."); sleep (10000); MethodTest.out.println ( " Erster Thread beendet die Ausf:hrung."); } catch (InterruptedException e) { } } } class SecondThread extends Thread { public synchronized void run() { try { MethodTest.out.println ( " Zweiter Thread beginnt mit der Ausf:hrung."); MethodTest.out.println ( " Zweiter Thread unterbricht sich selbst."); wait(); MethodTest.out.println ( " Zweiter Thread wird fortgesetzt und beendet." ); } catch (InterruptedException e) { } } } Im Folgenden finden Sie die Ausgabe dieses Programms: C:\MasteringJava\Ch08>javac MethodTest.java C:\MasteringJava\Ch08>java MethodTest Auf das Ende des ersten Thread warten... Erster Thread beginnt mit der Ausf:hrung. Zweiter Thread beginnt mit der Ausf:hrung. Zweiter Thread unterbricht sich selbst. Erster Thread beendet die Ausf:hrung. Eine lange Wartezeit! Zweiten Thread aktivieren... Auf das Ende des zweiten Thread warten... Zweiter Thread wird fortgesetzt und beendet. Bereit f:r den Schluss. Das Schl.sselwort synchronized wird in dem sp$tere Abschnitt „Multithreading fr Fortgeschrittene“ kurz erl$utert. Hinweis 415
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
Die Lebensdauer von Threads Nach der Erzeugung und vor der Freigabe eines Threads befindet sich der Thread immer in einem der folgenden vier Zust$nde: neu erzeugt, ausfhrbar, blockiert oder tot (dead). Diese Zust$nde sind in der folgenden Abbildung 14.1 dargestellt und werden in den n$chsten Abschnitten beschrieben.
Abb. 14.1 Die Lebensdauer von Threads
Neu erzeugte Threads Ein Thread erh$lt sofort nach der Erzeugung den Status neu erzeugt, d.h. dieser Status wird direkt erreicht, nachdem die Anweisung new zur Thread-Erzeugung ausgef.hrt wurde. In diesem Zustand werden die lokalen Datenelemente zugewiesen und initialisiert, aber die Ausf.hrung der Methode run() wird erst begonnen, wenn die Methode start() aufgerufen wird. Nach dem Aufruf der Methode start() wird der Thread in den Status ausfhrbar gesetzt.
Ausfhrbare Threads Wenn sich ein Thread in dem Status ausfhrbar befindet, besteht der Ausf.hrungskontext bereits und der Thread kann jederzeit ausgef.hrt werden, d.h., er wartet nicht darauf, dass ein Ereignis eintritt. Zur besseren Erkl$rung kann dieser Status in zwei Unterzust$nde aufgeteilt werden: in den Status gerade ausgefhrt und in den Status wartend. Wenn sich ein Thread in dem Zustand gerade ausgefhrt befindet, werden ihm CPU-Zyklen zugewiesen und er wird tats$chlich gerade verarbeitet. Ein Thread in dem Zustand wartend befindet sich in der Warteschlange und konkurriert mit anderen Threads um CPU-Zyklen. Der
416
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Thread-Grundlagen
Blockierte Threads Der Status blockiert wird erreicht, wenn eines der folgenden Ereignisse eintritt: Der Thread selbst oder eine anderer Thread ruft die Methode suspend() auf. Der Thread ruft die Methode wait() eines Objekts auf. Der Thread selbst ruft die Methode sleep() auf. Der Thread wartet darauf, dass eine I/O-Operation beendet wird. Der Thread wird .ber die Methode join() an einen anderen Thread angef.gt. Ein Thread in dem Zustand blockiert steht nicht zur Ausf.hrung an. Erst wenn eines der folgenden Gegenereignisse f.r das blockierende Ereignis eintritt, kehrt der Thread in den Status ausfhrbar zur.ck und konkurriert um CPU-Zyklen: wenn der Thread mit der Methode suspend() unterbrochen wurde und ein anderer Thread dessen Methode resume() aufruft. wenn der Thread durch Aufruf der Objektmethode wait() blockiert wurde und die Methoden notify() oder notifyAll() des Objekts aufgerufen werden. wenn der Thread mit der Methode sleep() blockiert wurde und die angegebene Ruhezeit abgelaufen ist. wenn der Thread wegen einer I/O-Operation blockiert wurde und diese beendet wird. Bitte beachten Sie, dass Sie – auch in Versionen vor Java 2 – suspend() oder resume() nicht aufrufen sollten. In Java 2 gelten diese Methoden als veraltet. Tipp
Tote Threads Ein Thread tritt in den Status tot ein, wenn dessen Ausf.hrung beendet ist oder er durch den Aufruf der Methode stop() von einem anderen Thread angehalten wird. Der richtige Weg, eine while (true)-Schleife zu verlassen, ohne die Methode stop() zu verwenden, ist, eine Statusvariable mitzuf.hren, welche die Laufbedingung der while-Schleife pr.ft. Anstelle die Methode run() (wie im folgenden Codefragment dargestellt) zu verwenden und mit stop() den Thread anzuhalten, public void run () { while (true) { ... } } 417
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
verwenden Sie f.r die Laufbedingung eine boolesche Variable. Wenn der Thread in diesem Fall angehalten werden soll, rufen Sie nicht stop() auf, sondern $ndern den Status der booleschen Variablen. Dadurch wird der Thread im n$chsten Durchlauf abgebrochen und gew$hrleistet, dass keine Daten in einem inkonsistenten Zustand zur.ckgelassen werden. public void run () { while (aBooleanVariable) { ... } } In JDK 1.1 und fr.heren Implementierungen der virtuellen Java-Maschine wird stop() ignoriert, wenn ein Thread erzeugt, aber noch nicht gestartet wurde. Tipp Zur
Thread-Gruppen Jede Thread-Instanz ist ein Element exakt einer Thread-Gruppe. Elemente einer Thread-Gruppe kGnnen sowohl Threads als auch andere Thread-Gruppen sein. In der Tat ist jede Thread-Gruppe – außer der System-Thread-Gruppe – ein Element einer anderen Thread-Gruppe. Alle Threads und Thread-Gruppen in einer Anwendung bilden eine Baumstruktur, deren Ausgangspunkt die System-Thread-Gruppe ist. Wenn eine Java-Anwendung gestartet wird, erzeugt die virtuelle Java-Maschine die Haupt-Thread-Gruppe als ein Element der System-Thread-Gruppe. In dieser HauptThread-Gruppe wird ein Haupt-Thread erzeugt, um die Methode main() der Anwendung auszuf.hren. Standardm$ßig sind alle neuen, benutzererzeugten Threads und Thread-Gruppen Elemente dieser Haupt-Thread-Gruppe, falls nicht eine andere Thread-Gruppe als erstes Argument der Anweisung new der Konstruktormethode .bergeben wird. Eine neue Thread-Gruppe wird durch Instanzierung der Klasse ThreadGroup erzeugt. Mit den folgenden Anweisungen wird beispielsweise eine Thread-Gruppe mit dem Namen MyThreadGroup als Element der Standard-Haupt-Thread-Gruppe und dann der Thread MyThread als Element der neu erzeugten Thread-Gruppe angelegt. ThreadGroup group = new ThreadGroup ("MyThreadGroup"); Thread thread = new Thread (group, "MyThread"); In der Klasse ThreadGroup sind zum gemeinsamen Bearbeiten aller Threads der Thread-Gruppe sowie all ihrer Unter-Thread-Gruppen drei Methoden definiert: stop(), suspend() und resume(). Genau wie bei der Klasse Thread sollten diese drei Methoden auch bei der Klasse ThreadGroup vermieden werden. Diese Methoden 418
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Thread-Grundlagen
funktionieren nicht ordnungsgem$ß, daher wurden sie von Sun f.r ungeeignet erkl$rt. Der richtige Gebrauch von Bedingungsvariablen sollte die Abh$ngigkeit von diesen Methoden aber verringern.
Informationen ber Threads und Thread-Gruppen ermitteln In den Klassen Thread und ThreadGroup sind viele Methoden zur Ermittlung von Informationen .ber Threads und Thread-Gruppen definiert.
Informationen ber Threads Im Folgenden sind einige der gebr$uchlichsten Methoden zur Ermittlung von Informationen .ber Threads aufgef.hrt: Methoden in java.lang.thread: static thread currentThread()– gibt eine Referenz auf den aktuell ausgef.hrten Thread zur.ck. string getName()– gibt den Namen des aktuellen Threads zur.ck. ThreadGroup getThreadGroup()– gibt die dem Thread .bergeordnete ThreadGruppe zur.ck. int getPriority()– gibt die aktuelle Priorit$t des Threads zur.ck. boolean isAlive()– gibt true zur.ck, wenn der Thread gestartet, aber noch nicht beendet ist. boolean isDaemon()– gibt true zur.ck, wenn der Thread ein Daemon-Thread ist.
Informationen ber Thread-Gruppen Im Folgenden sind einige der gebr$uchlichsten Methoden zur Ermittlung von Informationen .ber Thread-Gruppen aufgef.hrt. Methoden in java.lang.ThreadGroup: string getName()– gibt den Namen der Thread-Gruppe zur.ck. ThreadGroup getParent()– gibt die der Thread-Gruppe .bergeordnete ThreadGruppe zur.ck. int getMaxPriority()– gibt die aktuelle maximale Priorit$t der Thread-Gruppe zur.ck. 419
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
int activeCount()– gibt die Anzahl der aktiven Threads in der Thread-Gruppe zur.ck. int activeGroupCount()– gibt die Anzahl der aktiven Thread-Gruppen in der Thread-Gruppe zur.ck. int enumerate(Thread list[], boolean recursive) – f.gt alle aktiven Threads dieser Thread-Gruppe in das Array list ein. Wenn recursivetrue ist, werden auch alle Threads der Thread-Untergruppen in das Array kopiert. Diese Methode gibt die Anzahl der kopierten Threads zur.ck. Wenn Speicherplatz f.r das Thread-Array zugeteilt werden soll, kann anhand der Methode activeCount() die GrGße der Liste festgelegt werden. Thread-Priorit$ten und Daemon-Threads werden weiter unten in diesem Kapitel besprochen.
Ein Programm zum Ermitteln und Ausgeben von Thread-Informationen Dieser Abschnitt behandelt ein Beispiel, in dem die in den vorherigen Abschnitten beschriebenen Methoden zum Anzeigen von Informationen .ber Threads und Thread-Gruppen verwendet werden. Das Programm erzeugt eine Thread-Gruppe namens MyThreadGroup, die vier Threads enth$lt. Anschließend werden durch Aufruf der Methode printAllThreadInfo() alle Informationen ausgegeben. Die Methode printAllThreadInfo() sucht zun$chst die Thread-Stammgruppe aller gerade ausgef.hrten Threads und Thread-Gruppen. Dann werden die Informationen .ber die zugrunde liegenden Threads und Thread-Gruppen von der Stammgruppe ab nach unten ausgegeben. Die Ausgabe ist einger.ckt, um die Hierarchieebenen der einzelnen Threads und Thread-Gruppen in der Baumstruktur darzustellen. Das vollst$ndige Programm lautet folgendermaßen: Listing 14.5 ThreadInfo.java
420
import java.io.*; public class ThreadInfo { static PrintWriter out = new PrintWriter (System.out, true); public static void main (String args[]) { Thread[] threads = new Thread[4]; ThreadGroup group = new ThreadGroup ("MyThreadGroup"); if (args.length > 0) { Thread thread = Thread.currentThread(); thread.setName (args[0]); } for (int i = 0; i < 4; i++) threads[i] = new Thread (group, "MyThread#" + i); ThreadInfo.printAllThreadInfo(); }
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Thread-Grundlagen
// Informationen :ber alle Threads und alle // Thread-Gruppen der Anwendung auff:hren public static void printAllThreadInfo() { ThreadGroup parent, root; // Den alle laufenden threads :bergeordneten // Thread suchen root = parent = Thread.currentThread(). getThreadGroup(); while ((parent = parent.getParent()) != null) root = parent; // Information vom :bergeordneten Thread ab // ausgeben out.println(); printThreadGroupInfo ("", root); } // Informationen :ber eine Thread-Gruppe ausgeben public static void printThreadGroupInfo (String indent, ThreadGroup group) { final int SAFETY = 5; if (group == null) return; out.println (indent + "Thread-Gruppe: " + group.getName() + "; Max. Priorit[t: " + group.getMaxPriority() + (group.isDaemon() ? " [D[mon]" : "")); // Informationen :ber Komponenten-Threads // ausgeben int numThreads = group.activeCount(); Thread threads[] = new Thread[numThreads+ SAFETY]; numThreads = group.enumerate (threads, false); for (int i = 0; i < numThreads; i++) printThreadInfo(indent + " ", threads[i]); // Informationen :ber Komponenten-Thread// Gruppen ausgeben int numGroups = group.activeGroupCount(); ThreadGroup groups[] = new ThreadGroup[ numGroups+SAFETY]; numGroups = group.enumerate (groups, false); for (int i = 0; i < numGroups; i++) printThreadGroupInfo (indent + " ", groups[i]); } // Informationen :ber einen einzelnen Thread // ausgeben public static void printThreadInfo (String indent, Thread thread) { if (thread == null) return; out.println (indent +
421
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
"Thread: " + thread.getName() + "; Priorit[t: " + thread.getPriority() + (thread.isDaemon() ? " [D[mon]" : "") + (thread.isAlive() ? " [Aktiv]" : " [Inaktiv]") + ((Thread.currentThread() == thread) ? " <== aktuell" : "")); } }
Hinweis
Da sich die Anzahl der gerade ausgef.hrten Threads zwischen dem Aufruf von activeGroupCount() und dem Aufruf von enumerate() $ndern kGnnte, wurde eine Sicherheitsspanne f.r den Fall hinzugef.gt, dass die Anzahl sich erhGht. Im Abschnitt „Multithreading fr Fortgeschrittene“ weiter unten in diesem Kapitel werden MGglichkeiten zur LGsung dieses Problems behandelt. Wenn das Programm unter Windows NT ausgef.hrt wird, erhalten Sie die folgende Ausgabe: C:\MasteringJava\Ch08>javac ThreadInfo.java C:\MasteringJava\Ch08>java ThreadInfo Thread-Gruppe: system; Max. Priorit[t: 10 Thread: Signal dispatcher; Priorit[t 10 [D[mon] [Aktiv] Thread: Reference handler; Priorit[t 10 [D[mon] [Aktiv] Thread: Finalizer; Priorit[t: 8 [D[mon] [Aktiv] Thread-Gruppe: main; Max. Priorit[t: 10 Thread: main; Priorit[t: 5 [Aktiv] <== aktuell Thread-Gruppe: MyThreadGroup; Max. Priorit[t: 10 Unter Solaris 2.5 liefert dasselbe Programm die folgende sehr $hnliche Ausgabe: harpoon:/users/me/java/examples/ch8>javac ThreadInfo.java harpoon:/users/me/java/examples/ch8>java ThreadInfo Thread-Gruppe: system; Max. Priorit[t: 10 Thread: Clock; Priorit[t: 12 [D[mon] [Aktiv] Thread: Idle thread; Priorit[t: 0 [D[mon] [Aktiv] Thread: Async Garbage Collector; Priorit[t: 1 [D[mon] [Aktiv] Thread: Reference handler; Priorit[t: 10 [D[mon] [Aktiv] Thread: Finalizer; Priorit[t: 8 [D[mon] [Aktiv] Thread: SoftReference sweeper; Priorit[t: 9 [D[mon] [Aktiv] Thread-Gruppe: main; Max. Priorit[t: 10 Thread: main; Priorit[t: 5 [Aktiv] <== aktuell Thread-Gruppe: MyThreadGroup; Max. Priorit[t: 10
422
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
MGglicherweise erhalten Sie auf Ihrem System eine geringf.gig andere Ausgabe. Dies ist davon abh$ngig, wie Ihre Java-Implementierung System-Thread-Ressourcen verwaltet.
Multithreading f,r Fortgeschrittene In den folgenden Abschnitten werden einige komplexere Themen des Multithreading vorgestellt: Synchronisierung von Threads, Kommunikation zwischen Threads, Thread-Priorit$ten und Zuteilung von Rechenzeit sowie Daemon-Threads.
Synchronisierung von Threads Mit Hilfe der Synchronisierung von Threads wird eine Datenverf$lschung verhindert, die durch das gleichzeitige Zugreifen auf dieselben Daten verursacht werden kann. Da alle Threads eines Programms denselben Speicherbereich nutzen, kGnnen zwei Threads gleichzeitig auf dieselbe Variable zugreifen oder dieselbe Methode desselben Objekts ausf.hren. Probleme kGnnen dann auftreten, wenn mehrere Threads zur gleichen Zeit auf dieselben Daten zugreifen. Ein Thread kGnnte schneller sein als ein anderer und die Daten .berschreiben, die gerade von dem anderen Thread geschrieben wurden. Oder ein Thread kGnnte mit einem Zwischenergebnis eines anderen Threads arbeiten und die Datenkonsistenz zerstGren. Daher ist ein Mechanismus erforderlich, der den Zugriff auf die kritischen Daten verhindert, wenn diese Daten gerade von einem Thread bearbeitet werden. Stellen Sie sich beispielsweise vor, Sie h$tten ein Programm zur Verwaltung eines Bankkontos eines Benutzers entwickelt. Um eine Einzahlung vorzunehmen, sind drei Arbeitsschritte erforderlich: die Ermittlung des aktuellen Kontostands von einem Remote-Server, wof.r f.nf Sekunden benGtigt werden, den neu eingezahlten Betrag zu dem gerade ermittelten Kontostand hinzuf.gen, den neuen Kontostand zur.ck an den Server senden, wof.r wiederum f.nf Sekunden benGtigt werden. Wenn zwei Threads, die jeweils eine Einzahlung von $1.000 vornehmen, ungef$hr zur gleichen Zeit bei einem aktuellen Kontostand von $1.000 gestartet werden, kann es sein, dass der Endkontostand nur eine Einzahlung wiedergibt. In der folgenden Tabelle ist ein mGgliches Szenario beschrieben.
423
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
Zwei gleichzeitig ausgefhrte Threads zur Einzahlung auf ein Konto
Zeit Thread 1
Thread 2
Kontostand auf dem Remote-Server
a
Kontostand abrufen
b
Warten...
Kontostand abrufen
$1.000
c
Kontostand abrufen = $1,000
Warten...
$1.000
d
Kontostand berechnen = $ 2.000
Warten...
$1.000
e
Kontostand .bermitteln
Warten...
$1.000
f
Warten...
Kontostand abrufen = $1.000
$1.000
g
Warten...
Kontostand berechnen = $ 2.000
$1.000
h
Warten...
Kontostand .bermitteln
$1.000
i
Kontostand festgelegt
Warten...
$2.000
Kontostand festgelegt
$2.000
j
$1.000
Tabelle 14.1 Der Kontostand auf dem Remote-Server w.rde nur um eine Einzahlung erhGht! Das folgende Beispielprogramm simuliert das Szenario der obigen Tabelle. Es wird die Klasse Account mit den folgenden drei Methoden definiert: getBalance() zum Abrufen des aktuellen Kontostands von einem Pseudo-Server mit einer simulierten f.nfsek.ndigen VerzGgerung, setBalance() zum Zur.ckschreiben des neuen Kontostands auf denselben Pseudo-Server wiederum mit einer simulierten f.nfsek.ndigen VerzGgerung und deposit() zum Einsetzen der beiden anderen Methoden f.r das Abschließen der Einzahlungstransaktion. Die Klasse DepositThread wird deklariert, um den Einzahlungsvorgang mit dem .bergebenen Kontostand zu beginnen. Das Hauptprogramm erzeugt eine Instanz von Account und startet dann zwei Threads, die jeweils eine Einzahlung von $1.000 vornehmen. Im Folgenden finden Sie das vollst$ndige Programmlisting:
424
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
import java.io.*;
Listing 14.6
public class Deposit { // Vom Remote-Server erhaltenen Kontostand // simulieren static int balance = 1000;
Deposit.java
public static void main (String args[]) { PrintWriter out = new PrintWriter (System.out, true); Account account = new Account (out); DepositThread first, second; first = new DepositThread (account, 1000, "#1"); second = new DepositThread (account, 1000, "\t\t\t\t#2"); // Transaktionen starten first.start(); second.start(); // Warten, bis beide Transaktionen beendet sind try { first.join(); second.join(); } catch (InterruptedException e) {} // Endkontostand ausgeben out.println ( "*** Endkontostand betr[gt " + balance); } } class Account { PrintWriter out; Account (PrintWriter out) { this.out = out; } void deposit (int amount, String name) { int balance; out.println (name + " Versuch einer Einzahlung von " + amount); out.println (name + " Kontostand abrufen..."); balance = getBalance(); out.println (name + " Ermittelter Kontostand: " + balance); balance += amount; out.println (name + " Kontostand festlegen..."); setBalance (balance); out.println (name + " Neuer Kontostand ist " + Deposit.balance); } int getBalance() {
425
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
// VerzIgerung beim Abrufen des Kontostands // simulieren try { Thread.sleep (5000); } catch (InterruptedException e) {} return Deposit.balance; } void setBalance (int balance) { // VerzIgerung beim Setzen des neuen Kontostands // simulieren try { Thread.sleep (5000); } catch (InterruptedException e) {} Deposit.balance = balance; } } class DepositThread extends Thread { Account account; int depositAmount; String message; DepositThread (Account account, int amount, String message) { this.message = message; this.account = account; this.depositAmount = amount; } public void run() { account.deposit (depositAmount, message); } } Die Ausgabe des obigen Programms lautet folgendermaßen: C:\MasteringJava\Ch08>javac Deposit.java C:\MasteringJava\Ch08>java Deposit #1 Versuch einer Einzahlung von 1000 #1 Kontostand abrufen... #2 Versuch einer Einzahlung von 1000 #2 Kontostand abrufen... #1 Ermittelter Kontostand: 1000 #1 Kontostand festlegen... #2 Ermittelter Kontostand: 1000 #2 Kontostand festlegen... #1 Neuer Kontostand ist 2000 #2 Neuer Kontostand ist 2000 *** Endkontostand betr[gt 2000 426
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
Das Java-Monitormodell fr die Synchronisierung Java verwendet zum Synchronisieren des Datenzugriffs ein so genanntes Monitormodell. Monitor beschreibt in diesem Zusammenhang eine Art von .berwachtem Ort, an dem alle Ressourcen durch dasselbe Schloss gesch.tzt sind. Nur ein einziger Schl.ssel passt zu allen SchlGssern in einem Monitor. Ein Thread muss zun$chst den Schl.ssel erhalten, bevor er in den Monitor gelangen und auf die gesch.tzten Ressourcen zugreifen kann. Wenn mehrere Threads gleichzeitig in den Monitor gelangen wollen, wird nur einem einzigen der Schl.ssel .bergeben, alle anderen m.ssen vor dem Monitor warten, bis der Thread mit dem Schl.ssel die Ressourcen nicht mehr benGtigt und den Schl.ssel wieder an die virtuelle Maschine von Java zur.ckgibt. Sobald ein Thread den Schl.ssel zu einem Monitor erh$lt, kann er auf die von diesem Monitor .berwachten Ressourcen zugreifen, so oft und so lange er den Schl.ssel besitzt. Wenn der Thread mit dem Schl.ssel jedoch auf Ressourcen zugreifen muss, die von einem anderen Monitor kontrolliert werden, benGtigt der Thread den Schl.ssel dieses Monitors. Ein Thread kann jederzeit die Schl.ssel zu mehreren Monitoren und unterschiedliche Threads kGnnen gleichzeitig die Schl.ssel zu verschiedenen Monitoren besitzen. Wenn mehrere Threads darauf warten, jeweils den Schl.ssel von einem anderen Thread zu erhalten, kann es zu Deadlocks kommen. In Java handelt es sich bei von Monitoren gesch.tzten Ressourcen um Programmfragmente in Form von Methoden oder AnweisungsblGcken, die in geschweifte Klammern eingeschlossen sind. Wenn auf bestimmte Daten nur .ber von demselben Monitor gesch.tzte Methoden oder BlGcke zugegriffen werden kann, ist der Datenzugriff indirekt synchronisiert. Mit dem Schl.sselwort synchronized geben Sie an, dass die folgende Methode oder der folgende Anweisungsblock von einem Monitor synchronisiert werden soll. Wenn ein Anweisungsblock synchronisiert werden soll, muss unmittelbar nach dem Schl.sselwort synchronized eine in Klammern eingeschlossene Objektinstanz folgen, damit die virtuelle Maschine von Java dar.ber informiert wird, welcher Monitor .berpr.ft werden muss. Einen Monitor kGnnen Sie sich auch als bewachten Parkplatz vorstellen, wobei alle synchronisierten Methoden und BlGcke die Autos darstellen, die gefahren (bei Threads ausgef.hrt) werden kGnnen. Alle Autos benutzen denselben Schl.ssel. Dieser Schl.ssel ist erforderlich, um auf den Parkplatz zu gelangen und Autos so lange zu fahren, bis der Schl.ssel zur.ckgegeben wird. Eine andere wartende Person erh$lt dann den zur.ckgegebenen Schl.ssel und kann mit den gew.nschten Autos fahren. Die folgende Abbildung illustriert dieses Modell. Die Methode deposit() aus dem vorherigen Beispiel kann synchronisiert werden, damit nur ein Thread zur gleichen Zeit ausgef.hrt wird. Dazu muss nur das Schl.sselwort synchronized vor der Methodendefinition eingef.gt werden: synchronized void deposit(int amount, String name) {
427
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
Abb. 14.2 Threads ben
Im Folgenden finden Sie das vollst$ndige Programmlisting: Listing 14.7 Deposit.java (Version 2)
import java.io.*; public class Deposit { // Vom Remote-Server erhaltenen Kontostand // simulieren static int balance = 1000; public static void main (String args[]) { PrintWriter out = new PrintWriter (System.out, true); Account account = new Account (out); DepositThread first, second; first = new DepositThread (account, 1000, "#1"); second = new DepositThread (account, 1000, "\t\t\t\t#2"); // Transaktionen starten first.start(); second.start(); // Warten, bis beide Transaktionen beendet sind try { first.join(); second.join(); } catch (InterruptedException e) {} // Endkontostand ausgeben out.println ( "*** Endkontostand betr[gt " + balance); } } class Account { PrintWriter out; Account (PrintWriter out) { this.out = out; }
428
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
synchronized void deposit (int amount, String name) { int balance; out.println (name + " Versuch einer Einzahlung von " + amount); out.println (name + " Kontostand abrufen..."); balance = getBalance(); out.println (name + " Ermittelter Kontostand: " + balance); balance += amount; out.println (name + " Kontostand festlegen..."); setBalance (balance); out.println (name + " Neuer Kontostand ist " + Deposit.balance); } int getBalance() { // VerzIgerung beim Abrufen des Kontostands // simulieren try { Thread.sleep (5000); } catch (InterruptedException e) {} return Deposit.balance; } void setBalance (int balance) { // VerzIgerung beim Setzen des neuen Kontostands // simulieren try { Thread.sleep (5000); } catch (InterruptedException e) {} Deposit.balance = balance; } } class DepositThread extends Thread { Account account; int depositAmount; String message; DepositThread (Account account, int amount, String message) { this.message = message; this.account = account; this.depositAmount = amount; } public void run() { account.deposit (depositAmount, message); } } 429
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
Die Ausgabe des modifizierten Programms lautet folgendermaßen: C:\MasteringJava\Ch08>javac Deposit.java C:\MasteringJava\Ch08>java Deposit #1 Versuch einer Einzahlung von 1000 #1 Kontostand abrufen... #1 Ermittelter Kontostand: 1000 #1 Kontostand festlegen... #1 Neuer Kontostand ist 2000 #2 Versuch einer Einzahlung von 1000 #2 Kontostand abrufen... #2 Ermittelter Kontostand: 2000 #2 Kontostand festlegen... #2 Neuer Kontostand ist 3000 *** Endkontostand betr[gt 3000 Ein Anweisungsblock kann auch folgendermaßen in der Methode deposit() f.r das aufgerufene Objekt synchronisiert werden: void deposit (int amount, String name) { int balance; out.println (name + " Versuch einer Einzahlung von " + amount); synchronized (this) { out.println (name + " Kontostand abrufen..."); balance = getBalance(); out.println (name + " Ermittelter Kontostand = " + balance); balance += amount; out.println (name + " Kontostand festlegen..."); setBalance (balance); } out.println (name + " Neuer Kontostand = " + balance); } Die Ausgabe dieses Programms entspricht weitgehend der des vorherigen. Die erste Meldung des zweiten Threads ist in den Meldungen des ersten Threads integriert, weil die erste println()-Methode sich nicht innerhalb des synchronisierten Blocks befindet. Das vollst$ndige Programmlisting lautet folgendermaßen: Listing 14.8 Deposit.java (Version 3)
import java.io.*; public class Deposit { // Vom Remote-Server erhaltenen Kontostand // simulieren static int balance = 1000; public static void main (String args[]) { PrintWriter out = new PrintWriter (System.out,
430
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
true); Account account = new Account (out); DepositThread first, second; first = new DepositThread (account, 1000, "#1"); second = new DepositThread (account, 1000, "\t\t\t\t#2"); // Transaktionen starten first.start(); second.start(); // Warten, bis beide Transaktionen beendet sind try { first.join(); second.join(); } catch (InterruptedException e) {} // Endkontostand ausgeben out.println ( "*** Endkontostand betr[gt " + balance); } } class Account { PrintWriter out; Account (PrintWriter out) { this.out = out; } void deposit (int amount, String name) { int balance; out.println (name + " Versuch einer Einzahlung von " + amount); synchronized (this) { out.println (name + " Kontostand abrufen..."); balance = getBalance(); out.println (name + " Ermittelter Kontostand = " + balance); balance += amount; out.println (name + " Kontostand festlegen..."); setBalance (balance); } out.println (name + " Neuer Kontostand = " + balance); } int getBalance() { // VerzIgerung beim Abrufen des Kontostands // simulieren try { Thread.sleep (5000); } catch (InterruptedException e) {} return Deposit.balance;
431
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
} void setBalance (int balance) { // VerzIgerung beim Setzen des neuen Kontostands // simulieren try { Thread.sleep (5000); } catch (InterruptedException e) {} Deposit.balance = balance; } } class DepositThread extends Thread { Account account; int depositAmount; String message; DepositThread (Account account, int amount, String message) { this.message = message; this.account = account; this.depositAmount = amount; } public void run() { account.deposit (depositAmount, message); } } Die Ausgabe des Beispielprogramms lautet folgendermaßen: C:\MasteringJava\Ch08>javac Deposit.java C:\MasteringJava\Ch08>java Deposit #1 Versuch einer Einzahlung von 1000 #2 Versuch einer Einzahlung von 1000 #1 Kontostand abrufen... #1 Ermittelter Kontostand: 1000 #1 Kontostand festlegen... #1 Neuer Kontostand ist 2000 #2 Kontostand abrufen... #2 Ermittelter Kontostand: 2000 #2 Kontostand festlegen... #2 Neuer Kontostand ist 3000 *** Endkontostand betr[gt 3000 An jedes Objekt, das synchronisierte Instanzmethoden enth$lt oder das von einem synchronisierten Block referenziert wird, wird ein eindeutiger Schl.ssel ausgegeben. Bei synchronisierten Klassenmethoden wird der Schl.ssel an die Klasse ausgegeben, weil die Methode – schon bevor eine Klasseninstanz existiert – aufgerufen werden kGnnte. Das bedeutet, dass jedes Objekt und jede Klasse .ber einen Monitor verf.gen kann, wenn zugeordnete, synchronisierte Methoden oder Anwei432
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
sungsblGcke vorhanden sind. Dar.ber hinaus unterscheidet sich der Schl.ssel zu einem Klassenmonitor von den Schl.sseln zu Monitoren der jeweiligen Klasseninstanz.
Unterschiede zwischen Synchronisierungstechniken Im n$chsten Beispiel werden die Unterschiede zwischen einer synchronisierten Methode und einem synchronisierten Block sowie die Unterschiede zwischen einer klassenbasierten und einer objektbasierten Synchronisierung gezeigt. Die Klasse SyncToken enth$lt drei Methoden, die jeweils unterschiedlich synchronisiert sind und die Methode ticker() zum Ausgeben von drei periodischen Signalen in zuf$lligen Intervallen aufrufen. SyncTestRunner ist eine Thread-Klasse, die – basierend auf der .bergebenen ID – verschiedene Methoden der Klasse SyncToken f.r die Ausf.hrung ausw$hlt. Die Methode main() der Klasse SyncTest erzeugt zehn Threads, die ticker() zu Vergleichszwecken mit unterschiedlichen Synchronisierungsschemata ausf.hrt. Das Programmlisting lautet folgendermaßen: import java.io.*;
Listing 14.9
public class SyncTest {
SyncTest.java
public static void main (String args[]) { SyncToken token = new SyncToken(); SyncTestRunner runners[] = new SyncTestRunner[10]; for (int i = 0; i < 10; i++) { runners[i] = new SyncTestRunner (token, i); runners[i].start(); } } } class SyncTestRunner extends Thread { SyncToken token; int id; SyncTestRunner (SyncToken token, int id) { this.token = token; this.id = id; } public void run() { switch (id % 3) { case 0: SyncToken.classTicker ("\t\t\tKlasse #" + id, token); break; case 1: token.methodTicker ("Methode #" + id); 433
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
break; case 2: token.blockTicker ("Block #" + id); break; } } } class SyncToken { PrintWriter out = new PrintWriter (System.out, true); // Methode ticker: drei Signale in zuf[lligen // Intervallen senden void ticker (String message) { for (int i = 0; i < 3; i++) { try { Thread.sleep ((int) (800 * Math.random())); } catch (InterruptedException e) { } out.println(message + ", Signal #" + i); } } // Klassenbasierte Synchronisierung static synchronized void classTicker (String message, SyncToken token) { token.ticker(message); } // Objektbasierte Synchronisierung: // synchronisierter Block void blockTicker (String message) { synchronized(this) { ticker (message); } } // Objektbasierte Synchronisierung: // synchronisierte Methode synchronized void methodTicker (String message) { ticker (message); } } Die Ausgabe des Programms lautet folgendermaßen: C:\MasteringJava\Ch08>javac SyncTest.java C:\MasteringJava\Ch08>java SyncTest Klasse #0, Signal #0 434
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
Methode #1, Signal #0 Klasse Methode #1, Signal #1 Methode #1, Signal #2 Block #2, Signal #0 Klasse Klasse Block #2, Signal #1 Klasse Klasse Block #2, Signal #2 Methode #4, Signal #0 Methode #4, Signal #1 Methode #4, Signal #2 Klasse Block #5, Signal #0 Klasse Block #5, Signal #1 Block #5, Signal #2 Klasse Methode #7, Signal #0 Methode #7, Signal #1 Klasse Methode #7, Signal #2 Klasse Klasse Block #8, Signal #0 Block #8, Signal #1 Block #8, Signal #2
#0, Signal #1
#0, Signal #2 #3, Signal #0 #3, Signal #1 #3, Signal #2
#6, Signal #0 #6, Signal #1
#6, Signal #2
#9, Signal #0 #9, Signal #1 #9, Signal #2
MGglicherweise erhalten Sie auf Ihrem System eine geringf.gig andere Ausgabe. Dies ist davon abh$ngig, wie der Hersteller Ihrer Java-Implementierung Ressourcen verwaltet. Aus der Ausgabe kGnnen Sie entnehmen, dass objektbasierte, synchronisierte Methoden und synchronisierte BlGcke denselben Monitorschl.ssel verwenden, sofern sie f.r dasselbe Objekt konzipiert sind. Des Weiteren kGnnen Sie an den ineinander verzahnten Ausgaben erkennen, dass klassenbasierte Synchronisierung und objektbasierte Synchronisierung verschiedene Schl.ssel verwenden. Da die Synchronisierung eine aufwendige Operation darstellt, sollte deren Einsatz besonders f.r h$ufig ausgef.hrte Methoden oder AnweisungsblGcke auf ein Minimum beschr$nkt bleiben. Durch Synchronisierung lassen sich aber die wechselseitigen StGrungen von Threads reduzieren. Ein vern.nftiger Einsatz der Synchronisierung wird die Stabilit$t und die Robustheit eines Programms auf jeden Fall verbessern.
435
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
Kommunikation zwischen Threads Die Kommunikation zwischen Threads ermGglicht, dass Threads miteinander interagieren oder aufeinander warten. Threads kGnnen .ber gemeinsam genutzte Daten miteinander kommunizieren und .ber Thread-Steuerungsmethoden dazu veranlasst werden, aufeinander zu warten.
Von Threads gemeinsam genutzte Daten Alle Threads eines Programms nutzen denselben Speicherplatz. Wenn die Referenz auf ein Objekt f.r unterschiedliche Threads (wegen der Syntaxregeln f.r Geltungsbereichen) sichtbar ist oder explizit an andere Threads .bergeben wird, haben diese Threads gemeinsamen Zugriff auf die Datenelemente dieses Objekts. Wie im vorherigen Abschnitt erl$utert ist es manchmal erforderlich, mittels Synchronisierung einen exklusiven Zugriff auf Daten festzulegen, um Datenverf$lschungen zu verhindern.
Threads, die auf andere Threads warten Mit Hilfe von Thread-Steuerungsmethoden kGnnen Sie veranlassen, dass Threads aufeinander warten. Beispielsweise kann mit der Methode join() bewirkt werden, dass der aufrufende Thread auf die Beendigung des aufgerufenen Threads wartet. Ein Thread kann mit der Methode suspend() auch unterbrochen werden und warten, bis er von einem anderen Thread .ber die Methode resume()wieder aktiviert wird. Beide Threads kGnnen danach gleichzeitig laufen. Wenn ein Thread, der den Schl.ssel zu einem Monitor besitzt, unterbrochen wird oder darauf wartet, dass ein anderer Thread beendet wird, kGnnen Deadlocks auftreten. Wenn der Thread, auf den gewartet wird, in denselben Monitor gelangen muss, werden beide Threads ewig warten. Aus diesem Grund sollten Sie die Methoden suspend() und resume() nicht mehr verwenden. Die in der Klasse Object des java.lang-Packages definierten Methoden wait(), notify() und notifyAll() lGsen dieses Problem. Die Methode wait() bewirkt, dass die aufrufende Methode wartet, bis entweder eine Zeitbegrenzung abgelaufen ist oder ein anderer Thread die Methode notify() oder notifyAll() desselben Objekts aufruft. Die Syntax der Methode wait() lautet wie folgt: wait() oder wait (long timeoutPeriodInMilliseconds) Bei der ersten Syntax wird gewartet, bis der Thread benachrichtigt wird. Bei der zweiten Syntax wird entweder so lange gewartet, bis die angegebene Zeitbegrenzung verstrichen ist oder bis der Thread benachrichtigt wird.
436
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
Wenn ein Thread die Methode wait() aufruft, wird der Schl.ssel, den er besitzt, f.r einen anderen, wartenden Thread freigegeben. Mit der Methode notify() kann nur ein wartender Thread aktiviert werden. Die Methode notifyAll() aktiviert alle wartenden Threads. Nach der Benachrichtigung versucht der Thread, erneut in den Monitor zu gelangen, indem er den Schl.ssel anfordert. Eventuell muss er aber warten, bis ein anderer Thread den Schl.ssel freigibt. Bitte beachten Sie, dass diese Methoden nur innerhalb eines Monitors oder eines synchronisierten Blocks aufgerufen werden kGnnen. Der Thread, der die Methoden notify() oder notifyAll() eines Objekts aufruft, muss den Schl.ssel f.r diesen Objektmonitor besitzen, sonst wird die Exception IllegalMonitorStateException, ein Typ der Exception RuntimeException, ausgelGst. Im n$chsten Beispiel werden die Methoden wait() und notify() zur LGsung des klassischen Erzeuger-Verbraucher-Problems eingesetzt. Bei diesem Problem generiert der Erzeuger Daten, die der Verbraucher verwendet. Wenn aber der Erzeuger Daten schneller erzeugt, als der Verbraucher diese verarbeiten kann, kGnnten die neu erzeugten Daten – noch bevor sie verwendet wurden – wieder .berschrieben werden. Wenn andererseits der Verbraucher die Daten schneller verwendet, als der Erzeuger sie generieren kann, kGnnte es sein, dass der Verbraucher bereits verarbeitete Daten erneut verwendet. Durch Synchronisierung allein kann dieses Problem nicht gelGst werden, da dadurch nur ein exklusiver Datenzugriff, aber nicht die Verf.gbarkeit von Daten gew$hrleistet wird. In der ersten Implementierung wird mit einem Monitor, einer Instanz der Klasse NoWaitMonitor, der Zugriff auf die Daten token gesteuert. Der Erzeuger und der Verbraucher setzen bzw. ermitteln den Wert von token in zuf$lligen Intervallen. Die maximale L$nge eines Intervalls wird durch das Argument speed gesteuert, das an die Konstruktoren .bergeben wird. Das Hauptprogramm akzeptiert bis zu zwei Befehlszeilenargumente zum Setzen der Erzeugungs- und Verbrauchsgeschwindigkeit, erzeugt eine Instanz des Monitors, erstellt einen Erzeuger und einen Verbraucher und .berwacht deren Ausf.hrung zehn Sekunden lang. Im Folgenden finden Sie das zugehGrige Programmlisting: import java.io.*;
Listing 14.10
public class NoWaitPandC { static int produceSpeed = 200; static int consumeSpeed = 200;
NoWaitPandC.java
public static void main (String args[]) { if (args.length > 0) produceSpeed = Integer.parseInt (args[0]); if (args.length > 1) consumeSpeed = Integer.parseInt (args[1]); NoWaitMonitor monitor = new NoWaitMonitor(); new NoWaitProducer (monitor, produceSpeed); new NoWaitConsumer (monitor, consumeSpeed); try { Thread.sleep (1000);
437
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
} catch (InterruptedException e) { } System.exit(0); } } class NoWaitMonitor { int token = -1; PrintWriter out = new PrintWriter (System.out, true); // Wert von token ermitteln synchronized int get () { out.println ("Erhalten: " + token); return token; } // Wert von token setzen synchronized void set (int value) { token = value; out.println ("Gesetzt: " + token); } } class NoWaitProducer implements Runnable { NoWaitMonitor monitor; int speed; NoWaitProducer (NoWaitMonitor monitor, int speed) { this.monitor = monitor; this.speed = speed; new Thread (this, "Producer").start(); } public void run() { int i = 0; while (true) { monitor.set (i++); try { Thread.sleep ((int) (Math.random() * speed)); } catch (InterruptedException e) { } } } } class NoWaitConsumer implements Runnable { NoWaitMonitor monitor; int speed; 438
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
NoWaitConsumer (NoWaitMonitor monitor, int speed) { this.monitor = monitor; this.speed = speed; new Thread (this, "Consumer").start(); } public void run() { while (true) { monitor.get(); try { Thread.sleep((int) (Math.random() * speed)); } catch (InterruptedException e) {} } } } Im Folgenden finden Sie ein Beispiel der Ausgabe des Programms, in dem der Erzeuger schneller arbeitet als der Verbraucher: C:\MasteringJava\Ch08>javac NoWaitPandC.java C:\MasteringJava\Ch08>java NoWaitPandC 100 400 Gesetzt: 0 Erhalten: 0 Gesetzt: 1 Gesetzt: 2 Gesetzt: 3 Gesetzt: 4 Erhalten: 4 Gesetzt: 5 Gesetzt: 6 Gesetzt: 7 Gesetzt: 8 Gesetzt: 9 Gesetzt: 10 Erhalten: 10 Gesetzt: 11 Gesetzt: 12 Wie Sie sehen, werden eine Menge Daten generiert (ausgegeben als Gesetzt), aber vor der Verarbeitung (ausgegeben als Erhalten) bereits wieder .berschrieben. Im Folgenden finden Sie ein Beispiel f.r die Ausgabe des Programms, in dem der Verbraucher schneller arbeitet als der Erzeuger: C:\MasteringJava\Ch08>java NoWaitPandC 400 100 Gesetzt: 0 Erhalten: 0 Erhalten: 0 Erhalten: 0 439
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
Erhalten: 0 Erhalten: 0 Erhalten: 0 Gesetzt: 1 Gesetzt: 2 Erhalten: 2 Gesetzt: 3 Erhalten: 3 Erhalten: 3 Erhalten: 3 Erhalten: 3 Erhalten: 3 Gesetzt: 4 Erhalten: 4 Erhalten: 4 Erhalten: 4 Erhalten: 4 Erhalten: 4 In diesem Beispiel werden einige Daten mehrmals verarbeitet. Bei der zweiten Implementierung des Beispielprogramms wird mit den Methoden wait() und notify() sichergestellt, dass alle Daten exakt einmal erzeugt und verarbeitet werden. Das Programm unterscheidet sich vom vorherigen nur in der Implementierung des Monitors. Mit der booleschen Variablen valueSet wird angegeben, ob die Daten bereit zur Verarbeitung sind oder bereits verarbeitet wurden. Die Methode get() testet zuerst, ob die Daten bereit zur Verarbeitung sind. Falls nicht, wartet der aufrufende Thread, bis ein anderer Thread die Daten liefert und den aktuellen Thread benachrichtigt. Anschließend wird die boolesche Variable gesetzt, um anzugeben, dass die Daten verarbeitet wurden. Daraufhin werden alle Threads, die darauf warten, neue Daten zu erzeugen, dar.ber benachrichtigt, dass ihre Datenproduktion beginnen kann. Falls kein produzierender Thread vorhanden ist, wird die Methode notify() ignoriert. Im Folgenden finden Sie die Methode get(): synchronized int get() { if (! valueSet) try { wait(); } catch (InterruptedException e) { } valueSet = false; out.println ("Erhalten: " + token); notify(); return token; } Analog zu der Methode get() testet die Methode set() zun$chst, ob die Daten bereits verarbeitet wurden. Falls nicht, wartet der aufrufende Thread, bis ein anderer Thread die Daten verarbeitet und den aktuellen Thread benachrichtigt. Anschließend wird die boolesche Variable gesetzt, um anzugeben, dass die Daten zur Ver440
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
arbeitung bereit sind. Daraufhin werden alle Threads, die darauf warten, Daten zu verarbeiten, dar.ber benachrichtigt, dass sie mit der Datenverarbeitung beginnen kGnnen. Falls kein wartender Thread vorhanden ist, wird die Methode notify() ignoriert. Im Folgenden finden Sie die Methode set(): synchronized void set (int value) { if (valueSet) try { wait(); } catch (InterruptedException e) { } valueSet = true; token = value; out.println ("Gesetzt: " + token); notify(); } Das vollst$ndige Listing des Programms ist im Folgenden abgedruckt: import java.io.*;
Listing 14.11
public class PandC { static int produceSpeed = 200; static int consumeSpeed = 200;
PandC.java
public static void main (String args[]) { if (args.length > 0) produceSpeed = Integer.parseInt (args[0]); if (args.length > 1) consumeSpeed = Integer.parseInt (args[1]); Monitor monitor = new Monitor(); new Producer(monitor, produceSpeed); new Consumer(monitor, consumeSpeed); try { Thread.sleep(1000); } catch (InterruptedException e) { } System.exit(0); } } class Monitor { PrintWriter out = new PrintWriter (System.out, true); int token; boolean valueSet = false; // Wert von token ermitteln synchronized int get () { if (! valueSet)
441
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
try { wait(); } catch (InterruptedException e) { } valueSet = false; out.println ("Erhalten: " + token); notify(); return token; } // Wert von token setzen synchronized void set (int value) { if (valueSet) try { wait(); } catch (InterruptedException e) { } valueSet = true; token = value; out.println ("Gesetzt: " + token); notify(); } } class Producer implements Runnable { Monitor monitor; int speed; Producer (Monitor monitor, int speed) { this.monitor = monitor; this.speed = speed; new Thread (this, "Producer").start(); } public void run() { int i = 0; while (true) { monitor.set (i++); try { Thread.sleep ((int) (Math.random() * speed)); } catch (InterruptedException e) { } } } } class Consumer implements Runnable { Monitor monitor; int speed; 442
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
Consumer (Monitor monitor, int speed) { this.monitor = monitor; this.speed = speed; new Thread (this, "Consumer").start(); } public void run() { while (true) { monitor.get(); try { Thread.sleep ((int) (Math.random() * speed)); } catch (InterruptedException e) { } } } } Im Folgenden finden Sie eine Beispielausgabe des Programms: C:\MasteringJava\Ch08>javac PandC.java C:\MasteringJava\Ch08>java PandC 400 100 Gesetzt: 0 Erhalten: 0 Gesetzt: 1 Erhalten: 1 Gesetzt: 2 Erhalten: 2 Gesetzt: 3 Erhalten: 3 Gesetzt: 4 Erhalten: 4 In diesem Beispiel werden alle generierten Daten exakt einmal verarbeitet.
Thread-Priorit5ten und Zuteilung von Rechenzeit Durch PrioritGten wird gew$hrleistet, dass wichtige oder zeitkritische Threads h$ufig oder sofort ausgef.hrt werden. Durch die Zuteilung von Rechenzeit kGnnen Priorit$ten ber.cksichtigt und eine gewisse Ausgewogenheit erzielt werden. Wenn nur eine CPU vorhanden ist, m.ssen alle ausf.hrbaren Threads abwechselnd ausgef.hrt werden. Bei der Zuteilung von Rechenzeit wird die Ausf.hrungsreihenfolge mehrerer Threads festgesetzt.
443
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
Priorit5tswerte von Threads In Java wird jedem Thread ein Priorit$tswert zugewiesen. Wenn mehrere Threads um die CPU-Zeit konkurrieren, erh$lt der Thread mit dem hGchsten Priorit$tswert den Vorzug. Priorit$tswerte von Threads, die benutzererstellten Threads zugeordnet werden kGnnen, sind Ganzzahlen im Bereich zwischen Thread.MIN_PRIORITY und Thread.MAX_PRIORITY. Benutzeranwendungen arbeiten normalerweise mit einem Priorit$tswert von Thread.NORM_PRIORITY. In JDK 1.3 haben die Konstanten MIN_PRIORITY, MAX_PRIORITY und NORM_PRIORITY der Klasse Thread die Werte 1, 10 bzw. 5. Jeder Thread-Gruppe wird ein maximaler Priorit$tswert zugeordnet. Wenn Element-Threads und Thread-Gruppen erzeugt oder deren Priorit$tswerte ge$ndert werden sollen, stellt der zugeordnete maximale Priorit$tswert den oberen Grenzwert dar. Bei der Erstellung erbt ein Thread den Priorit$tswert des erzeugenden Threads, sofern dieser Priorit$tswert nicht den Grenzwert der .bergeordneten ThreadGruppe .bersteigt. Mit der Methode setPriority() der Klasse Thread kann der Priorit$tswert eines Threads gesetzt werden. Wenn sich dieser Wert außerhalb des zul$ssigen Bereichs befindet, wird eine IllegalArgumentException ausgelGst. Wenn der Wert grGßer als der maximale Priorit$tswert der .bergeordneten ThreadGruppe ist, wird der maximale Priorit$tswert verwendet. Mit der Methode setMaxPriority() der Klasse ThreadGroup kann der maximale Priorit$tswert einer Thread-Gruppe festgelegt werden. Aus Sicherheitsgr.nden (damit ein benutzererzeugter Thread die CPU nicht mit Beschlag belegt) kann ein Webbrowser verhindern, dass ein Applet seine Priorit$t $ndert.
Preemptive Rechenzeitzuteilung und Zeitscheibenverfahren Die Rechenzeitzuteilung in Java ist preemptiv: Wenn ein Thread mit einer hGheren Priorit$t als der aktuell laufende den Zustand ausfhrbar erh$lt, wird der Thread mit der hGheren Priorit$t sofort ausgef.hrt und der aktuell laufende Thread in die Warteschlange zur.ckgestellt, wo er wartet, bis er wieder an der Reihe ist. Ein Thread kann sein CPU-Ausf.hrungsprivileg durch Aufruf der Methode yield() explizit an andere wartende Threads mit derselben Priorit$t .bergeben. In einigen Implementierungen werden Threads im so genannten Zeitscheibenverfahren ausgef.hrt: Threads mit denselben Priorit$tswerten haben dieselben Chancen, in einer Art „Reihum-Methode“ ausgef.hrt zu werden. Sogar Threads mit niedrigeren Priorit$ten erhalten einen kleinen Anteil der Ausf.hrungszeit, ann$hernd proportional zu ihren Priorit$tswerten. Deshalb muss kein Thread langfristig auf CPU-Zeit verzichten. Andere Implementierungen setzen das Zeitscheibenverfahren nicht ein. Ein Thread gibt hier die Kontrolle nur in den folgenden F$llen auf: Seine Ausf.hrung ist beendet, der Thread wird von einem Thread mit hGherer Priorit$t verdr$ngt oder der Thread wird von einer I/O-Operation oder den Methodenaufrufen sleep(), wait() oder suspend() blockiert. Bei rechenintensiven Threads sollte gelegentlich die
444
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
Methode yield() aufgerufen werden, damit auch andere Threads eine Ausf.hrungschance erhalten. Dadurch kann die gesamte interaktive Ansprechbarkeit von grafischen Benutzerschnittstellen verbessert werden. Bis zum JDK 1.1 arbeitet die virtuelle Java-Maschine f.r Windows 95/98 und NT mit dem Zeitscheibenverfahren. Die virtuelle Java-Maschine f.r Solaris 2 verwendet dieses Verfahren nicht. In Java 2 steht aber f.r Solaris eine Variation dieses Verfahrens zur Verf.gung. Dazu m.ssen jedoch einige Patches installiert werden und deshalb ist dieses Verfahren nicht standardm$ßig aktiviert. In der Readme-Datei von JDK finden Sie eine Liste der Patches, die f.r Solaris 2.5.1 und Solaris-x86 installiert werden m.ssen. Anschließend m.ssen Sie zur Aktivierung des Zeitscheibenverfahrens die Umgebungsvariable THREADS_FLAG auf native setzen. F.r das Standardverhalten setzen Sie diese Variable auf green (oder belassen sie ungesetzt). Die Laufzeitoptionen -native oder -green kGnnen auch mit den Befehlszeilen-Tools wie java und javac festgelegt werden.
Tipp
Rechenzeitzuteilung bei Threads mit unterschiedlichen Priorit5ten Das n$chste Beispiel zeigt die Auswirkung der Rechenzeitzuteilung bei Threads mit unterschiedlichen Priorit$ten. Das Hauptprogramm akzeptiert ein optionales Befehlszeilenargument, das angibt, ob die erzeugten Threads sich regelm$ßig abwechseln. Das Hauptprogramm beginnt mit vier Threads mit den folgenden Priorit$tswerten 1, 2, 4 und 4. Jeder Thread erhGht seinen Z$hler 600.001 mal und .bergibt optional bei jeder 3.000sten ErhGhung sein Ausf.hrungsprivileg an Threads mit derselben Priorit$t. Da der Haupt-Thread einen hGheren Priorit$tswert (5) als diese rechenintensiven Threads hat, kann der Haupt-Thread alle 0,3 Sekunden auf die CPU zugreifen, um die Z$hlerwerte der vier Berechnungs-Threads auszugeben. Das Programm lautet folgendermaßen: import java.io.*;
Listing 14.12
public class PriorityTest { static int NUM_THREADS = 4; static boolean yield = true; static int counter[] = new int[NUM_THREADS];
PriorityTest.java
public static void main (String args[]) { PrintWriter out = new PrintWriter (System.out, true); int numIntervals = 10; if (args.length > 0) yield = false; out.println ("yield() verwenden? " + (yield ? "JA" : "NEIN")); for (int i = 0; i < NUM_THREADS; i++) (new PrTestThread ((i > 1) ? 4 : (i + 1), i)).start(); 445
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
out.println(); // Die Z[hlerwerte wiederholt ausgeben int step = 0; while (true) { boolean allDone = true; try { Thread.sleep (100); } catch (InterruptedException e) { } out.print ("Schritt " + (step++) + ": Z`HLER:"); for (int j = 0; j < NUM_THREADS; j++) { out.print (" " + counter[j]); if (counter[j] < 2000000) allDone = false; } out.println(); if (allDone) break; } System.exit(0); } } class PrTestThread extends Thread { int id; PrTestThread (int priority, int id) { super ("PrTestThread#" + id); this.id = id; setPriority(priority); } public void run() { for (int i = 0; i <= 2000000; i++) { if (((i % 3000) == 0) && PriorityTest.yield) yield(); PriorityTest.counter[id] = i; } } } Im Folgenden finden Sie eine Beispielausgabe des Programms, das auf einem System mit Zeitscheibenverfahren (Windows 95/98/NT oder Solaris mit nativen Threads) ausgef.hrt wurde. Die Berechnungs-Threads wechseln sich hier h$ufig ab: C:\MasteringJava\Ch08>javac PriorityTest.java C:\MasteringJava\Ch08>java PriorityTest yield() verwenden? JA Schritt 0: Z`HLER: 2999 0 530999 533999 446
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
Schritt Schritt Schritt Schritt Schritt Schritt Schritt
1: 2: 3: 4: 5: 6: 7:
Z`HLER: Z`HLER: Z`HLER: Z`HLER: Z`HLER: Z`HLER: Z`HLER:
5999 2999 1073999 1085999 8999 5999 1607999 1637999 149999 149999 2000000 2000000 704999 701999 2000000 2000000 1249866 1259999 2000000 2000000 1796999 1817999 2000000 2000000 2000000 2000000 2000000 2000000
Dieser Ausgabe kGnnen Sie einige .berraschende Ergebnisse entnehmen. Die beiden Threads mit der hGchsten Priorit$t versuchen, die meiste CPU-Zeit zu beanspruchen. Da aber die Priorit$ten 1 und 2 so nahe beieinander liegen, wird die CPUZeit knapper zugeteilt. Im Folgenden ist eine Beispielausgabe desselben Programms abgedruckt, das aber auf einer virtuellen Java-Maschine ohne Zeitscheibenverfahren ausgef.hrt wurde. Auch hier wurde die Methode yield() verwendet. harpoon:/users/me/java/examples/ch8> javac PriorityTest.java harpoon:/users/me/java/examples/ch8> java PriorityTest yield() verwenden? JA Schritt Schritt Schritt Schritt Schritt Schritt Schritt Schritt Schritt Schritt Schritt Schritt
0: Z`HLER: 0 0 103563 101999 1: Z`HLER: 0 0 206999 208476 2: Z`HLER: 0 0 314999 312189 3: Z`HLER: 0 0 419999 416889 4: Z`HLER: 0 0 527999 520335 5: Z`HLER: 0 67070 600000 600000 6: Z`HLER: 0 295645 600000 600000 7: Z`HLER: 0 521522 600000 600000 8: Z`HLER: 145375 600000 600000 600000 9: Z`HLER: 374097 600000 600000 600000 10: Z`HLER: 515023 600000 600000 600000 11: Z`HLER: 600000 600000 600000 600000
Hier ist deutlich ersichtlich, dass die Threads mit niedrigeren Priorit$tswerten erst eine Chance erhalten, ausgef.hrt zu werden, wenn die Ausf.hrung aller Threads mit einer hGheren Priorit$t beendet ist. Sie m.ssen mit der Einstellung des Maximalwertes f.r die Z$hler auf Ihrem Rechner experimentieren, um vern.nftige Ergebnisse zu erzielen. Verwenden Sie keinen so niedrigen Wert, dass die Ausf.hrung bereits nach zwei oder drei Schritten beendet ist. Ein Wert, der .ber 20 Schritte hinausgeht, ist aber etwas zu hoch.
Hinweis
Im Folgenden finden Sie die Ausgabe desselben Programms, das auf einem System mit Zeitscheibenverfahren aber ohne yield() ausgef.hrt wurde:
447
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
C:\MasteringJava\Ch08>java PriorityTest 0yield() verwenden? NEIN Schritt Schritt Schritt Schritt Schritt Schritt Schritt Schritt
Hinweis
0: 1: 2: 3: 4: 5: 6: 7:
Z`HLER: Z`HLER: Z`HLER: Z`HLER: Z`HLER: Z`HLER: Z`HLER: Z`HLER:
60965 61129 546422 441717 126821 123038 1052759 951456 188446 182858 1555466 1484050 252345 246123 2000000 2000000 764587 861142 2000000 2000000 1302086 1435581 2000000 2000000 1900295 1977578 2000000 2000000 2000000 2000000 2000000 2000000
Wenn das Programm unter einem anderen Betriebssystem ausgef.hrt wird, kGnnte eine unterschiedliche Ausgabe generiert werden. Dies ist abh$ngig von dem Algorithmus f.r die Rechenzeitzuteilung des Betriebssystems und/oder dem jeweiligen Port des JDK. Interessanterweise haben die Threads mit niedriger Priorit$t eine hGhere Chance, ausgef.hrt zu werden als in dem vorherigen Durchlauf mit yield(). Das liegt wahrscheinlich daran, dass yield() den urspr.nglichen Zuteilungsplan des Schedulers f.r Threads mit niedriger Priorit$t stGrt, indem der Scheduler zuerst nach Threads mit derselben Priorit$t suchen muss. Ohne yield() kGnnen alle Zuteilungspl$ne f.r Threads mit niedriger Priorit$t abgearbeitet werden. F.r das letzte Beispiel wurde das Programm ohne yield() und auf einem System ohne Zeitscheibenverfahren ausgef.hrt: harpoon:/users/me/java/examples/ch8> java PriorityTest 0 yield() verwenden? NEIN Schritt Schritt Schritt Schritt Schritt Schritt Schritt Schritt Schritt Schritt Schritt Schritt
0: Z`HLER: 0 0 203552 0 1: Z`HLER: 0 0 203552 210978 2: Z`HLER: 0 0 413376 210978 3: Z`HLER: 0 0 413376 422790 4: Z`HLER: 0 0 600000 444539 5: Z`HLER: 0 57353 600000 600000 6: Z`HLER: 0 272848 600000 600000 7: Z`HLER: 0 488745 600000 600000 8: Z`HLER: 100596 600000 600000 600000 9: Z`HLER: 314749 600000 600000 600000 10: Z`HLER: 587513 600000 600000 600000 11: Z`HLER: 600000 600000 600000 600000
Die Threads mit einer niedrigeren Priorit$t haben keine Chance, ausgef.hrt zu werden, solange die Threads mit einer hGheren Priorit$t noch nicht beendet sind. Sogar Threads mit denselben Priorit$tswerten kGnnen erst ausgef.hrt werden, wenn der Haupt-Thread den laufenden Thread an das Ende der Warteschlange setzt. Wenn der Haupt-Thread die Programmkontrolle nach dem Ausgeben der Z$hlerwerte abgibt, erh$lt der Thread, der an erster Stelle in der Warteschlange steht, eine Aus448
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Multithreading fr Fortgeschrittene
f.hrungschance. Sie kGnnen dies im obigen Ausgabebeispiel .berpr.fen: Nur der Z$hler von einem der Threads mit der hGchsten Priorit$t wird zwischen zwei Ausgaben hochgesetzt.
Lokale Thread-Variablen Obwohl das Konzept der lokalen Thread-Variablen in die API in Java 2 eingef.hrt wurde, ist es eigentlich nicht neu f.r Java. Eine $hnliche Klasse war bereits im internen sun.server.util-Package von Java Web Server enthalten. Anscheinend war die Nachfrage groß genug, um das Konzept zu einem Standardbestandteil von Java zu machen: Es ist nun in den Klassen java.lang.ThreadLocal und java.lang.InheritableThreadLocal zu finden. Lokale Thread-Variablen ermGglichen, dass einzelne ThreadInstanzen eigenst$ndige Kopien von Variablen besitzen. Normalerweise werden Sie ThreadLocal-Variablen kaum verwenden, InheritableThreadLocal-Variablen schon eher. Lokale Thread-Variablen werden wichtig, wenn Sie statische Variablen zum Speichern von Bezeichnern (z.B. f.r den Zugriff auf Datenbanken oder zur Identifizierung einer Sitzung) benGtigen. In diesem Fall nutzen Sie die Variable nur innerhalb eines bestimmten laufenden Threads oder in einem Thread und in allen seinen Nachkommen. Eine ThreadLocal-Variable wird durch die als protected deklarierte Methode initialValue() initialisiert. Der Standardanfangswert einer ThreadLocal-Variable ist null. Zum Zuweisen eines anderen Anfangswertes m.ssen Sie eine Unterklasse von ThreadLocal bilden und die Methode .berschreiben. Der Wert kann auch sp$ter mit der Methode set() ge$ndert werden.
Hinweis
Das folgende Beispiel zeigt die Unterschiede zwischen Klassenvariablen und ThreadLocal-Variablen. In einer Multithread-Umgebung erzeugt das Programm mehrere Instanzen einer Klasse und z$hlt die erzeugte Anzahl in der Klassenvariablen counter. Jedem Thread steht auch die statische Variable threadLocal zur Verf.gung. Der Wert von threadLocal wird als Angabe f.r die inaktive Zeit verwendet. Die innere Klasse MyThreadLocal bietet dem Thread in der Methode initialValue() eine inaktive Zeit von bis zu 1.000 Millisekunden. Beachten Sie, dass beide Variablen als static deklariert sind. Sie erwarten wahrscheinlich, dass eine Kopie beider Variablen f.r alle Instanzen der Klasse vorhanden ist. import java.io.*;
Listing 14.13
public class LocalThreadVars implements Runnable {
LocalThreadVars.java
static private class MyThreadLocal extends ThreadLocal { protected Object initialValue() { return new Double (Math.random() * 1000.0); } } static ThreadLocal threadLocal =
449
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Threads und Multithreading
new MyThreadLocal(); static int counter = 0; private LocalThreadVars() { counter++; } public void run() { LocalThreadVars myLTV = new LocalThreadVars(); displayValues(); try { Thread.currentThread().sleep ( ((Double)threadLocal.get()).longValue()); myLTV.displayValues(); } catch (InterruptedException e) { e.printStackTrace(); } } private void displayValues() { System.out.println (threadLocal.get() + "\t" + counter + "\t" + Thread.currentThread().getName()); } public static void main (String args[]) { LocalThreadVars ltv = new LocalThreadVars(); ltv.displayValues(); for (int i=0;i<5;i++) { Thread t = new Thread (ltv); t.start(); } } } Nach der Programmausf.hrung werden Sie eines Besseren belehrt. Im Folgenden finden Sie ein Beispiel der von dem Programm erzeugten Ausgabe. Da das Programm Zufallszahlen verwendet, wird Ihre Ausgabe hGchstwahrscheinlich anders aussehen. C:\MasteringJava\Ch08>javac LocalThreadVars.java C:\MasteringJava\Ch08>java LocalThreadVars 353.6782033483381 1 main 607.6189861951625 2 Thread-0 204.82242103443437 3 Thread-1 216.68547449023978 4 Thread-2 960.1210961092618 5 Thread-3 221.4544981562063 6 Thread-4 204.82242103443437 6 Thread-1 450
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ausblick
216.68547449023978 221.4544981562063 607.6189861951625 960.1210961092618
6 6 6 6
Thread-2 Thread-4 Thread-0 Thread-3
Beachten Sie, dass die Variable counter von verschiedenen Threads gemeinsam genutzt wird, die Variable threadLocal aber nicht. Wenn die statische Variable ThreadLocal von verschiedenen Threads gemeinsam genutzt w.rde, w.rde derselbe Wert f.r alle Werte statt nur f.r mehrere Klassen innerhalb desselben Threads wiederholt werden. Sie werden die Klasse ThreadLocal hGher sch$tzen, wenn Sie sich mit der Programmierung von Netzwerken befassen. F.r jetzt gen.gt es, wenn Sie von ihrer Existenz wissen und sich bei Bedarf an sie erinnern.
Daemon-Threads Daemon-Threads sind Dienst-Threads. Sie sind nur vorhanden, um f.r andere Threads Dienste bereitzustellen. Normalerweise werden sie in Endlosschleifen ausgef.hrt und warten darauf, dass Clients ihre Dienste anfordern. Wenn es sich bei allen verbleibenden aktiven Threads um Daemon-Threads handelt, wird die virtuelle Java-Maschine beendet. Ein Timer-Thread, der in regelm$ßigen Intervallen aktiv wird, stellt ein gutes Beispiel f.r Daemon-Threads dar. Dieser Timer-Thread kann andere Threads regelm$ßig .ber Zeit.berschreitungen benachrichtigen. Wenn keine anderen Threads ausgef.hrt werden, gibt es keinen Bedarf f.r den Timer-Thread. Rufen Sie zum Erzeugen eines Daemon-Threads die Methode setDaemon() sofort nach der Erzeugung des Threads und vor dem Beginn der Thread-Ausf.hrung auf. Der Konstruktor des Threads ist f.r diesen Methodenaufruf der geeignete Ort. Standardm$ßig handelt es sich bei allen von einem Daemon-Thread erzeugten Threads ebenfalls um Daemon-Threads. Die Syntax der Methode setDaemon() lautet: setDaemon (boolean isDaemon) Wenn isDaemontrue ist, wird der Thread als Daemon-Thread, andernfalls als NichtDaemon-Thread gekennzeichnet.
Ausblick Kapitel 15: Java Database Connectivity (JDBC) behandelt die Java Database Connectivity (JDBC) und durchleuchtet Java als eine Front-End-Datenbankanwendung.
451
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Java als ein Datenbank-Front-End
454
Client/Server-Datenbanken
455
Das zweischichtige Datenbankmodell
456
Das dreischichtige Datenbankmodell
457
JDBC-API
458
Ein Beispiel fr eine JDBC-Datenbank
481
JDBC-Treiber
488
Die JDBC-ODBC-Brcke
490
Aktuelle JDBC-Treiber
491
Alternative Connectivity-Strategien
491
15 Ausblick
493
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Aus „Mastering Java 2“ von John Zukowski, ISBN 0-7821-2180-2, 1280 Seiten, $49,99
In unserem gegenw$rtigen Informationszeitalter stellt eine Datenbank das Tool zum Sammeln und Bearbeiten von Daten dar. Datenbanken bilden in vielen Firmen die Grundlage der Infrastruktur. Ein Datenbanksystem eignet sich hervorragend f.r die Datenspeicherung und -abfrage. Um die gespeicherten Daten aber anzuzeigen und damit zu arbeiten, ist eine so genannte Front-End-Anwendung erforderlich. Front-Ends stellen f.r Datenbanken benutzerfreundliche Oberfl$chen zur Verf.gung. Die meisten Firmen arbeiten aber nicht mit einheitlichen Computersystemen. Die Grafik- und Marketingabteilungen verwenden wahrscheinlich Macintosh-Systeme, technische Abteilungen UNIX-Workstations und Verkaufsabteilungen eine Version von Microsoft Windows (Windows 95/98/2000, Windows NT 4, Windows NT 3.51 oder Windows 3.1) auf PCs. Entwickler m.ssen all diesen unterschiedlichen Systemen Rechnung tragen, wenn sie Daten einer Firmendatenbank zug$nglich machen wollen. In diesem Kapitel wird Java als die LGsung f.r die Problematik Datenbank-Front-End beleuchtet: Java stellt mit der Database Connectivity API eine einzelne und konsistente Programmierschnittstelle zur Verf.gung.
Java als ein Datenbank-Front-End Java bietet Entwicklern, die Front-End-Anwendungen f.r einen Datenbankserver erstellen, mehrere Vorteile. Java-Programme kGnnen ohne erneute Kompilierung an alle Computerarchitekturen und Betriebssysteme verteilt werden, die mit der virtuellen Maschine von Java ausgestattet sind. F.r große Unternehmen bedeutet eine einheitliche Entwicklungsplattform eine enorme Kostenreduzierung: Programmierer m.ssen nicht mehr separate Anwendungen f.r alle unterschiedlichen im Unternehmen eingesetzten Plattformen schreiben. Java ist auch f.r Fremdhersteller interessant: Ein einziges Java-Programm kann sowohl f.r die Anforderungen von kleinen als auch von Großkunden geeignet sein. Die Bereitstellung und Wartung der unterschiedlichen Hard- und Softwaresysteme (Clients) bedingen weitere Kosten f.r Unternehmen. Systeme wie Windows-PCs sowie Desktop-zentrierte Macintosh- und UNIX-Clients (Fat Clients) kGnnen sich in einem Kostenrahmen von $10.000 bis zu $15.000 pro Rechnerinstallation bewegen. Die Java-Technologie ermGglicht Unternehmen, kleinere Systeme einzusetzen. Diese Systeme basieren auf einem Java-Chipset und kGnnen s$mtliche Java-Programmme auf einem integrierten Java-Betriebssystem ausf.hren. Java-basierte Clients (Thin Clients), die mit einem Minimum an Hardwareressourcen betrieben werden, aber dennoch die komplette Java-Umgebung ausf.hren, kosten ungef$hr $750 pro Rechnerinstallation. Laut zahlreicher Untersuchungen kGnnte die Kostenersparnis f.r ein Unternehmen, das 10.000 Fat-Client-Systeme in ThinClient-Systeme umwandelt, bei bis zu $100 Millionen pro Jahr liegen. Da PentiumSysteme auch schon f.r weniger als $1.000 erh$ltlich sind, erkl$ren sich die hGheren Kosten f.r diese Systeme haupts$chlich durch die Konfigurations- und Wartungsaufwendungen (nicht zuletzt f.r die lokalen Speicher). 454
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Client/Server-Datenbanken
Daraus folgt, dass der Anreiz, Java-basierte UnternehmenslGsungen zu entwickeln, sehr groß ist. Unternehmen sind $ußerst daran interessiert, ihre Anwendungen von architektur- und betriebssystemspezifischen Modellen auf netzwerkzentrierte Modelle umzustellen. Java stellt eine langfristige Strategie zum Einsparen von Ressourcenkosten dar. F.r Entwickler bedeutet dies eine enorme Marktchance. Es gibt nur sehr wenige, mittlere bis große Organisationen, die nicht zumindest in einigen Unternehmensbereichen Datenbanken einsetzen. Die meisten Unternehmen verwenden Datenbanken f.r s$mtliche gesch$ftliche Aktivit$ten, von der Personal- bis hin zur Verkaufsabteilung. Dieses Kapitel behandelt die Java Database Connectivity (JDBC) einschließlich der Frage, wie Java-Anwendungen und -Applets mit Hilfe der aktuellen JDBC-API mit Datenbankservern verbunden werden kGnnen.
Client/Server-Datenbanken Die Evolution der relationalen Datenspeicherung begann 1970 mit der Arbeit von Dr. E. F. Codd, der zwGlf Regeln zur Identifizierung von Beziehungen zwischen Datenelementen vorschlug. Codds Regeln bildeten die Grundlage bei der Entwicklung von Datenverarbeitungssystemen. Die heutigen Relational Database Management Systems (RDBMS) sind das Ergebnis von Codds Vision. Daten in RDBMS werden als Zeilen von verschiedenartigen Informationen in Tabellen gespeichert. Mit einer strukturierten Sprache (SQL) werden die Daten abgefragt (ermittelt), gespeichert und ge$ndert. SQL (Structured Query Language) ist ein ANSIStandard. Alle großen kommerziellen RDBMS-Anbieter integrieren Mechanismen zum Ausf.hren von SQL-Anweisungen in ihre Systeme. Die ersten RDBMS-Anwendungen benutzten ein integriertes Model von Benutzerschnittstellencode, Anwendungscode und Datenbankbibliotheken. Dieses einschichtige bin$re Modell konnte nur auf lokalen Rechnern, typischerweise Mainframes, ausgef.hrt werden. Die Anwendungen waren einfach, aber leider nicht effizient und arbeiteten nicht in LANs. Das Modell ließ sich nicht normieren und der Anwendungs- und Benutzerschnittstellencode war eng an die Datenbankbibliotheken gekoppelt. Die folgende Abbildung illustriert das monolithische, einschichtige Datenbankmodell. Dar.ber hinaus konnten in dem monolithischen Modell mehrere Instanzen einer Anwendung nicht untereinander kommunizieren. Verschiedene Instanzen einer Anwendung konkurrierten daher oft miteinander. Der wechselweise Einsatz ist f.r RDBMS und DBMS (Database Management System) charakteristisch, weil die meisten großen kommerziellen Datenbanken relational sind und irgendeine Form von SQL unterst.tzen, damit Benutzer Beziehungen zwischen Datentabellen abfragen kGnnen.
Hinweis
455
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Abb. 15.1 Das monolithische, einschichtige Datenbankmodell
Das zweischichtige Datenbankmodell Zweischichtige Modelle tauchten mit der Einf.hrung der Servertechnologie auf. Die Entwicklung von Kommunikationsprotokollen und der umfangreiche Einsatz von LANs (Local Area Network) und WANs (Wide Area Network) ermGglichten es Datenbankentwicklern, ein Anwendungs-Front-End zu erstellen, das .ber eine Verbindung (Socket) zum Back-End-Server auf Daten zugreift. Die folgende Abbildung illustriert ein zweischichtiges Datenbankmodell, in dem die Clientsoftware mit der Datenbank .ber ein Socket verbunden ist.
Abb. 15.2 Das zweischichtige Datenbankmodell
Clientprogramme (die die Benutzerschnittstelle bereitstellen) senden SQL-Anforderungen an den Datenbankserver. Der Server liefert die entsprechenden Ergebnisse zur.ck. Der Client ist dabei f.r die Formatierung und die Anzeige der Daten verantwortlich. Clients verwenden auch in diesem Modell eine vom Hersteller mitgelieferte Bibliothek mit Funktionen, die die Kommunikation zwischen Client und Server verwalten. Die meisten Funktionsbibliotheken sind in C oder Perl geschrieben. Kommerzielle Datenbankanbieter erkannten das Potenzial, das im Hinzuf.gen von Intelligenz zu Datenbankservern lag. Sie entwickelten propriet$re Techniken, mit denen Datenbankdesigner Makroprogramme zur einfachen Datenbearbeitung erstellen konnten. Diese Makros, so genannte Stored Procedures, kGnnen im Bereich der Versionskontrolle und der Programmwartung Probleme verursachen. Da Stored Procedures in der Datenbank vorhandene, ausf.hrbare Programme sind, kGnnten sie versuchen, auf Spalten einer Datenbanktabelle zuzugreifen, nachdem die 456
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das dreischichtige Datenbankmodell
Tabelle ver$ndert wurde. Wenn z.B. eine Spalte mit dem Namen id in cust_id ge$ndert wird, wird eine Stored Procedure, die f.r den urspr.nglichen Spaltennamen vorgesehen war, nutzlos. Wenn die von einer Abfrage zur.ckgegebenen Daten nicht erwartet werden, kGnnen Trigger diese Probleme sogar noch verst$rken, was wiederum das Ergebnis eines Versuchs sein kGnnte, auf eine ge$nderte Tabellenspalte zuzugreifen. Trigger sind Stored Procedures, die automatisch ausgef.hrt werden, wenn in einer Tabelle (oder mehreren Tabellen) eine Aktion (wie z.B. das Einf.gen von Daten) ausgef.hrt wird. Trotz des Erfolgs von Client/Server-Architekturen weisen zweischichtige Datenbankmodelle die folgenden Schwachstellen auf: Anbieterspezifische Bibliothek: Beim Wechseln von einem Datenbankanbieter zu einem anderen muss ein betr$chtlicher Teil des Codes der Clientanwendung neu geschrieben werden. Versionskontrolle: Wenn ein Anbieter die clientseitigen Bibliotheken aktualisiert, m.ssen auch die Anwendungen, die auf die Datenbank zugreifen, neu kompiliert und verteilt werden. Anbieterbibliotheken arbeiten mit Low-Level-Datenmanipulation. Die Hauptbibliothek arbeitet normalerweise nur mit Abfragen und Aktualisierungen von einzelnen Datenzeilen oder -spalten. Auf der Serverseite kann dies durch eine Stored Procedure erweitert werden, was aber die Komplexit$t des Systems erhGht. Die gesamte Intelligenz, die f.r die Verwendung und Bearbeitung der Daten erforderlich ist, ist in der Clientanwendung implementiert, was auf der Clientseite zu langen Laufzeiten und zu Kostensteigerungen f.hrt.
Das dreischichtige Datenbankmodell Momentan besteht großes Interesse an mehrschichtigen Datenbankmodellen. Bei diesen Architekturen kommuniziert der Client mit einem Zwischenserver, der eine Abstraktionsschicht des RDBMS bereitstellt. Es brauchen nicht nur drei Schichten zu sein, aber konzeptionell stellen drei Schichten den n$chsten Schritt dar. Die folgende Abbildung illustriert ein dreischichtiges Datenbankmodell.
Abb. 15.3 Ein dreischichtiges Datenbankmodell
457
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
In der mittleren Schicht erfolgt die Bearbeitung von mehreren Clientanforderungen und die Verwaltung der Verbindung zu einem oder mehreren Datenbankservern. Das dreischichtige Modell weist wegen der mittleren Schicht die folgenden Vorteile gegen.ber dem zweischichtigen Modell auf: Es ist Multithread-f$hig und kann daher mehrere Clientverbindungen gleichzeitig verwalten. Es kann Verbindungen zu Clients .ber eine Vielzahl von herstellerneutralen Protokollen (von HTTP bis zu TCP/IP) aufnehmen, dann die Anforderungen an den passenden herstellerspezifischen Datenbankserver .bergeben und die Antworten schließlich an die entsprechenden Clients zur.ckliefern. Es kann mit einer Reihe von Business Rules programmiert werden, die die Datenbearbeitung verwalten. In die Business Rules kGnnten Regeln f.r Zugriffsbeschr$nkungen f.r bestimmte Datenbereiche bis hin zur
JDBC-API Die JDBC-API soll es Entwicklern ermGglichen, Datenbank-Front-Ends zu erstellen, ohne den Programmcode st$ndig anpassen zu m.ssen. Trotz der vom ANSI-Komitee festgelegten Standards verwendet jeder Anbieter von Datenbanksystemen eigene Methoden zum Verbinden und in einigen F$llen auch zum Kommunizieren mit ihrem System. Die Aussicht, robuste, plattformunabh$ngige Anwendungen und Web-basierte Applets erstellen zu kGnnen, veranlasste Entwickler dazu, sich .ber Java als Entwicklungswerkzeug f.r Front-End-Connectivity-LGsungen Gedanken zu machen. 458
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JDBC-API
Am Anfang deckten Softwarehersteller den Bedarf durch propriet$re LGsungen, indem native Methoden zur Integration von clientseitigen Bibliotheken verwendet oder eine dritte Schicht und ein neues Protokoll erzeugt wurden. Die Java Software Division, die bei Sun Microsystems f.r die Entwicklung der JavaProdukte zust$ndige Abteilung, arbeitete mit Anbietern von Datenbanken und Datenbank-Tools zusammen, um einen DBMS-unabh$ngigen Mechanismus zu entwickeln, der es ermGglichen sollte, clientseitige Anwendungen zu schreiben, ohne sich um die jeweils verwendete Datenbank k.mmern zu m.ssen. Das Ergebnis ist die JDBC-API, die Teil des JDK 1.3 ist. JDBC bietet Anwendungsentwicklern eine einzelne, einheitliche und datenbankunabh$ngige API. Die API stellt einen Standard zum Schreiben und einen Standard, der alle verschiedenen Anwendungsdesigns ber.cksichtigt, zur Verf.gung. Das Geheimnis liegt in einem Set von Java-Schnittstellen, die von einem Treiber implementiert werden. Der Treiber besorgt die Umwandlung der Standard-JDBC-Aufrufe in die f.r die unterst.tzte Datenbank spezifischen Aufrufe. Die folgende Abbildung zeigt dieses Vorgehen: Die Anwendung wird einmal geschrieben und dann an die verschiedenen Treiber .bergeben. Die Anwendung bleibt immer unver$ndert, nur die Treiber $ndern sich. Mit Treibern kann auch die mittlere Schicht (Middleware) eines mehrschichtigen Datenbankmodells entwickelt werden.
Abb. 15.4 JDBC-Datenbankmodell
Zus$tzlich zu einer einheitlichen und DBMS-unabh$ngigen Designgrundstruktur bietet JDBC Entwicklern auch ein Mittel zum Beibehalten der spezifischen Funktionalit$t der jeweiligen Datenbank an. JDBC-Treiber m.ssen zwar den Standard-ANSI-SQL-2 Entry Level unterst.tzen, in JDBC ist es aber mGglich, Abfragestrings direkt an den verbundenen Treiber zu .bergeben. Diese Strings kGnnen in ANSI SQL oder SQL geschrieben sein, m.ssen es aber nicht. Die Verwendung dieser Strings h$ngt von dem zugrunde liegenden Treiber ab. Diese Funktion schr$nkt nat.rlich die Freiheit des Anwendungsentwicklers ein, Datenbank-Back-Ends zu $ndern.
Hinweis
459
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Hinweis
Ausf.hrliche Informationen .ber SQL finden Sie in dem Buch SQL Instant Reference von Martin Gruber (Sybex-Verlag) oder in den ANSI-Standarddokumenten unter http://www.ansi.org. JDBC ist kein Derivat der Microsoft-Spezifikation Open Database Connectivity (ODBC). JDBC ist vollst$ndig in Java geschrieben, w$hrend ODBC eine C-Schnittstelle ist. ODBC kann zwar von nicht-C-Sprachen wie Visual Basic verwendet werden, es enth$lt aber das inh$rente Entwicklungsrisiko von C, wie z.B. Arbeitsspeicherfehler. Sowohl JDBC als auch ODBC basieren jedoch auf dem X/Open SQL Command Level Interface (CLI). Diese gemeinsame konzeptionelle Basis ermGglichte es, dass die Arbeit an der API schnell voranschritt und die Akzeptanz und das Erlernen der API vereinfacht wurde. Sun bietet eine JDBC-ODBC-Br.cke an, die JDBC in ODBC .bersetzt. Diese mit nativen Methoden durchgef.hrte Implementierung ist sehr klein und effizient. Im Allgemeinen gibt es zwei Schnittstellenebenen in der JDBC-API: die Anwendungsebene (Application Layer), in der der Entwickler die API f.r Datenbankaufrufe .ber SQL verwendet und die Ergebnisse abruft sowie die Treiberebene (Driver Layer), die die gesamte Kommunikation mit einer spezifischen Treiberimplementierung verwaltet. Jede JDBC-Anwendung (oder jedes Applet) muss mindestens .ber einen JDBC-Treiber verf.gen und jeder Treiber ist f.r den jeweiligen verwendeten DBMS-Typ spezifisch. Ein Treiber muss jedoch nicht direkt einer Datenbank zugeordnet sein.
API-Komponenten Wie bereits weiter oben erw$hnt gibt es in der JDBC-API zwei verschiedene Ebenen: die Anwendungsebene, die von Datenbankanwendungsentwicklern benGtigt wird, und die Treiberebene, die die Treiberanbieter implementieren. Es ist sehr wichtig, das Konzept der Treiberebene zu verstehen: Der Treiber erzeugt einige der Objekte, die in der Anwendungsebene verwendet werden. Die folgende Abbildung zeigt die Verbindung zwischen der Treiber- und der Anwendungsebene.
Abb. 15.5 JDBC-API-Komponenten
460
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JDBC-API
Erfreulicherweise muss der Anwendungsentwickler nur die Standard-API-Schnittstellen verwenden, um JDBC-Kompatibilit$t zu gew$hrleisten. Der Entwickler des Treibers ist f.r die Entwicklung des Codes verantwortlich, der die Verbindung zur Datenbank herstellt und die Aufrufe der JDBC-Anwendungsebenen unterst.tzt. Es gibt vier Hauptschnittstellen, die jeder Treiber implementieren muss und eine Klasse, die die Anwendungs- und die Treiberebene .berbr.ckt. Die vier Schnittstellen sind Driver, Connection, Statement und ResultSet. In der Driver-Schnittstellenimplementierung wird die Verbindung zur Datenbank hergestellt. In den meisten Anwendungen wird auf die Driver-Schnittstelle .ber die Klasse DriverManager zugegriffen – was eine weitere Abstraktionsebene f.r den Entwickler darstellt. Die Schnittstellen Connection, Statement und ResultSet werden vom Treiberanbieter implementiert. Diese Schnittstellen repr$sentieren die Methoden, die der Anwendungsentwickler als reale Objektklassen behandelt und die dem Entwickler das Erstellen von Anweisungen und das Ermitteln von Ergebnissen ermGglichen. Die Unterscheidung zwischen Treiber- und Anwendungsebene ist daher etwas k.nstlich, aber sie erlaubt Entwicklern, Datenbankanwendungen zu erstellen, ohne dar.ber nachzudenken, wo die Objekte herkommen oder sich dar.ber Sorgen zu machen, welcher spezielle Treiber f.r die Anwendung erforderlich ist.
Die Treiberebene Es gibt eine Eins-zu-eins-Entsprechung zwischen der Datenbank und dem JDBCTreiber. Diese Betrachtungsweise ist in mehrschichtigen Modellen gebr$uchlich. Die Klasse Driver stellt eine vom Treiberanbieter implementierte Schnittstelle dar. Eine weitere wichtige Klasse ist die Klasse DriverManager, die .ber der Treiber- und der Anwendungsebene residiert. Die Klasse DriverManager verwaltet das Laden und Entladen der Treiber und das Herstellen von Verbindungen .ber Treiber. Die Klasse DriverManager stellt außerdem Funktionen f.r die Protokollierung und die
Hinweis
Die Schnittstelle Driver: Jedes JDBC-Programm muss mindestens .ber eine JDBCTreiberimplementierung verf.gen. Die Schnittstelle Driver macht die Klasse DriverManager und die JDBC-Anwendungsebene unabh$ngig von der verwendeten Datenbank. Ein JDBC-Treiber ist eine Implementierung der Schnittstellenklasse Driver. Treiber verwenden einen String, um Datenbanken aufzufinden und darauf zuzugreifen. Die Syntax dieses Strings $hnelt der von URL-Strings. Durch JDBC-URLStrings wird eine Trennung zwischen Anwendungs- und Treiberentwickler erzielt. Sun definiert die folgenden Ziele f.r Treiber-URLs: Der Name der URL f.r den Treiberzugriff sollte den Typ der verwendeten Datenbank definieren.
461
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Der Benutzer (Anwendungsentwickler) sollte von allen Verwaltungsaufgaben, die zum Herstellen einer Datenbankverbindung erforderlich sind, befreit sein. Daher sollten alle Informationen .ber die Datenbankverbindung (Host, Port, Datenbankname, Benutzerzugriff und PasswGrter) in der URL kodiert werden. Ein System zur Netzwerkbenennung (Network Naming System) kann eingesetzt werden, damit der Benutzer nicht den exakten Hostnamen und die Portnummer der Datenbank kodieren muss. Die im World Wide Web verwendete URL-Syntax unterst.tzt eine Standardsyntax, die diese Ziele erf.llt. Eine JDBC-URL hat die folgende Syntax und Struktur: jdbc:<subprotocol>:<subname> wobei <subprotocol> den Treibertyp definiert und <subname> einen kodierten Netzwerknamen enth$lt. Zum Beispiel: jdbc:oracle:products Der Datenbanktreiber ist hier ein Oracle-Treiber und der Subname ist eine lokale Datenbank mit dem Namen Products. Der Treiber ist so konzipiert, dass er den Subnamen bei der Herstellung der Verbindung zu einer Oracle-Datenbank entschl.sseln kann. Anstelle eines bestimmten Datenbanktreibernamens kann als Subprotokoll auch ein Network Naming Service angegeben werden. In diesem Fall w.rde das Subprotokoll den Naming Service definieren: jdbc:localnaming:human-resources Im obigen Beispiel definiert das Subprotokoll einen lokalen Dienst, der den Subnamen human-resources in eine Adresse eines Datenbankservers umwandelt. Wenn der Benutzer von der Eingabe der aktuellen Position und des Namens der Datenbank sowie des Datenbankbenutzernamens und des Datenbankpassworts befreit werden soll, bietet sich dieses Vorgehen an. Die obige URL legt einen Treiber mit dem Namen localnaming fest. Dabei kGnnte es sich um ein Java-Programm handeln, das eine einfache, lineare Suche ausf.hrt und human-resources z.B. in hrdatabase1.eng:888/personnel .bersetzt sowie den Benutzernamen user und das Passwort matilda kennt. Die Einzelheiten der Verbindung bleiben dem Benutzer verborgen. Normalerweise kennt der Anwendungsentwickler den Speicherort der Datenbank und es kGnnte sein, dass er keine Umadressierung verwenden will. In diesem Fall kann die URL mit dem Speicherort des Hosts und speziellen Port- und Datenbankinformationen erg$nzt werden. jdbc:msql://dbserver.eng:1112/bugreports
462
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JDBC-API
Im obigen Beispiel wird mit einem Datenbanktreiber des Typs msql ein Server mit dem Namen dbserver in der Dom$ne eng gesucht und versucht, .ber Port 1112 eine Verbindung mit einem Datenbankserver herzustellen, der die Datenbank bugreports enth$lt. Dabei wird der Standardbenutzername und das Standardpasswort verwendet. Subprotokollnamen kGnnen sich .berschneiden. Um dies zu vermeiden, unterh$lt Sun informell ein Kartei mit reservierten Namen. N$here Informationen .ber das Registrieren von JDBC-Subprotokollnamen finden Sie in der JDBC-Spezifikation.
Hinweis
Die Anbieter der Treiber implementieren die Schnittstelle Driver, indem f.r jede der folgenden Schnittstellenmethoden Methoden erstellt werden: Signatur: public interface java.sql.Driver public abstract Connection connect(String url, Properties info) throws SQLException: Die Treiberimplementierung dieser Methode sollte den im URL-String .bergebenen Subprotokollnamen auf
463
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Informationen zu DriverPropertyInfo Fr jede m"gliche Eigenschaft steht eine Instanz von DriverPropertyInfo zur Verfgung. Zur Beschreibung der Eigenschaft sind fnf Instanzvariablen verfgbar, die die automatische Erstellung eines grafischen Dialogfeldes aktivieren und die Informationen abfragen: name
Name der Eigenschaft
description Kurze Beschreibung der Eigenschaft (kann Null sein) required
Ein boolescher Wert, der angibt, ob die Eigenschaft erforderlich ist
value
Aktueller/Standardeigenschaftswert (kann Null sein)
choices
Array der fr die Eigenschaft zulssigen Werte (kann Null sein)
public int getMinorVersion(): Gibt die Unterversionsnummer des Treibers zur.ck. Wenn die Versionsnummer des Treibers beispielsweise 4.3 ist, w.rde 3 zur.ckgeliefert. public boolean jdbcCompliant(): Gibt an, ob es sich bei dem Treiber um eine vollst$ndige JDBC-Implementierung handelt. Bei Altdaten-Konvertiersystemen oder bei Lightweight-LGsungen ist eine vollst$ndige Implementierung eventuell nicht mGglich oder erforderlich. Die Methode connect() der Schnittstelle Driver ist die wichtigste Methode und wird vom DriverManager zum Abrufen eines Connection-Objekts aufgerufen. Wie in Abbildung 15.5 gezeigt ist das Connection-Objekt der Beginn der JDBC-Anwendungsebene. Mit dem Connection-Objekt werden Statement-Objekte erzeugt, die Abfragen ausf.hren. Die Methode connect() f.hrt normalerweise die folgenden Schritte aus:
464
1
2
]ffnet eine TCP-Verbindung zu dem angegebenen Host und der angegebenen Portnummer.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JDBC-API
3
Versucht, auf die angegebene Datenbanktabelle zuzugreifen (sofern vorhanden).
4
Liefert eine Instanz eines Connection-Objekts zur.ck.
Connection ist eine Java-Schnittstelle, daher handelt es sich bei dem zur.ckgegebenen Objekt eigentlich um eine Referenz auf die Connection-Schnittstelle der Treiberimplementierung.
Hinweis
Die Klasse DriverManager: Die Klasse DriverManager dient als Hilfsklasse zur Verwaltung der JDBC-Treiber. Die Klasse stellt Methoden zur Herstellung einer Verbindung .ber einen Treiber, zur Registrierung und Deregistrierung von Treibern, zum Einrichten der Protokollierung sowie zum Festlegen von Zeit.berschreitungen beim Zugriff auf Datenbanken bereit. Alle unten aufgef.hrten Methoden der Klasse DriverManager sind als static deklariert. Sie kGnnen anhand des folgenden Klassennamens auf diese Methoden zugreifen: Signatur: public class java.sql.DriverManager public static synchronized Connection getConnection(String url, Properties info) throws SQLException: Diese Methode (und die anderen getConnection()-Methoden) versucht, eine Referenz auf ein Objekt zur.ckzuliefern, das die Schnittstelle Connection implementiert. Die Methode durchl$uft eine interne Kollektion von gespeicherten Driver-Klassen und .bergibt den URL-String und die Informationen (info) des Properties-Objekt nacheinander an jede Klasse. Die erste Driver-Klasse, die ein Connection-Objekt zur.ckgibt, wird verwendet. info ist eine Referenz auf ein Properties-Containerobjekt mit Tag-/Wertepaaren, normalerweise Benutzername/Passwort. Diese Methode l$sst f.r jeden Treiber in der Kollektion mehrere Versuche zu, eine autorisierte Verbindung herzustellen. public static synchronized Connection getConnection(String url) throws SQLException: Diese Methode ruft getConnection(url, info) mit dem leeren Objekt Properties(info) auf. public static synchronized Connection getConnection(String url, String user, String password) throws SQLException: Diese Methode erzeugt das Objekt Properties(info), speichert die Benutzer- und Passwort-Strings darin und ruft anschließend getConnection(url, info) auf. public static synchronized void registerDriver(java.sql.Driver driver) throws SQLException: Diese Methode speichert die Instanz der Driver-Schnittstellenimplementierung in einer Treiberkollektion. Dabei wird zur Feststellung, woher der Treiber stammt, auch der aktuelle Sicherheitskontext des Programms gespeichert.
465
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
public static void setLogStream(PrintStream out): Veraltet. Diese Methode legt ein PrintStream-Objekt zur Protokollierung/Ablaufverfolgung fest, das von der Klasse DriverManager verwendet wird. public static void setLoginTimeout(int seconds): Diese Methode setzt die zul$ssige Zeitspanne, die ein Treiber beim Anmelden bei einer Datenbank warten sollte. Treiber werden bei der Klasse DriverManager entweder bei der Initialisierung dieser Klasse oder bei der Erzeugung einer Instanz des Treibers registriert. Beim Laden der Klasse DriverManager wird ein statischer Codeabschnitt (in der Klasse) ausgef.hrt und die in der Java-Eigenschaft jdbc.drivers aufgef.hrten Klassennamen der Treiber werden geladen. Mit dieser Eigenschaft kann eine Liste mit durch Doppelpunkt getrennten Treiberklassennamen definiert werden, wie z.B.: jdbc.drivers=imaginary.sql.Driver:oracle.sql.Driver: weblogic.sql.Driver Jeder Treibername stellt einen Klassendateinamen (einschließlich der Package-Deklaration) dar, den die Klasse DriverManager .ber den aktuellen CLASSPATH zu laden versucht. Die Klasse DriverManager verwendet den folgenden Aufruf, um die angegebene Klasse zu suchen, zu laden und einzubinden: Class.forName(driver); Wenn die Eigenschaft jdbc.drivers leer (nicht angegeben) ist, muss der Anwendungsprogrammierer eine Instanz der Treiberklasse erzeugen. In beiden F$llen muss die Klassenimplementierung Driver sich selbst explizit bei der Klasse DriverManager durch den folgenden Aufruf registrieren: DriverManager.registerDriver (this); Im Folgenden finden Sie einen Codeabschnitt f.r einen imagin$ren Treiber (f.r die Mini-SQL-Datenbank). Jedes Mal, wenn eine Instanz des imagin$ren Treibers erzeugt wird, registriert sich der Treiber selbst: ... public class iMsqlDriver implements java.sql.Driver { static { try { new iMsqlDriver(); } catch( SQLException e ) { e.printStackTrace(); }
466
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JDBC-API
} /** * Erstellt einen neuen Treiber und registriert * ihn mit java.sql.DriverManager.registerDriver() * entsprechend der Spezifikation im JDBC Draft * Protocol. */ public iMsqlDriver() throws SQLException { java.sql.DriverManager.registerDriver(this); } ... DriverManager wird eingesetzt, um .ber die Methode getConnection eine Connection-Objektreferenz zu erhalten: Connection conn; conn = DriverManager.getConnection ( "jdbc:sybase://dbserver:8080/billing", dbuser, dbpasswd); Diese Methode durchl$uft die Liste der registrierten Treiber und .bergibt den URLString und die Parameter .ber die Methode connect() des jeweiligen Treibers an alle Treiber. Wenn der Treiber die Subprotokoll- und Subnamensinformationen unterst.tzt, wird eine Connection-Objektreferenz zur.ckgegeben. Die Klasse DriverManager muss keine JDBC-Anwendungen erzeugen, da es mGglich ist, ein Connection-Objekt direkt von Driver zu erhalten: Connection conn; Driver sybDriver = new SybaseDriver(); conn = sybDriver.connect( "jdbc:sybase://dbserver:8080/billing", props); Dieses Vorgehen zum Herstellen einer Verbindung ist nicht so ideal, da der Anwendungsentwickler hier davon abh$ngig ist, dass die Driver-Implementierungsklasse Sicherheits.berpr.fungen bereitstellt.
Die Anwendungsebene Die Anwendungsebene enth$lt drei Schnittstellen, die in der Treiberebene implementiert, aber vom Anwendungsentwickler eingesetzt werden. In Java stellen Schnittstellen ein Mittel dar, um mit einem allgemeinen Namen ein spezielles Objekt anzugeben. Der allgemeine Name definiert Methoden, die von den speziellen Objektklassen implementiert werden m.ssen. F.r den Anwendungsentwickler bedeutet dies, dass die spezielle Klassenimplementierung von Driver irrelevant ist – das Programmieren f.r die Standard-JDBC-APIs ist ausreichend. Nat.rlich wird dabei vorausgesetzt, dass der Treiber mit JDBC kompatibel ist und das bedeutet wiederum, dass die Datenbank zumindest ANSI SQL-2 Entry Level unterst.tzen muss. 467
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Die drei Hauptschnittstellen sind: Connection, Statement und ResultSet. Ein Connection-Objekt wird durch den Methodenaufruf DriverManager.getConnection() von der Treiberimplementierung abgerufen. Wenn ein Connection-Objekt zur.ckgeliefert wurde, kann der Anwendungsentwickler ein Statement-Objekt erzeugen, um die Datenbank abzufragen. Das Ergebnis eines Statement-Objekts ist ein ResultSetObjekt, das die Ergebnisse der speziellen Anweisung (sofern vorhanden) enth$lt. Grundlagen der Schnittstelle Connection: Die Schnittstelle Connection repr$sentiert eine Sitzung .ber die von der Schnittstelle Driver bereitgestellte Datenbankverbindung. Typische Datenbankverbindungen beinhalten die F$higkeit, durch Transaktionen vorgenommene Snderungen an den aktuellen Daten zu steuern. Eine Transaktion umfasst eine Reihe von Operationen, die ordnungsgem$ß abgeschlossen werden. Bei einer Commit-Aktion werden Daten in der Datenbank gespeichert (oder ge$ndert). Bei einer Rollback-Aktion wird die vorherige Transaktion r.ckg$ngig gemacht, bevor Daten ge$ndert oder gespeichert werden. Bei der Erzeugung befinden sich die JDBC-Connection-Objekte in dem Modus Auto-Commit – ein Rollback ist nicht mGglich. Nachdem ein Connection-Objekt vom Treiber abgerufen wurde, sollte der Entwickler den Modus Auto-Commit mit der Methode setAutoCommit(boolean b) auf false setzen. Wenn der Modus Auto-Commit deaktiviert ist, unterst.tzt das Connection-Objekt die beiden Methodenaufrufe Connection.commit() und Connection.rollback(). Das Unterst.tzungsniveau von Transaktionsisolationen ist von der zugrunde liegenden Transaktionsunterst.tzung in der Datenbank abh$ngig. Im Folgenden finden Sie einen Teil der Connection-Schnittstellendefinition: Signatur: public interface Connection Statement createStatement() throws SQLException: Die Connection-Objektimplementierung liefert eine Instanz einer Implementierung eines StatementObjekts zur.ck. Das Statement-Objekt wird dann f.r Abfragen verwendet. PreparedStatement prepareStatement(String sql) throws SQLException: Die Connection-Objektimplementierung liefert eine Instanz eines PreparedStatement-Objekts zur.ck, das mit dem .bergebenen String sql konfiguriert ist. Wenn die Datenbank (der Treiber) vorkompilierte Anweisungen verarbeitet, kann der Treiber die Anweisung anschließend an die Datenbank senden. Andernfalls kann der Treiber warten, bis das Objekt PreparedStatement von einer Ausf.hrungsmethode verarbeitet wird. Wenn der Treiber und die Datenbank keine vorkompilierten Anweisungen implementieren, kann eine Exception ausgelGst werden. CallableStatement prepareCall(String sql) throws SQLException: Die Connection-Objektimplementierung liefert eine Instanz eines CallableStatement-Objekts zur.ck. CallableStatement-Objekte sind f.r die Behandlung von Stored Procedures optimiert. Nachdem die Methode prepareCall() beendet ist, kann die Treiberimplementierung den String sql sofort senden oder abwarten, bis eine execute()-Methode auftritt. 468
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JDBC-API
void setAutoCommit(boolean autoCommit) throws SQLException: Setzt in der Treiberimplementierung ein Flag, das Commit-/Rollback-Aktionen (false) aktiviert oder alle Transaktionen sofort .bergibt (true). void commit() throws SQLException: F.hrt alle Snderungen aus, die seit Beginn der aktuellen Transaktion vorgenommen wurden (entweder seit dem ]ffnen der Verbindung oder seit der letzten commit()- oder rollback()-Aktion). void rollback() throws SQLException: Verwirft alle Snderungen, die seit Beginn der aktuellen Transaktion vorgenommen wurden. Die Connection-Schnittstelle wird eingesetzt, um eine Anweisung zu erzeugen: Connection msqlConn; Statement stmt; msqlConn = DriverManager.getConnection (url); stmt = msqlConn.createStatement(); Mit dieser Anweisung kGnnen SQL-Abfragen gesendet werden, die in einer ResultSet-Objektreferenz eine einzelne Ergebnismenge oder die Anzahl der von der Anweisung betroffenen Datens$tze liefern. Anweisungen, die mit geringen Abwandlungen mehrmals aufgerufen werden m.ssen, werden mit einem PreparedStatement-Objekt wahrscheinlich effizienter ausgef.hrt. Die Schnittstelle Connection wird auch zum Erzeugen eines CallableStatement-Objekts verwendet, das in erster Linie Stored Procedures ausf.hrt. Der Hauptunterschied zwischen den Objekten Statement, PreparedStatement und CallableStatement ist, dass Statement keine Parameter in der auszuf.hrenden SQLAnweisung zul$sst, w$hrend PreparedStatementIn-Parameter und CallableStatement sowohl Inout- als auch Out-Parameter erlaubt. In-Parameter sind Parameter, die an eine Operation .bergeben werden. Out-Parameter sind Parameter, die durch eine Referenz .bergeben werden und ein Ergebnis vom Typ der Referenz liefern. Inout-Parameter sind Out-Parameter, die einen Anfangswert enthalten, der sich als Ergebnis der Operation ver$ndern kann. JDBC unterst.tzt alle drei Parametertypen.
Tipp
Meistens kennt der Entwickler die Datenbankspezifikationen vorab und erstellt die Anwendung entsprechend. JDBC stellt aber eine Schnittstelle bereit, die zur dynamischen Ermittlung von datenbankspezifischen Informationen eingesetzt werden kann. Die Methode getMetaData der Schnittstelle Connection gibt ein DatabaseMetaData-Objekt zur.ck. Die Instanz der Klasse, die die Schnittstelle implementiert, liefert Informationen .ber die Datenbank als Ganzes: Zugriffsinformationen f.r Tabellen und Prozeduren, Spaltennamen, Datentypen usw. Die Einzelheiten der Implementierung von DatabaseMetaData h$ngen davon ab, wie der Datenbankanbieter die
469
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Grundlagen der Schnittstelle Statement:
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JDBC-API
int rowCount;rowCount = stmt.executeUpdate ( "DELETE FROM customer WHERE CustomerID = 'McG10233'"); F.r SQL-Anweisungen, die eine einzelne Ergebnismenge liefern, kann die Methode executeQuery() verwendet werden. Diese Methode gibt ein einzelnes ResultSet-Objekt zur.ck. Das Objekt repr$sentiert die Datensatzinformationen, die als Ergebnis der Abfrage geliefert wurden. ResultSet results; results = stmt.executeQuery ("SELECT * FROM stock"); SQL-Anweisungen, die Stored Procedures ausf.hren (oder eine Stored Procedure auslGsen) kGnnen mehrere Ergebnismengen liefern. Die Methode execute() ist eine Universalmethode, die eine einzelne Ergebnismenge, einen Ergebnisz$hler oder eine beliebige Kombination von beiden zur.ckgeben kann. Die Methode setzt ein boolesches Flag, anhand dessen festgestellt werden kann, ob mehrere Ergebnisse vorhanden sind. Da eine Ergebnismenge entweder Daten oder einen Datensatzz$hler enth$lt, werden die Methoden getResultSet(), getMoreResults() und getUpdateCount() verwendet. Zum Beispiel: // In diesem Beispiel wird davon ausgegangen, dass // SQLString mehrere Ergebnismengen liefert // true, wenn ein ResultSet zur:ckgegeben wird boolean result = stmt.execute (SQLString); int count = stmt.getUpdateCount(); // Schleife durchlaufen, bis alle Ergebnisse oder // Aktualisierungsz[hler verarbeitet wurden while (result || (count != -1)) { // Ist das Ergebnis ein ResultSet? if (result) { results = stmt.getResultSet(); // Ergebnismenge verarbeiten } else if (count != -1) { // Z[hler verarbeiten } result = stmt.getMoreResults(); count = stmt.getUpdateCount(); } Die Schnittstelle PreparedStatement erweitert die Schnittstelle Statement. Die Schnittstelle PreparedStatement stellt f.r SQL-Anweisungen, die mit geringen Abweichungen mehrmals wiederholt werden m.ssen, einen effizienten Mechanismus f.r die
471
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Anhand von PreparedStatement-Parametern werden Daten an eine SQL-Anweisung .bergeben, daher werden sie als In-Parameter bezeichnet. Die Daten werden mit den Methoden setType eingetragen:
Hinweis
Die setType-Methoden tragen in einer PreparedStatement-Anweisung die Werte der Parameter (durch Fragezeichen gekennzeichnet) ein. Diese Parameter werden von 1 bis n indiziert. // In diesem Beispiel stellt priceList ein Array // mit Preisen dar, die f:r einen Ausverkauf um // 10 % reduziert werden sollen und reducedItems // ist ein Array mit Artikel-IDs PreparedStatement ps = msqlConn.prepareStatment ( "UPDATE Catalog SET Price = ? WHERE ItemID = ?"); // Aktualisierungen in einer Schleife ausf:hren for (int i = 0; i < reducedItems.length(); i++) { // Beachten Sie, dass die setType-Methoden den // Wert der Parameter setzen, die in der // SQL-Anweisung mit einem Fragezeichen (?) // gekennzeichnet sind. Sie werden von 1 bis n // indiziert. ps.setFloat (1, (priceList[i]*((float)( 100-reduction)/100))); ps.setString (2, reducedItems[i]); if (ps.executeUpdate() == 0) { throw new SQLException ( "No Item ID: " + reducedItems[i]); } } Die aktuellen Werte der Parameter bleiben solange bestehen, bis entweder erneut die Methode setType oder die Methode clearParameters() f.r das PreparedStatement-Objekt aufgerufen wird. Zus$tzlich zu den von Statement geerbten executeMethoden deklariert PrepareStatement die in der folgenden Tabelle aufgef.hrten setType-Methoden. Jede Methode hat zwei Argumente: einen Parameterindex und den Klassentyp.
setType-Methoden
472
Methodensignatur
Java-Typ
SQL-Typ aus der Datenbank
void setByte (int index, byte b)
byte
TINYINT
void setShort (int index, short s)
short
SMALLINT
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JDBC-API
setType-Methoden (Forts.)
Methodensignatur
Java-Typ
SQL-Typ aus der Datenbank
void setInt (int index, int i)
int
INTEGER
void setLong (int index, long l)
long
BIGINT
void setFloat (int index, float f)
float
FLOAT
void setDouble (int index, double d)
double
DOUBLE
void setBigDecimal (int index, BigDecimal bd)
java.math.BigDecimal
NUMERIC void setString (int index, String s)
java.lang.String
VARCHAR
void setCharacterStream (int index, Reader r, int length)
java.io.Reader
LONGVARCHAR
void setBytes (int index, byte b[])
byte array
VARBINARY
void set BinaryStream (int index, InputStream is, int length)
java.io.InputStream
LONGVARBINARY
void setString (int parameterIndex, String x)
String
CHAR, VARCHAR oder LONGVARCHAR
void setDate (int index, Date d)
java.sql.Date
DATE
void setTime (int index, Time t)
java.sql.Time
TIME
void setTimestamp (int index, Timestamp ts)
java.sql.Timestamp
TIMESTAMP
void setNull (int index, int sqlType)
–
java.sql.types f.hrt die SQL-Typen nach Nummern auf und NULL ist die Ganzzahl 0 473
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
setType-Methoden (Forts.)
Methodensignatur
Java-Typ
SQL-Typ aus der Datenbank
void setBoolean (int index, boolean b)
boolean
BIT Tabelle 15.1
Mit der Schnittstelle CallableStatement werden SQL-Stored-Procedures ausgef.hrt. CallableStatement ist von der Schnittstelle PreparedStatement abgeleitet, daher stehen alle execute()- und setType-Methoden zur Verf.gung. Da die Syntax f.r Stored Procedures von Datenbank zu Datenbank unterschiedlich ist, definiert JDBC einen Standard f.r alle RDBMSs zum Aufrufen von Stored Procedures. Signatur: public interface CallableStatement extends PreparedStatement JDBC verwendet dabei eine Syntax, bei der Parameter sowohl als In-Parameter als auch als Out-Parameter .bergeben werden kGnnen. Die Syntax ermGglicht auch die R.ckgabe eines Ergebnisses. Wenn diese Syntax eingesetzt wird, muss der Parameter als Out-Parameter registriert werden. Im Folgenden finden Sie ein Beispiel f.r die CallableStatement-Syntax, bei der ein Out-Parameter zur.ckgeliefert wird: CallableStatement cs = conn.prepareCall( "{call getQuote (?, ?)}"); cs.setString (1, stockName); // java.sql.Types definiert SQL-Datentypen, die als // Out-Parameter zur:ckgegeben werden cs.registerOutParameter (2, Types.FLOAT); stmt.executeUpdate(); float quote = stmt.getFloat (2); CallableStatement definiert getType-Methoden, die die von der Datenbank gelieferten SQL-Typen in Java-Typen konvertieren (siehe Tabelle 15.2). Diese Methoden entsprechen den von PreparedStatement deklarierten Methoden.
Hinweis
474
Die getType-Methoden greifen auf jede Spalte eines Abfrageergebnisses zu. Die einzelnen Spalten kGnnen dabei entweder .ber die jeweilige Position in der Zeile (von 1 bis n Spalten) oder .ber den Namen, wie z.B. custID, angesprochen werden.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JDBC-API
getType-Methoden
Methodensignatur
Java-Typ
SQL-Typ aus der Datenbank
boolean getBoolean (int index)
boolean
BIT
byte getByte (int index)
byte
TINYINT
short getShort (int index)
short
SMALLINT
int getInt (int index)
int
INTEGER
long getLong (int index)
long
BIGINT
float getFloat (int index)
float
FLOAT
double getDouble (int index)
double
DOUBLE
BigDecimal getBigDecimal (int index, int scale)
java.math.BigDecimal
NUMERIC
String getString (int index)
String
CHAR, VARCHAR oder LONGVARCHAR
byte[] getBytes (int index)
byte array
BINARY oder VARBINARY
Date getDate (int index)
java.sql.Date
DATE
Time getTime (int index)
java.sql.Time
TIME
Timestamp getTimestamp (int index)
java.sql.Timestamp
TIMESTAMP Tabelle 15.2
Beachten Sie bitte, dass der JDBC-Treiber f.r die Konvertierung der von der Datenbank als SQL-Datentypen .bergebenen Daten in Java-Werte verantwortlich ist. Hinweis Grundlagen der Schnittstelle ResultSet: Die Schnittstelle ResultSet definiert Methoden f.r den Zugriff auf Datentabellen, die als Ergebnis einer Statement-Ausf.hrung generiert werden. Auf ResultSet-Spaltenwerte kann in jeder beliebigen Reihenfolge zugegriffen werden. Die Spalten sind indiziert und daher kGnnen sie anhand der Nummer (1 bis n) oder des Namens ausgew$hlt werden. ResultSet speichert die Position der aktuellen Zeile, beginnend mit der ersten Zeile der zur.ckgegebenen Daten. Die Methode next() wechselt in die n$chste Datenzeile. Im Folgenden finden Sie eine Beschreibung der wichtigsten Methoden der Schnittstelle ResultSet: 475
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Signatur: public interface ResultSet boolean next() throws SQLException: Wechselt in die n$chste Zeile; die Anfangszeilenposition von ResultSet ist die erste Zeile der Ergebnismenge. ResultSetMetaData getMetaData() throws SQLException: Gibt ein Objekt zur.ck, das eine Beschreibung der aktuellen Ergebnismenge enth$lt: die Spaltenanzahl, die Typen der Spalten und Eigenschaften der Ergebnisse. void close() throws SQLException: Schließt die Ergebnismenge. Eine Ergebnismenge wird normalerweise geschlossen, wenn eine andere Anweisung (Statement) ausgef.hrt wird, es kann aber erforderlich sein, die Ressourcen fr.her freizugeben. Genau wie bei CallableStatement kGnnen die Ergebnisdaten mit getType-Methoden gelesen werden. Zum Beispiel: // Eine Abfrage an das Objekt Statement :bergeben ResultSet rs = stmt.executeQuery( "SELECT * FROM stock WHERE quantity = 0"); // Ergebnisse als Java-Typen ermitteln // Beachten Sie, dass auf Spalten :ber den Index // (beginnend mit 1) oder :ber den Namen, wie z.B. // "ItemID" zugegriffen werden kann System.out.println ("Stock replenishment list"); while (rs.next()) { System.out.println ( "Item ID: " + rs.getString("ItemID")); System.out.println ( "Next ship date: " + rs.getDate(2)); System.out.println (""); } Mit Java 2 wurden einige Methoden f.r die JDBC-2.0-API eingef.hrt. JDBC 2.0 stellt eine umfassendere Funktionalit$t bereit, die ein nicht-sequenzielles Lesen von Datenzeilen und ein Aktualisieren der Zeilen beim Lesen zul$sst. Im Folgenden finden Sie eine Beschreibung der wichtigsten JDBC-2.0-Methoden der Schnittstelle ResultSet: int getType() throws SQLException: Gibt den Typ der Ergebnismenge zur.ck. Der Typ bestimmt, wie die Ergebnisse gelesen werden kGnnen. G.ltige R.ckgabewerte sind TYPE_FORWARD_ONLY, TYPE_STATIC, TYPE_KEYSET oder TYPE_DYNAMIC. Bei TYPE_FORWARD_ONLY lGsen die meisten anderen Methoden eine SQLException aus. boolean first() throws SQLException: Setzt den Datensatzzeiger in die erste Zeile der Ergebnismenge. Wenn die Zeile g.ltig ist, wird true zur.ckgegeben, ansonsten false. 476
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JDBC-API
boolean last() throws SQLException: Setzt den Datensatzzeiger in die letzte Zeile der Ergebnismenge. Wenn die Zeile g.ltig ist, wird true zur.ckgegeben, ansonsten false. boolean previous() throws SQLException: Setzt den Datensatzzeiger in die vorherige Zeile der Ergebnismenge. Wenn die Zeile g.ltig ist, wird true zur.ckgegeben, ansonsten false. boolean absolute(int row) throws SQLException: Setzt den Datensatzzeiger in die angegebene Zeile der Ergebnismenge. Wenn f.r row ein negativer Wert angegeben wird, wird der Datensatzzeiger in die angegebene Zeile relativ zum Ende der Ergebnismenge gesetzt. boolean relative(int row) throws SQLException: Setzt den Datensatzzeiger in die angegebene Zeile relativ zur aktuellen Position. boolean isFirst() throws SQLException: (JDBC 2.0) Gibt an, ob sich der Datensatzzeiger in der ersten Zeile der Ergebnismenge befindet. boolean isLast() throws SQLException: (JDBC 2.0) Gibt an, ob sich der Datensatzzeiger in der letzten Zeile der Ergebnismenge befindet. Die JDBC-2.0-API ermGglicht auch das Aktualisieren von Zeilen beim Lesen der Ergebnismenge. Mit der Methode deleteRow() kann die aktuelle Zeile gelGscht werden. Mit Methoden wie updateInt(int col, int value) oder updateFloat(String columnName, float value) kGnnen die Spalten der aktuellen Zeile aktualisiert werden. F.r jeden Datentyp gibt es zwei Methoden: eine f.r den Zugriff auf Spalten .ber die Spaltennummer und eine f.r den Zugriff .ber den Spaltennamen. Wenn Sie eine Zeile aktualisiert haben, m.ssen Sie das System mit updateRow() anweisen, auch die eigentliche Datenbank zu aktualisieren.
Hinweis
ResultSetMetaData: Außer der MGglichkeit, Daten eines ResultSet-Objekts zu lesen, stellt JDBC eine Schnittstelle zur Verf.gung, mit der festgestellt werden kann, welcher Datentyp zur.ckgegeben wurde. Die Schnittstelle ResultSetMetaData entspricht in konzeptioneller Hinsicht der Schnittstelle DatabaseMetaData, ist aber spezifisch f.r die aktuelle Ergebnismenge gedacht. Wie auch bei der Schnittstelle DatabaseMetaData ist es eher unwahrscheinlich, dass viele Entwickler diese Schnittstelle einsetzen, da in den meisten Anwendungen die Datenbankstruktur und die Spaltennamen und -werte ber.cksichtigt sind. ResultSetMetaData eignet sich aber f.r das dynamische Ermitteln von Metadaten einer Ergebnismenge, die von einer Stored Procedure oder von einer vom Benutzer eingegebenen SQL-Anweisung geliefert wurde. Das folgende Codebeispiel zeigt, wie Ergebnisse mit Hilfe von ResultSetMetaData angezeigt werden kGnnen, wenn der Inhalt nicht bekannt ist: ResultSet results = stmt.executeQuery (sqlString); ResultSetMetaData meta = results.getMetaData(); int columns = 0; boolean first = true;
477
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
while (results.next()) { if (first) { columns = meta.getColumnCount(); for (int i=1; i<=columns; i++) { System.out.print (meta.getColumnName(i) + "\t"); } System.out.println(); first=false; } for (int i=1; i<=columns; i++) { System.out.print (results.getString (i) + "\t"); } System.out.println(); } Große DatenblGcke senden und empfangen: Die SQL-Datentypen LONGVARBINARY und LONGVARCHAR kGnnen eine beliebige GrGße aufweisen. Diese Datentypen werden von den Methoden getBytes() und getString() bis zu der vom Treiber festgelegten Obergrenze gelesen. Die Obergrenze kann mit der Methode Statement.getMaxFieldSize() ermittelt werden. Bei grGßeren DatenblGcken kGnnen in JDBC EingabestrGme (Input Streams) verwendet werden, die die Daten portionsweise liefern. Streams m.ssen sofort nach der Abfrageausf.hrung gelesen werden. Sie werden n$mlich automatisch beim n$chsten Empfang einer Ergebnismenge geschlossen. Tipp Große DatenblGcke kGnnen auch .ber java.io.OutputStream-Parameter gesendet werden. Wenn eine Anweisung abgearbeitet wird, f.hrt der JDBC-Treiber wiederholte Aufrufe zum Lesen und
Hinweis
In der JDBC-2.0-API kGnnen die Typen Array, Blob, Clob und Struct zum Lesen und Speichern von großen Objekten verwendet werden. Diese Typen gehGren zum SQL3-Standard, der gerade entsteht. Eine vollst$ndige Beschreibung der Funktionalit$t von JDBC 2 finden Sie unter http://java.sun.com/products/jdbc/jdbcsw2.html.
Einschr5nkungen bei JDBC (Anwendungen im Vergleich zu Applets) In der Java-Welt gibt es zwei Programmtypen: Anwendungen und Applets. Jeder Programmtyp bietet Vorteile – der Einsatz des jeweiligen Programmtyps wird im Allgemeinen durch die Vorgaben des Entwicklers bestimmt, wie der Benutzer auf das Programm zugreifen soll.
478
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JDBC-API
Anwendungen Java-Anwendungen sind eigenst$ndige, ausf.hrbare Programme. Der Benutzer muss hier lokal auf die ausf.hrbaren Programmdateien (.class-Dateien) und den Java-Interpreter zugreifen kGnnen. Bei einem Intranet-basierten Datenbank-FrontEnd bietet diese Strategie den Vorteil eines schnelleren Starts (die .class-Dateien sind lokal) und der Ausnutzung der lokalen Festplatte. Außerdem gelten Java-Anwendungen als sehr sicher. Ihnen wird daher eine grGßere Flexibilit$t bei Socket-Verbindungen zugestanden und ermGglicht, dass das Clientprogramm auf mehrere Datenbanksysteme auf Remote-Servern zugreifen kann. In dem Maße, in dem die Verbreitung von Tools zur GUI-Entwicklung zunimmt und Geschwindigkeitssteigerungen durch Just-In-Time (JIT)-Compiler/Interpreter ermGglicht werden, wird der Einsatz von Java-Anwendungen anwachsen. Anwendungen kGnnen auch die Probleme bei Browser-Sicherheitsmodellen und deren abweichenden Java-Implementierungen reduzieren oder eliminieren.
Applets Applets sind Mini-Java-Programme, f.r deren Ausf.hrung ein Java-f$higer Browser erforderlich ist. Der Browser stellt eine Umgebung bereit, in der das Applet ausgef.hrt werden kann. Dabei wird das Zeichnen und das Anzeigen von Ressourcen direkt in der Browserseite vorgenommen. Wenn ein Benutzer eine Browserseite mit einem Applet Gffnet, wird das Applet automatisch ausgef.hrt. Dazu muss der erforderliche Java-Applet-Code, die JDBC-Treiber und die Software der Anwendungsebene heruntergeladen und automatisch eine Sicherheitspr.fung des Codes vorgenommen werden. Nur wenn alles in Ordnung ist, wird das Applet ausgef.hrt. Applets bieten gegen.ber Anwendungen im Wesentlichen die folgenden Vorteile: Versionskontrolle: Ein Applet kann $ußerst schnell modifiziert werden. Es m.ssen nur die .class-Dateien in der HTML-Seitenreferenz ersetzt werden. Einfacheres Ausf.hrungsmodell: Die Bedienung auch der anspruchvollsten Browser und die Ausf.hrung eines Front-End-Clients ist sehr einfach zu erlernen. Der Benutzer muss nur die Seite ansteuern, in der sich das Applet befindet. Online-Hilfe: In Browser-HTML-Seiten lassen sich sehr bequem Hilfeverkn.pfungen einbetten, die separat vom ausgef.hrten Programm entwickelt werden kGnnen. Eine f.r Applets typische VerwendungsmGglichkeit ist z.B. die betriebliche Weiterbildung: Die verwendeten Daten kGnnen eingegrenzt und der Zugriff darauf auf ein zweischichtiges Modell beschr$nkt werden. Auch dreischichtige Modelle sind mGglich, aber daf.r sind komplexere Ebenenschemata erforderlich. Ein weiteres
479
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Einsatzgebiet w$re eine einfache Datenpr$sentation f.r die Internetgemeinde, wobei auch hier die Datenmenge gering sein und die Datensicherheit nicht im Vordergrund stehen sollte. Applets sind jedoch in den folgenden Beziehungen stark von der Browserumgebung abh$ngig: Applets kGnnen nicht auf lokale Dateien zugreifen. Dadurch wird das lokale Caching, die Tabellenbearbeitung und eine Speicherung im Arbeitsspeicher, w$hrend das Applet aktiv ist, eingeschr$nkt. Applets kGnnen nicht zu beliebigen Hosts verbinden. Socket-Verbindungen sind nur zwischen dem Applet und dem Host, von dem das Applet stammt, erlaubt. Applets kGnnen Treiber, die native Methoden (C-Sprachaufrufe) enthalten, weder laden noch ausf.hren. Außerdem gibt es beim Laden von Applet-Code in einer Internet-Netzwerkverbindung (Wide Area Network) betr$chtliche Leistungseinbußen. Einige dieser Beschr$nkungen kGnnen durch sichere (trusted) Applets und Browser, die diese akzeptieren, aufgehoben oder reduziert werden. Der Code von sicheren Applets kann mit kryptografischen Schl.sseln signiert oder an sicheren Orten gespeichert werden. Wenn der Quellcode eines Applets f.r die Browserumgebung sicher ist, kGnnen Applets wie Anwendungen behandelt werden. Trotzdem kann es weiterhin Einschr$nkungen bez.glich des Speicherorts von Datenbanken im Internet geben, die nicht von dem Java Security Manager abh$ngen. Sichere Applets stehen im Mittelpunkt der zuk.nftigen
Sicherheitsberlegungen Die JDBC-API ber.cksichtigt das Java-Standardsicherheitsmodell. Das bedeutet, dass Anwendungscode als sicher und Applet-Code als unsicher angesehen wird. Im Allgemeinen wird das Schreiben von sicheren JDBC-Treibern den jeweiligen Anbietern .berlassen. 480
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ein Beispiel fr eine JDBC-Datenbank
Die virtuelle Maschine von Java verwendet eigene, gut dokumentierte Sicherheits.berpr.fungen f.r unsichere Applets, einschließlich der oben erw$hnten Einschr$nkungen. Wenn jedoch ein Hersteller eines JDBC-Treibers das Modell durch das Hinzuf.gen weiterer Funktionen – z.B. damit mehrere Applets .ber dieselbe TCP-Socket-Verbindung mit einer Datenbank kommunizieren kGnnen – zu seinem Treiber erweitern mGchte, liegt es in der Verantwortung des Herstellers zu .berpr.fen, ob alle Applets berechtigt sind, die Verbindung zu nutzen. Zus$tzlich zur Beibehaltung der Integrit$t des Java-Sicherheitsmodells m.ssen sowohl der JDBC-Treiberhersteller als auch der JDBC-Anwendungsentwickler ber.cksichtigen, dass die JDBC-API Maßnahmen zum Ausf.hren von Datenbankaufrufen und kein Netzwerksicherheitsmodell definiert. Die .ber die Verbindung an die Datenbank gesendeten Daten und die resultierenden Tabelleninformationen (z.B. Kreditkarteninformationen eines Kunden) liegen offen und kGnnen von jedem Terminal gelesen werden, das im Netzwerk spionieren kann.
Ein Beispiel f,r eine JDBC-Datenbank Das folgende Beispiel verwendet die in diesem Kapitel dargestellten Konzepte. Das Beispiel ist hypothetisch und soll nur die Verwendung der Schnittstellen Statement, PreparedStatement und CallableStatement demonstrieren. Die einfache Datenbank enth$lt die Tabelle Customer mit der folgenden Struktur:
Datentabelle Customer
Spaltenname
Typ
CustomerID
VARCHAR
LastName
VARCHAR
FirstName
VARCHAR
PhoneNumber
VARCHAR
StreetAddress
VARCHAR
Zipcode
VARCHAR Tabelle 15.3
Die Tabelle Customer ist Teil einer grGßeren Datenbank, in der Informationen eines Bestellsystems gespeichert werden. Im Folgenden finden Sie die Definition eines einfachen Customer-Objekts mit den zwei Hauptmethoden insertNewCustomer() und getCustomer(): Signatur: public class Customer 481
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
public Customer(Connection conn): Der Konstruktor der Klasse. Der Konstruktor Customer erh$lt ein Connection-Objekt, anhand dessen Statement-Referenzen erzeugt werden. Außerdem erstellt der Konstruktor ein PreparedStatement-Objekt und drei CallableStatement-Objekte. public String insertNewCustomer(String lname, String fname, String pnum, String addr, String zip) throws insertFailedException, SQLException: Erstellt einen neuen Kundendatensatz mit einer neuen ID. Die ID wird von einer Stored Procedure erzeugt, die die aktuelle Liste der Kundennummern liest und eine neue Referenz erzeugt. Die Methode gibt die neu erzeugte ID zur.ck oder lGst eine Exception aus, falls das Einf.gen misslingt. public CustomerInfo getCustomer(String custID) throws selectException, SQLException: Gibt ein Objekt zur.ck, das die Daten der Tabelle Customer enth$lt. Wenn die .bergebene Kundennummer nicht vorhanden oder ordnungsgem$ß formatiert ist oder wenn die SQL-Anweisung fehlschl$gt, wird eine Exception ausgelGst. public static synchronized boolean validateZip (String zip) throws SQLException: Ist eine Hilfsmethode zur
// Customer-Datensatzklasse // Diese Klasse wird zum Speichern von und f:r den // Zugriff auf Kundendaten in der Datenbank // verwendet import java.sql.*; public class Customer { private Connection conn; private PreparedStatement insertNewCustomer; private CallableStatement getNewID; public static CallableStatement checkZip; public static CallableStatement checkID; // Customer-Konstruktor: eine lokale Kopie des // Connection-Objekts speichern und Anweisungen // zur sp[teren Verwendung erzeugen public Customer (Connection c) { conn = c; try {
482
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ein Beispiel fr eine JDBC-Datenbank
insertNewCustomer = conn.prepareStatement( "INSERT INTO customers VALUES" + "(?, ?, ?, ?, ?, ?)"); getNewID = conn.prepareCall ( "{call getNewID (?)}"); checkID = conn.prepareCall ( "{call checkID (?,?)}"); checkZip = conn.prepareCall ( "{call checkZip (?, ?)}"); } catch (SQLException e) { System.err.println ( "Cannot create statements"); } } // Methode zum Erzeugen eines neuen // Kundendatensatzes. // Die customerID wird durch den Aufruf einer // Stored Procedure erzeugt public String insertNewCustomer (String lname, String fname, String pnum, String addr, String zip) throws InsertFailedException, SQLException { String newID; // Neue Kundennummer von der Stored Procedure // ermitteln if ((newID = getNewID ()) == null) { throw new InsertFailedException ( "could not get new ID"); } // Neue Kundennummer einf:gen insertNewCustomer.setString (1, newID); insertNewCustomer.setString (2, lname); insertNewCustomer.setString (3, fname); insertNewCustomer.setString (4, pnum); insertNewCustomer.setString (5, addr); insertNewCustomer.setString (6, zip); // Anweisung ausf:hren if (insertNewCustomer.executeUpdate() != 1) { throw new InsertFailedException ( "could not execute insert"); } return (newID); } // Einzelnen Kundendatensatz mit dieser ID abrufen // Hinweis: diese Methode wandelt die gelieferten // Daten in ein CustomerInfo-Containerobjekt um public CustomerInfo getCustomer (String custID) throws SelectException, SQLException {
483
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
// Zuerst ID :berpr:fen if (!validateID (custID)) { throw new SelectException ( "no customer with ID: " + custID); } // Select-Anweisung erstellen Statement stmt = conn.createStatement(); // Ergebnisse ermitteln ResultSet rs = stmt.executeQuery ( "SELECT *FROM Customer WHERE CustID = " + custID); // CustomerInfo-Containerobjekt erzeugen CustomerInfo info = new CustomerInfo (); // Das CustomerInfo-Containerobjekt mit Daten // f:llen // Spalten sind beginnend mit 1 indiziert info.CustomerID = rs.getString (1); info.LastName = rs.getString (2); info.FirstName = rs.getString (3); info.PhoneNumber = rs.getString (4); info.StreetAddress = rs.getString (5); info.Zipcode = rs.getString (6); return (info); } // Methode zur _berpr:fung der Postleitzahl // Diese Methode ist als public deklariert, damit // sie von einer Benutzerschnittstelle aus // aufgerufen werden kann public static synchronized boolean validateZip (String zip) throws SQLException { // Stored Procedure zum _berpr:fen der // Postleitzahl aufrufen checkZip.setString (1, zip); checkZip.registerOutParameter (2, Types.BIT); checkZip.executeUpdate(); return (checkZip.getBoolean(2)); } // Methode zum _berpr:fen der Kundennummer // Diese Methode ist als public deklariert, damit // sie von einer Benutzerschnittstelle aus // aufgerufen werden kann public static synchronized boolean validateID ( String id) throws SQLException { 484
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ein Beispiel fr eine JDBC-Datenbank
// Stored Procedure zum _berpr:fen der // Kundennummer aufrufen checkID.setString (1, id); checkID.registerOutParameter (2, Types.BIT); checkID.executeUpdate(); return (checkID.getBoolean(2)); } // Methode zum Ermitteln einer neuen // Kundennummer aus der Datenbank private String getNewID () throws SQLException { // Stored Procedure zum Ermitteln der // Kundennummer aus der Datenbank aufrufen getNewID.registerOutParameter (1, Types.VARCHAR); getNewID.executeUpdate(); return (getNewID.getString(1)); } } // Exceptions // InsertFailedException ist eine allgemeine // Exception f:r SQL-Einf:geprobleme class InsertFailedException extends SQLException { public InsertFailedException () { } public InsertFailedException (String reason) { super (reason); } } // SelectException ist eine allgemeine // Exception f:r SQL-Select-Probleme class SelectException extends SQLException { public SelectException (String reason) { super (reason); } public SelectException () { } } Die Klasse CustomerInfo ist ein einfaches Container-Objekt. Container-Klassen schematisieren die
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Listing 15.2 CustomerInfo.java
// Ein Container-Objekt f:r die Tabelle Customer public class CustomerInfo { String CustomerID; String LastName; String FirstName; String PhoneNumber; String StreetAddress; String Zipcode; } Zum Testen der Klasse Customer finden Sie im Folgenden eine einfache Java-Anwendung, die das Laden eines Sybase-Treibers, das Herstellen einer Verbindung und die
Listing 15.3 Example.java
// Eine einfache Java-Anwendung, die die Verwendung // von DriverManager, Driver, Connection, Statement // und ResultSet zeigt import java.sql.*; public class Example { Connection sybaseConn; public static void main (String arg[]) { // URL, Benutzername und Passwort anfordern if (arg.length < 3) { System.err.println ("Example use:"); System.err.println ( "java Example <username> <password>"); System.exit (1); } // Eine Instanz der Klasse erzeugen Example ex = new Example (); // Verbindung initialisieren ex.initdb (arg[0], arg[1], arg[2]); // Verbindung testen - einen Kundendatensatz // schreiben und wieder einlesen ex.testdb (); } // Methode zum Initialisieren der Datenbank// verbindung // Die Connection-Objektreferenz wird global // gehalten public void initdb (String url, String user, String passwd) { // Datenbank Iffnen und Verbindung herstellen
486
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ein Beispiel fr eine JDBC-Datenbank
try { // Beachten Sie bitte, dass in diesem Beispiel // vorausgesetzt wird, dass die Java// Eigenschaft "jdbc.drivers" den passenden // Treiber (oder die Treiber) f:r die im // Aufruf von getConnection angegebene URL // l[dt. Es kInnte auch explizit eine Instanz // des Treibers erzeugt werden, z.B. // new sybase.sql.driver (); // Eine Verbindung herstellen sybaseConn = DriverManager.getConnection (url, user, passwd); } catch (SQLException e) { System.err.println ( "Database connection failed:"); System.err.println (e.getMessage()); System.exit (2); } } // Eine einfache Methode zum Testen der Methoden // der Klasse Customer public void testdb () { String custID = null; // Die Instanz der Customer-Klasse erzeugen Customer cust = new Customer (sybaseConn); try { // Neuen Kundendatensatz einf:gen custID = cust.insertNewCustomer ( "Jones", "Bill", "555-1234", "5 Main Street", "01234"); } catch (SQLException e) { System.err.println ("Insert failed:"); System.err.println (e.getMessage()); System.exit (3); } try { // Datensatz aus der Datenbank einlesen CustomerInfo info = cust.getCustomer (custID); } catch (SQLException e) { System.err.println ("Read failed:"); System.err.println (e.getMessage()); System.exit (4); } } }
487
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Kompilieren Sie das Programm Example.java (denken Sie daran, die Pfadangabe durch den Verzeichnispfad zu Ihrer Datenbank zu ersetzen): C:\MasteringJava\Ch21>javac Example.java C:\MasteringJava\Ch21>java Example Example use: java Example <username> <password> C:\MasteringJava\Ch21>java Example jdbc:sybase://dbserver:8080/billing john MyPassword Dieses Beispiel zeigt, wie mit CallableStatement-Objekten Stored Procedures zur
JDBC-Treiber Ein wirklicher Glanzpunkt der JDBC-API ist, dass Sie Anwendungen in dem Bewusstsein entwickeln kGnnen, dass alle großen Datenbankanbieter parallel an der Entwicklung von Treibern arbeiten. Eine Vielzahl von Treibern ist sowohl bei Datenbankanbietern als auch bei Fremdherstellern erh$ltlich. Sie sollten sich daher nach dem besten Verh$ltnis von Funktionen, Kosten und Support umsehen. Treiber gibt es in vielen unterschiedlichen Auspr$gungen, je nach Struktur und Datenbanktyp, f.r den sie konzipiert wurden. Sun teilt die Datenbanktreiber in vier Kategorien ein: 1
JDBC-ODBC-Brckentreiber (siehe Abbildung 15.6), die mit ODBC-Bin$rcode implementiert werden (in einigen F$llen auch mit einer Clientbibliothek). Der Br.ckentreiber besteht aus drei Teilen: einigen C-Bibliotheken, welche die JDBC mit dem ODBC-Treibermanager verbinden, dem ODBC-Treibermanager und dem ODBC-Treiber.
Abb. 15.6 JDBC-ODBC-Br1ckentreiber
488
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JDBC-Treiber
ODBC ist eine Microsoft-API f.r den Zugriff auf Datenbanken, die auf PCs weit verbreitet ist. ODBC-Treiber sind f.r die meisten PC-basierten Datenbanksysteme erh$ltlich und ermGglichen die Migration nach JDBC, ohne dass ODBC-Treiberhersteller einen zus$tzlichen JDBC-Treiber entwickeln m.ssten.
Hinweis
Native Bibliothek-zu-Java-Implementierungen (siehe Abbildung 15.7). Dieser Treiber verwendet native Bibliotheksaufrufe in C, um JDBC in die native Clientbibliothek zu .bersetzen. Diese Treiber setzen in C geschriebene Bibliotheken ein, die anbieterspezifische Funktionen bereitstellen und diese Bibliotheken (.ber native Methodenaufrufe) an JDBC binden. F.r Oracle, Sybase, Informix, DB2 und andere auf ClientBibliotheken basierende RDBMSs waren diese Treiber zuerst erh$ltlich.
Abb. 15.7 Native Bibliothek-zu-Java-Treiber
Die folgende Abbildung 15.8 zeigt die Struktur eines Netzwerkprotokoll-Java-Treibers. JDBC-Aufrufe werden von diesem Treiber in ein DBMS-unabh$ngiges Protokoll .bersetzt und .ber einen Socket an einen Mittelschichtserver gesendet. Der Code der Mittelschicht kann anstelle des Clients auf eine Vielzahl von Datenbanken zugreifen. Dieses Vorgehen wird immer popul$rer und ist das mit Abstand flexibelste. Es befasst sich auch eigens mit Fragen der Netzwerksicherheit und der
Abb. 15.8 DBMS-unabh7ngiger Netzwerkprotokolltreiber
489
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
Native Protokoll-Java-Treiber (siehe Abbildung 15.9). JDBC-Aufrufe werden direkt in das vom DBMS-Server verwendete Netzwerkprotokoll konvertiert. In diesem Treiberszenario unterst.tzt der Datenbankanbieter einen Netzwerk-Socket und der JDBC-Treiber kommuniziert .ber eine Socket-Verbindung direkt mit dem Datenbankserver. Der clientseitige Code kann in Java geschrieben werden. Diese LGsung hat den Vorteil, dass sie sehr einfach zu implementieren und sehr praktisch f.r Intranets ist. Da aber das Netzwerkprotokoll vom Anbieter definiert wird und es sich normalerweise um eine propriet$re Technologie handelt, kann der Treiber meist nur vom Datenbankanbieter bezogen werden.
Abb. 15.9 Nativer DBMS-ProtokollJava-Treiber
Die JDBC-ODBC-Br,cke Die JDBC-ODBC-Br.cke ist ein JDBC-Treiber, der f.r die Umwandlung von JDBC-Aufrufen in ODBC-Operationen sorgt. Es gibt eine Reihe von DBMSs, die ODBC unterst.tzen. Wenn ein Unternehmen von der GrGße von Microsoft einen Standard f.r den Datenbankzugriff entwickelt, gibt es selbstverst$ndlich Anbieter, die dem folgen. Tats$chlich sind .ber 50 verschiedene ODBC-Treiber erh$ltlich. Wie bereits weiter oben erw$hnt, basieren sowohl JDBC als auch ODBC auf X/Open-SQL-CLI. Daher ist die Umwandlung von JDBC in ODBC relativ direkt. ODBC umfasst einige clientseitige Bibliotheken sowie einen f.r das Betriebssystem des Clients und in einigen F$llen auch f.r die Maschinenarchitektur spezifischen Treiber. Aus der Perspektive des Entwicklers ist der Einsatz einer JDBC-ODBC-Br.cke eine einfache Wahl. Anwendungen kommunizieren direkt mit den JDBC-Schnittstellenklassen, daher entspricht die Verwendung einer JDBC-ODBC-Br.cke dem Einsatz eines beliebigen anderen JDBC-Treibers. Bei der Implementierung einer JDBCODBC-Br.cke muss sich der Entwickler aber genau dar.ber im Klaren sein, was zur Ausf.hrung der Anwendung erforderlich ist. Da ODBC-Aufrufe mit bin$ren C-Aufrufen durchgef.hrt werden, muss der Client .ber lokale Kopien des ODBC-Treibers, des ODBC-Treibermanagers und der clientseitigen Bibliotheken verf.gen.
490
Aus diesen Gr.nden empfiehlt Sun, die JDBC-ODBC-Br.cke nicht f.r Web-basierte Datenbankzugriffe einzusetzen. F.r den Intranet-Zugriff muss der Entwickler das Java-Programm entweder als Java-Anwendung oder als Java-Applet (das als sicherer Quellcode auf dem lokalen Client-Dateisystem ausf.hrbar w$re) auf die Clientrechner verteilen.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Alternative Connectivity-Strategien
Aktuelle JDBC-Treiber Da JDBC-Treiber von so vielen Anbietern und so schnell hintereinander herausgegeben werden, ist eine endg.ltige Liste einfach nicht zweckm$ßig und w.rde zum Zeitpunkt der Drucklegung dieses Buches schon wieder veraltet sein. Informationen .ber Anbieter von aktuellen Treibern, die Produktnamen und die unterst.tzten Datenbanken finden Sie unter http://java.sun.com/products/jdbc/industry.html
Alternative Connectivity-Strategien Beim Entwickeln von Datenbankanwendungen stellt JDBC eine sehr einfache MGglichkeit dar, Zeit und k.nftige Investitionen zu sparen. Die API gew$hrleistet, dass ein gem$ß dem JDBC-Standard geschriebenes Clientprogramm mit jedem JDBCkompatiblen Treiber und jeder beliebigen Datenbankkombination ausf.hrbar ist. Der n$chste Abschnitt erl$utert zwei alternative Sun-Technologien, die auch eine flexible MGglichkeit darstellen, Entwicklungsinvestitionen zu reduzieren: Remote Method Invocation (RMI) und Common Object Request Broker Architecture (CORBA).
Remote Method Invocation (RMI) Mit RMI und CORBA kann eine Verbindung zwischen Clientanwendungen und -datenbanken hergestellt werden, obwohl dabei einige Vorkehrungen zu treffen sind. RMI ist analog zu Remote Procedure Calls (RPC), der MGglichkeit eine Prozedur in einem anderen ausgef.hrten Programm aufzurufen und auszuf.hren. RPC wurde nicht f.r verteilte Objektsysteme entwickelt, was aber gerade die St$rke von RMI darstellt. RMI ermGglicht Clientanwendungen, Methoden von auf einem Remote-Server vorhandenen Objekten so auszuf.hren, dass der Eindruck entsteht, es handele sich um lokale Objekte. F.r die Datenbank-Connectivity bedeutet dies, dass der Entwickler eine Anwendung erstellen kann, die direkt auf Datenbankobjekte zugreift, obwohl diese Objekte eigentlich auf dem Datenbankserver-Host implementiert sind. Da RMI Mechanismen f.r die
Common Object Request Broker Architecture (CORBA) Common Object Request Broker Architecture (CORBA) ist das Ergebnis jahrelanger Arbeit der Object Management Group (OMG). Die OMG ist ein Konsortium von mehr als 500 Unternehmen, das eine Spezifikation f.r eine Kommunikationsinfra-
491
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Java Database Connectivity (JDBC)
struktur erarbeitet hat. Diese Infrastruktur ermGglicht verschiedenen Computersprachen auf verschiedenen Computerarchitekturen, auf eine verteilte Kollektion von Objekten zuzugreifen. CORBA bietet Datenbankanwendungsentwicklern in einer heterogenen Entwicklungsumgebung die grGßtmGgliche Flexibilit$t. Der Server kGnnte in C oder C++ entwickelt werden und der Client kGnnte ein Java-Applet sein. Gegenw$rtig wird bei Sun ein Compiler f.r die Java Interface Definition Language (IDL) hergestellt, der eine CORBA 2.0 IDL-Datei .bernimmt und die erforderlichen Hilfsdateien, so genannte Stubs, f.r eine Clientimplementierung erzeugt. CORBA ist ein Standard (Version 2.3 zum Zeitpunkt als dieses Buch geschrieben wurde), der eine anbieter- und sprachneutrale Definitionssprache definiert. Anhand der IDL wird ein Zusammenschluss zwischen einer Client- und Serverimplementierung hergestellt. IDL ist keine Implementierungssprache, sie beschreibt lediglich Objektdienste und Operationen, die mit einer Implementierung dieser Dienste ausgef.hrt werden kGnnen. Den Kern von CORBA stellt der Object Request Broker (ORB) dar. Der ORB ist die Hauptkomponente f.r die Informations.bertragung (Anforderungen von Operationen und deren Ergebnisse) zwischen dem Client und dem Server einer CORBA-Anwendung. Der ORB verwaltet Marshaling-Anforderungen, richtet eine Verbindung zum Server ein, sendet die Daten und f.hrt die serverbezogenen Anforderungen aus. Derselbe Prozess wird ausgef.hrt, wenn der Server die Ergebnisse der Operation zur.ckgibt. Die CORBA-2.0-Spezifikation definiert auch ein InteroperabilitGtsprotokoll f.r das Internet (IIOP), welches das Verbindungsprotokoll zwischen dem Client und dem Server .ber den ORB festlegt. Dadurch kGnnen Entwickler einen Client-IDL-Compiler und einen Server-IDL-Compiler von verschiedenen Herstellern einsetzen. Neben Sun gibt es noch eine Reihe Hersteller, die CORBA-2.0-Kompatibilit$t, einschließlich IIOP und Java-IDL-Compiler anbieten. Ein Vielzahl zus$tzlicher Informationen .ber das OMG-Konsortium finden Sie auf der Webseite http://www.omg.org/.
Connectivity zu Objektdatenbanken Eine weitere Alternative – neben RMI und CORBA – stellen Objektdatenbanken dar, besonders solche, die das Objektmodell von Java unterst.tzen. Es gibt mehrere Objektdatenbanken f.r Java und – als Ergebnis der Object Database Management Group (ODMG) – eine Spezifikation zum Speichern von Java-Objekten in Datenbanken. Die Spezifikation heißt ODMG 2.0. Hersteller von Java-Objektdatenbanken liefern Produkte, die diesen Standard unterst.tzen.
492
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ausblick
Informationen .ber den ODMG-2.0-Standard finden Sie auf der Website der ODMG unter http://www.odmg.org/ oder in dem Buch The Object Database Standard: ODMG 2.0 (Morgan Kaufmann Publishers, 1997, ISBN: 1-55860-463-4).
Hinweis
Connectivity mit Web-basierten Datenbanksystemen Es gibt noch eine weitere Alternative f.r den Zugriff auf Datenbanken von Webseiten aus, die zwar nicht speziell f.r JDBC und nicht immer auf Java bezogen ist. Es ist aber mGglich, mit HTML-Seiten Informationen an CGI-Skripte (Common Gateway Interface) zu senden. Die CGI-Skripte stellen dann die Verbindung zur Datenbank her und geben die Ergebnisse an die HTML-Seite zur.ck. Anbieter auf dem Web-basierten Datenbankmarkt verf.gen .ber eine Vielzahl von Strategien zur Verbesserung der Leistung von CGI durch in C oder C++ geschriebene Multithread-Anwendungen, die die Datenbankverbindung und die Abfragen verwalten. Neben dem JDK stellt JavaSoft eine weitere API bereit, mit der Servlets programmiert werden kGnnen. Die API ist im JSDK (Java Servlet Development Kit) zusammengefasst. Das JSDK mit die Verwendung von C, C++ oder Perl f.r CGI-Skripte .berfl.ssig. Das Servlet kann angewiesen werden, eine Datenbankverbindung zu Gffnen, ein Ergebnis zu ermitteln und die Daten in Form von HTML-Webseiten an einen Clientbrowser zur.ckzusenden. In Kapitel 7: Applets, Applikationen und das Java Development Kit finden Sie ausf.hrliche Informationen .ber die Servlet-Technologie.
Ausblick Kapitel 16: Die 2-D-Grafik-API behandelt die Grafikfunktionen der 2D-API.
493
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Die Klasse Graphics2D und die Darstellung von Formen
497
Zeichenoperationen
504
Verallgemeinerte Pfade fr selbstdefinierte Kurven
519
Ausblick
549
16
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Aus „Java 2 Developer’s Handbook“ von Philip Heller und Simon Roberts, ISBN 0-7821-2179-9 , 1.248 Seiten, $59,99
Die urspr.nglichen F$higkeiten von Java zur zweidimensionalen Darstellung waren ziemlich primitiver Art. Sie konnten eine Linie mit einer Mindestst$rke zeichnen. Es war mGglich, den Umriss eines Rechtecks zu zeichnen oder ein solches mit einem Muster zu f.llen. Dieselben Operationen konnten auch an einem Oval oder (mit gewissem Mehraufwand) einem unregelm$ßigen Vieleck durchgef.hrt werden. Sollte mehr Fantasie ins Spiel kommen, mussten Sie versuchen, mit Bildern (Bitmaps) zurechtzukommen. Wenn das Ziel Ihrer W.nsche beispielsweise in einer diagonalen Linie mit 25 Pixel Breite bestand, blieb Ihnen nur .brig, die erforderlichen Berechnungen selbst durchzuf.hren und eine geeignete MemoryImageSource zu generieren. Sobald jemand in der Marketingabteilung die Vorgabe in 28 Pixel Breite und einen etwas steileren Neigungswinkel ab$nderte, mussten Sie ans Zeichenbrett (oder den Computer) zur.ck. Wenn Sie Farben mischen, kompliziertere Varianten f.r Linien.berg$nge verwenden oder das Koordinatensystem einer Rotation unterziehen wollten, befanden Sie sich in demselben Dilemma – Sie mussten mit großem Kodieraufwand geometrische Berechnungen oder Farbmanipulationen durchf.hren, nur um einen Effekt zu erzielen, der auf anderen (nicht-Java-)-Plattformen zum Standard gehGrt. Die JDK-1.1-Funktionalit$t war einfach ungeeignet, anspruchsvollere grafische Entw.rfe zu unterst.tzen. Die 2-D-API $ndert diese Situation durchgreifend. Dieses Kapitel f.hrt Sie in einige der grundlegenden Konzepte der neuen API ein. Dazu gehGren Kurven, Transformationen, Striche und F.llungen.
Visuelle Gesichtspunkte Einer der Grnde fr die Popularitt von Java als Programmiersprache ist seine Fhigkeit, grafische Benutzeroberflchen schnell und problemlos zu erstellen. Wie Programmierer wissen, geh"ren Design und visuelle Wirkung der grafischen Oberflche zu den wichtigsten Elementen einer jeden Anwendung, da sie festlegen, auf welche Weise die Benutzer mit dem Programm interagieren. Es gibt zwei Aspekte visueller Gestaltung einer grafischen Benutzeroberflche, die es wert sind, hier erwhnt zu werden: Schriftarten und Farbmodelle. java.awt.Font Die Verfgbarkeit von Schriftarten hngt von der jeweiligen Hardwareplattform ab, doch von allen Java-Portierungen wird erwartet, dass sie Helvetica, Times Roman, Courier und Dialogschriftarten untersttzen sowie Ersatzschriftarten fr jede gewhlte Schriftart anbieten. Das Paket java.awt.Font stellt Klassen und Schnittstellen fr den Umgang mit Schriftarten bereit. Es enthlt Untersttzung fr die Darstellung von Type 1-, Type 1 Multiple Master-, OpenType- und TrueType-Schriftarten.
496
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klasse Graphics2D und die Darstellung von Formen
java.awt.Color Ebenso wie Schriftarten sind auch Farben von der Hardware des Benutzers abhngig. Das Farbenmodell von Java untersttzt 32-Bit-Farben (True Color) und besitzt zwei integrierte gebruchliche Farbmodelle, Direct ColorModel und IndexColorModel. Das Paket java.awt.Color stellt Klassen bereit, mit deren Hilfe vorhandene Farbrume verwendet oder eigene definiert werden k"nnen. Außerdem enthlt das Paket eine Implementierung eines Farbraums, der auf der auf der Profile Format Specification des (ICC International Color Consortium) basiert.
Dieses Kapitel geht bei der Behandlung seines Themas sehr stark auf visuelle Details ein. Das Ziel des Kapitels ist es, Sie mit den 2-D-DarstellungsmGglichkeiten der genannten API vertraut zu machen. Um Ihnen die bestmGgliche Illustration der neuen Konzepte zu bieten, enth$lt dieses Kapitel mehrere
Die Klasse Graphics2D und die Darstellung von Formen Nach dem JDK-1.1-Grafikmodell war es mGglich, ein Rechteck oder Oval zu zeichnen oder einzuf$rben, da die Klasse Graphics solche Methoden wie drawRect(), fillOval() usw. besaß (und immer noch besitzt). Anders formuliert: Sie konnten eine bestimmte Form nur dann zeichnen, wenn die Klasse Graphics eine Methode bereitstellte, die solche Formen unterst.tzte. Durch die neue 2-D-API stehen weitere Formen zur Verf.gung, mit deren Hilfe Sie sogar eigene Formen definieren kGnnen. Sie kGnnen die Umrisslinie einer Form zeichnen oder die Form mit einer Farbe oder einem Muster f.llen. Es ist sogar mGglich, eine Form als Schablone zu verwenden. Um dies alles zu tun, m.ssen Sie allerdings den Umgang mit der Klasse Graphics2D und der Schnittstelle Shape beherrschen.
497
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Die Klasse Graphics2D Die Klasse java.awt.Graphics ist erweitert worden. Die neue Subklasse nennt sich java.awt.Graphics2D. In JDK 1.3 wird beim Aufruf der paint()-Methode einer Komponente das Argument der Methode weiterhin als eine Objektinstanz von Typ Graphics deklariert, doch was zur Laufzeit tats$chlich .bergeben wird, ist eine Instanz der Subklasse Graphics2D. Wenn Sie die Funktionalit$t der 2D-API nicht w.nschen, kGnnen Sie die vom JDK 1.1 gewohnte Vorgehensweise beibehalten, d.h. f.r das gegebene Argument Funktionen wie setColor(), drawLine() usw. aufrufen. Wenn Sie andererseits die Vorteile der neuen 2D-Funktionen nutzen mGchten, m.ssen Sie f.r das Methodenargument eine Typanpassung vornehmen und es in ein Graphics2D-Objekt umwandeln: public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; // Verschiedene Aufrufe von g2d-Methoden Nat.rlich sind alle von der Klasse Graphics geerbten Methoden auch in der Subklasse Graphics2D weiterhin verf.gbar. Zus$tzlich stehen neue Funktionen in weitreichendem Umfang bereit. In diesem Kapitel werden die folgenden Methoden n$her untersucht:
clip(Shape)
setRenderingHint (RenderingHint)
draw (Shape)
setStroke (Stroke)
fill(Shape)
shear (double, double)
rotate (double)
transform (AffineTransform)
scale (double, double)
translate (double, double)
setPaint (Paint)
Viele dieser Methoden operieren auf Formen, die im nachfolgenden Abschnitt behandelt werden.
Die Schnittstelle Shape und ihre Implementierungen Die Schnittstelle Shape wurde in JDK 1.1 eingef.hrt, aber nicht verwendet. Diese Schnittstelle ist mit Erscheinen von Java 2 umfassend modifiziert worden. Dass Sie die Schnittstelle jemals selbst implementieren, ist jedoch sehr unwahrscheinlich. Eher werden Sie stattdessen eine der folgenden Implementierungsklassen verwenden: 498
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klasse Graphics2D und die Darstellung von Formen
Arc2D
Ellipse2D
Line2D
QuadCurve2D
Rectangle2D
CubicCurve2D
RoundRectangle2D
GeneralPath
Diese acht Klassen sind alle Bestandteil des Pakets java.awt.geom.Die ersten sieben Klassen sind Entsprechungen f.r bestimmte Kurventypen, w$hrend die achte Klasse – GeneralPath – eine selbstdefinierte Kurve repr$sentiert, deren Verlauf Sie beliebig festlegen kGnnen. Nat.rlich enth$lt das Paket java.awt.geom weitaus mehr 2D-Klassen, die hier aber nicht alle im Detail besprochen werden. Dieser Abschnitt erGrtert die ersten sieben Formen. GeneralPath ist ein weitreichendes Thema und wird in einem eigenen Abschnitt am Ende dieses Kapitels behandelt. Viele der durch die 2D-API neu eingef.hrten Klassen befinden sich in dem Paket java.awt. Die restlichen Klassen sind Teil des neuen Pakets java.awt.geom. Wenn Sie vorhaben, in grGßerem Stil 2D-Code zu schreiben, sollten Sie sich angewGhnen, java.geom.* zu importieren.
Hinweis
In JDK 1.1 konnten Sie nur Integer zur Festlegung von Formparametern .bergeben. Mit der Ausnahme von GeneralPath erwarten alle oben aufgelisteten Klassen nun die
499
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
public abstract class Rectangle2D extends RectangularShape implements Shape { public static class Rectangle2D.Double extends Rectangle2D { ... } } MGglicherweise fragen Sie sich, warum es nGtig ist, die Verwendung von Gleitkommawerten mit einfacher und doppelter Genauigkeit f.r Parameter zu unterst.tzen, deren Maß Pixel sind. In JDK 1.1 w.rde dies keinen Vorteil bieten. Doch in JDK 1.3 haben Sie die MGglichkeit, ein Koordinatensystem zu skalieren, zu drehen und zu verschieben. Sie kGnnten beispielsweise einen Raum erzeugen, in dem die Werte der X-Achse im Bereich -0,0001 bis +0,0001 liegen. F.r solche Situationen sind Parameter zur Verf.gung zu stellen, die Gleitkommawerte einfacher (oder in extremen F$llen sogar doppelter) Genauigkeit repr$sentieren. Die nachfolgend aufgelistete Anwendung zeichnet ein einfaches Rechteck des Typs Rectangle2D. Listing 16.1
import java.awt.*; import java.awt.geom.*;
RectDemo.java
public class RectDemo extends Frame { public static void main(String[] args) { (new RectDemo()).setVisible(true); } public RectDemo() { setSize(150, 150); } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; Rectangle2D r2d = new Rectangle2D.Float(10f, 10f, 130f, 130f); g2d.draw(r2d); } } Die Formen Line2D (Linien) und Ellipse2D (Ellipsen) sind umkompliziert. Die Formen RoundRectangle2D,Arc2D, QuadCurve2D und CubicCurve2D erfordern jedoch eine gewisse Erkl$rung.
500
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klasse Graphics2D und die Darstellung von Formen
Die Form RoundRectangle2D ist ein Rechteck mit abgerundeten Ecken. Der Konstruktor akzeptiert als Argumente die .blichen Angaben (x- und x-Wert, Breite, HGhe) sowie die Breite und HGhe des abgerundeten Teils der Ecken. Der nachfolgend aufgelistete Beispielcode zeichnet ein abgerundetes Rechteck an der Startposition (20,30) mit 100 Pixel Breite und 200 Pixel HGhe. Der abgerundete Teil einer jeden Ecke ist 10 Pixel breit und 15 Pixel hoch. RoundRectangle2D rr = new RoundRectangle2D.Float(20, 30, 100, 200, 10, 15); Die Form Arc2D repr$sentiert ein Kreis- oder Ellipsensegment. Der Konstruktor der internen Subklasse Float ist unten zu sehen. (Der Konstruktor der internen Subklasse Double hat denselben Aufbau.) Public Arc2D.Float(float x, float y, float width, float height, float arcStart, float arcExtent, int type} Der Kreis bzw. die Ellipse wird durch Bereitstellung der (x,y)-Position sowie der Breite (width) und HGhe (height) des Begrenzungsquadrats oder -rechtecks festgelegt. Die Kurve selbst beginnt an dem durch den Winkel arcStart gegebenen Punkt und erstreckt sich .ber einen Winkel von arcExtent Grad. Beide Gradangaben sind gegen den Uhrzeigersinn gemessen. F.r den Typ (type) sind folgende Angaben mGglich: Arc2D.CHORD – die Kurve wird durch ein geradliniges Segment geschlossen, das die beiden Endpunkte der Kurve verbindet. Arc2D.PIE – die Kurve wird durch zwei geradlinige Segmente geschlossen, die die Endpunkte der Kurve mit dem Mittelpunkt des Kreises bzw. der Ellipse verbinden. Arc2D.ARC – die Kurve ist nicht geschlossen. Die Formen QuadCurve2D und CubicCurve2D unterst.tzen die Darstellung glatter Kurven. Eine Kurve zweiter Ordnung (QuadCurve) wird durch Angabe der Kurvenendpunkte und eines dritten St.tzpunkts festgelegt, der das Kurvenverhalten zwischen den Endpunkten bestimmt. Eine Kurve dritter Ordnung (CubicCurve) ist $hnlich, verwendet aber zwei zus$tzliche St.tzpunkte. Die zugehGrigen Float-Subklassen haben folgende Konstruktoren: QuadCurve2D.Float(float x0, float y0, float ctrlX, float ctrlY, float x1, float y1) CubicCurve2D.Float(float x0, float y0, float ctrlX0, float ctrlY0, float ctrlX1, float ctrlY1, float x1, float y1) 501
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Die Konstruktoren der internen Double-Klassen haben dieselbe Syntax bis auf den Umstand, dass ihre Parameter vom Typ double sind. Die unten aufgelistete Anwendung ShapeSampler zeichnet ein abgerundetes Rechteck in der oberen linken Ecke, drei Arten von Kurven in der oberen rechten Ecke und darunter jeweils eine Kurve zweiter und dritter Ordnung (siehe Abbildung 16.1).
Abb. 16.1 Das Programm ShapeSampler
Listing 16.2
import java.awt.*; import java.awt.geom.*;
ShapeSampler.java
public class ShapeSampler extends Frame { public static void main(String[] args) { (new ShapeSampler()).setVisible(true); } public ShapeSampler() { 502
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Klasse Graphics2D und die Darstellung von Formen
super("Shape Sampler"); setSize(400, 550); } public void paint(Graphics g) { RoundRectangle2D rrect; Arc2D arc; QuadCurve2D quadcurve; CubicCurve2D cubcurve; Graphics2D g2d = (Graphics2D)g; rrect = new RoundRectangle2D.Float(50, 50, 100, 200, 30, 20); g2d.draw(rrect); arc = new Arc2D.Float(200, 50, 100, 50, 0, 90, Arc2D.OPEN); g2d.draw(arc); arc = new Arc2D.Float(200, 125, 100, 50, 0, 90, Arc2D.CHORD); g2d.draw(arc); arc = new Arc2D.Float(200, 200, 100, 50, 0, 90, Arc2D.PIE); g2d.draw(arc); quadcurve = new QuadCurve2D.Float(50, 300, 320, 300, 350, 400); g2d.draw(quadcurve); g2d.fillOval(48, 298, 5, 5); g2d.fillOval(318, 298, 5, 5); g2d.fillOval(348, 398, 5, 5); cubcurve = new CubicCurve2D.Float(30, 400, 150, 400, 200, 500, 350, 450); g2d.fillOval(28, 398, 5, 5); g2d.fillOval(148, 398, 5, 5); g2d.fillOval(198, 498, 5, 5); g2d.fillOval(348, 448, 5, 5); g2d.draw(cubcurve); } } Kompilieren Sie die Anwendung ShapeSampler und f.hren Sie sie aus. Verwenden Sie hierzu die folgenden beiden Befehle: C:\javadevhdbk\ch16\javac ShapeSampler.java C:\javadevhdbk\ch16\java ShapeSampler 503
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Zeichenoperationen Die drei wesentlichen Operationen, die Sie auf einer Form ausf.hren kGnnen, sind Zeichnen, Ausfllen und Clipping. Sie kGnnen eine Form auch transformieren, was es erlaubt, den Koordinatenraum des gegebenen Zeichenbereichs zu manipulieren. Die grundlegenden Transformationen sind Rotation, Translation und Skalierung.
Zeichnen von Linien In dem JDK-1.1-Grafikmodell war eine durchgehende Linie unendlicher Breite die einzige Art von Linie, die Sie zeichnen konnten. Bei Verwendung der 2-D-API haben Sie die MGglichkeit, die Linienbreite, die Art des Linienabschlusses, die Art der Linienzusammenf.hrung sowie das Strichmuster anzugeben. Der Linienstil wird in zwei Schritten festgelegt: 1
Zuerst wird eine Instanz der Klasse BasicStroke erzeugt.
2
Anschließend erfolgt ein Aufruf von setStroke() f.r das aktuelle Graphics2D-Objekt, wobei als Argument das gerade erzeugte BasicStroke-Objekt .bergeben wird.
Es gibt verschiedene Konstruktoren f.r BasicStroke, die die Angabe unterschiedlicher Optionen erlauben oder die verschiedenen Linienattribute ignorieren. Der Konstruktor mit den meisten MGglichkeiten ist der folgende: public BasicStroke(float width, int cap, int join, float miterLimit, float dash[], float dashphase) Die Argumente des Konstruktors haben die folgende Bedeutung: width – legt die Linienbreite fest. cap – legt den Typ des Linienabschlusses fest. MGglich sind die Werte BasicStroke.BUTT, BasicStroke.ROUND oder BasicStroke.SQUARE. join – legt den Stil f.r Linienzusammenf.hrungen fest. MGgliche Werte sind BasicStroke.BEVEL, BasicStroke.MITER oder BasicStroke.ROUND. miterLimit – legt die maximale Erweiterung einer durch Gehrung bewirkten Linienzusammenf.hrung fest. Hat nur Bedeutung bei Verwendung einer Linienzusammenf.hrung des Typs BasicStroke.MITER. dash – legt ein Array mit float-Werten fest, die das Muster der Linienstrichelung beschreiben. dashphase – legt den Startpunkt innerhalb des Arrays von Strichmustern fest. 504
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Zeichenoperationen
Das Programm StrokeLab ermGglicht es Ihnen, die Linienbreite, den Typ des Linienabschlusses und die Art der Linienzusammenf.hrung einer Graphics2D-Linie zu variieren. Abbildung 16.2 zeigt die Anzeige des Programms, welches f.r diese Abbildung mit einer Linienst$rke von 10, abgerundeten Enden und abgeschr$gten Linienzusammenf.hrungen konfiguriert wurde.
Abb. 16.2 Das Programm StrokeLab
Wenn Sie die Anwendung ausf.hren, kGnnen Sie mit unterschiedlichen Linienst$rken, Linienabschl.ssen und Linienzusammenf.hrungen experimentieren. Der zugehGrige Code ist nachfolgend (in Listing 16.3) aufgelistet. Am wichtigsten sind die Zeilen in paint(), die ein BasicStroke-Objekt erzeugen und anschließend setStroke() f.r das Graphics2D-Objekt aufrufen: BasicStroke stroke = new BasicStroke(width, cap, join); g.setStroke(stroke); import java.awt.*; import java.awt.event.*; public class StrokeLab extends Frame implements ActionListener, AdjustmentListener { private Choice capChoice; private Choice joinChoice; private Choice dashCountChoice; private Scrollbar widthBar; private TextField widthTF; private Button drawBtn; private Button quitBtn; private StrokeCanvas strokeCanvas; private final static int WIDTH_MIN = 0; private final static int WIDTH_MAX = 21; private final static String[] CAP_TYPE_NAMES = { "Butt", "Round", "Square"
Listing 16.3 StrokeLab.java
505
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
}; private final static int[] CAP_TYPES = { BasicStroke.CAP_BUTT, BasicStroke.CAP_ROUND, BasicStroke.CAP_SQUARE }; private final static String[] JOIN_TYPE_NAMES = { "Bevel", "Miter", "Round" }; private final static int[] JOIN_TYPES = { BasicStroke.JOIN_BEVEL, BasicStroke.JOIN_MITER, BasicStroke.JOIN_ROUND }; class WideScrollbar extends Scrollbar { private int widthPix; WideScrollbar(int min, int max, int widthPix) { super(Scrollbar.HORIZONTAL, 0, 1, WIDTH_MIN, WIDTH_MAX); this.widthPix = widthPix; } public Dimension getPreferredSize() { int prefHt = super.getPreferredSize().height; return new Dimension(widthPix, prefHt); } } public static void main(String[] args) { (new StrokeLab()).setVisible(true); }
506
StrokeLab() { setSize(700, 300); Panel panel = new Panel(); capChoice = new Choice(); for (int i=0; i
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Zeichenoperationen
joinChoice.addItem(JOIN_TYPE_NAMES[i]); panel.add(new Label("Join:")); panel.add(joinChoice); widthBar = new WideScrollbar(WIDTH_MIN, WIDTH_MAX, 120); widthBar.addAdjustmentListener(this); widthTF = new TextField(" 0"); widthTF.setEnabled(false); panel.add(new Label("Width:")); panel.add(widthBar); panel.add(widthTF); drawBtn = new Button("Draw"); drawBtn.addActionListener(this); panel.add(drawBtn); quitBtn = new Button("Quit"); quitBtn.addActionListener(this); panel.add(quitBtn); add(panel, BorderLayout.NORTH); strokeCanvas = new StrokeCanvas(); add(strokeCanvas, BorderLayout.CENTER); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } public void actionPerformed(ActionEvent e) { { if (e.getSource() == quitBtn) System.exit(0); strokeCanvas.repaint(); } public void adjustmentValueChanged(AdjustmentEvent e) { widthTF.setText("" + widthBar.getValue()); } class StrokeCanvas extends Canvas { public void paint(Graphics graphics) { Graphics2D g = (Graphics2D)graphics; g.setColor(Color.blue); float width = widthBar.getValue(); int cap = CAP_TYPES[capChoice.getSelectedIndex()]; int join =
507
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
JOIN_TYPES[joinChoice.getSelectedIndex()]; BasicStroke stroke = new BasicStroke(width, cap, join); g.setStroke(stroke); g.drawRect(50, 10, 250, 200); g.drawLine(340, 10, 550, 210); g.drawLine(340, 210, 550, 10); } } } Kompilieren Sie die Anwendung StrokeLab und f.hren Sie sie aus. Verwenden Sie hierzu die folgenden beiden Befehle: C:\javadevhdbk\ch16\javac StrokeLab.java C:\javadevhdbk\ch16\java StrokeLab
Ausfllen Die 2D-API bietet eine Vielzahl von Optionen f.r das Ausf.llen von Formen. Sie kGnnen wie bisher im JDK 1.1 die Form mit einer einzigen Farbe einf$rben. Außerdem haben Sie jetzt die MGglichkeit, Formen unter Verwendung von Texturmustern oder einem Farbverlauf zu f.llen. Die Art der F.llung teilen Sie einem Graphics2D-Objekt mit einem Aufruf seiner Methode setPaint() mit. Das Argument hat den Typ java.awt.Paint, welcher eine Schnittstelle bezeichnet. Es existieren drei Klassen, die die Schnittstelle Paint implementieren und zur Auswahl f.r die
508
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Zeichenoperationen
Ein Aufruf bewirkt die Festlegung eines F.llmusters, das aus sich wiederholenden Kopien einer Bitmap besteht. Das im Argument control .bergebene Rechteck gibt den Teil des Bildes an, der als F.llmuster verwendet werden soll, sowie dessen Positionierung. Die
Abb. 16.3 Das Programm GradientLab
509
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Das vollst$ndige Listing f.r GradientLab ist weiter unten aufgef.hrt (Listing 16.4). Listing 16.4
import java.awt.*; import java.awt.event.*;
GradientLab.java
public class GradientLab extends Canvas implements MouseListener { private TextField logTF; private int nClicks; private int[] xs, ys; private Color[] colors; private ColorDialog colorDialog; private final static String[] messages = { "Click to define first point.", "Click to define second point.", "Click to fill with gradient.", "Click to clear and do it again." }; public static void main(String[] args) { Frame frame = new Frame(); TextField tf = new TextField(); frame.add(tf, BorderLayout.NORTH); tf.setFont(new Font("Serif", Font.PLAIN, 18)); frame.add(new GradientLab(tf), BorderLayout.CENTER); frame.pack(); frame.setVisible(true); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); }
510
GradientLab(TextField logTF) { this.logTF = logTF; logTF.setText(messages[0]); setBackground(Color.white); addMouseListener(this); xs = new int[2]; ys = new int[2]; colors = new Color[2]; Component c = logTF; while (!(c instanceof Frame))
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Zeichenoperationen
c = c.getParent(); colorDialog = new ColorDialog((Frame)c); } public Dimension getPreferredSize() { return new Dimension(400, 400); } public void mouseClicked(MouseEvent e) { if (nClicks <= 1) { xs[nClicks] = e.getX(); ys[nClicks] = e.getY(); colorDialog.setVisible(true); colors[nClicks] = colorDialog.getColor(); } nClicks = ++nClicks % 4; logTF.setText(messages[nClicks]); repaint(); } private void clear(Graphics g) { g.setColor(Color.white); g.fillRect(0, 0, getSize().width, getSize().height); } public void paint(Graphics graphics) { Graphics2D g = (Graphics2D)graphics; g.setColor(Color.white); g.fillRect(0, 0, getSize().width, getSize().height); switch(nClicks) { case 0: // LIschen break; case 1: // 1. Punkt g.setColor(Color.black); g.fillOval(xs[0]-5, ys[0]-5, 10, 10); g.setColor(colors[0]); g.fillOval(xs[0]-4, ys[0]-4, 8, 8); break; case 2: // 2. Punkt g.setColor(Color.black); g.fillOval(xs[0]-5, ys[0]-5, 10, 10); g.setColor(colors[0]);
511
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
g.fillOval(xs[0]-4, ys[0]-4, 8, 8); g.setColor(Color.black); g.fillOval(xs[1]-5, ys[1]-5, 10, 10); g.setColor(colors[1]); g.fillOval(xs[1]-4, ys[1]-4, 8, 8); break; case 3: // 3. Punkt GradientPaint gradi = new GradientPaint(xs[0], ys[0], colors[0], xs[1], ys[1], colors[1]); g.setPaint(gradi); g.fillRect(0, 0, getSize().width, getSize().height); g.setColor(Color.black); g.drawRect(Math.min(xs[0], xs[1]), Math.min(ys[0], ys[1]), Math.abs(xs[0]-xs[1]), Math.abs(ys[0]-ys[1])); break; } } public void mousePressed(MouseEvent e) { } public void mouseReleased(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } class ColorDialog extends Dialog implements ActionListener, ItemListener { Canvas swatch; Choice choice; Scrollbar[] bars; TextField[] tfs; final String[] colorNames = { "Red", "Blue", "Green", "Yellow", "Orange", "Cyan", "Magenta", "White", "Black" }; final Color[] colors = { Color.red, Color.blue, Color.green, Color.yellow, Color.orange, Color.cyan, Color.magenta, Color.white, Color.black }; 512
ColorDialog(Frame parent)
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Zeichenoperationen
{ super(parent, true); setSize(200, 100); Panel panel = new Panel(); choice = new Choice(); for (int i=0; i
Clipping Wenn Sie eine Form als Schablone verwenden, dient die Form als Ausschnitt – alle Pixel außerhalb dieses Clipping-Bereichs werden durch nachfolgende Zeichenoperationen nicht modifiziert. Die Festlegung einer Form als Clipping-Bereich erfolgt durch einen Aufruf der Methode clip() der Klasse Graphics2D, wobei die ClippingForm der Methode als Argument .bergeben wird. 513
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Abbildung 16.4 zeigt ein eingef$rbtes Rechteck mit einem kreisfGrmigen ClippingBereich. Der Code, der die Darstellung in Abbildung 16.4 generiert, ist nachfolgend in Listing 16.5 zu sehen.
Abb. 16.4 Kreisf
Listing 16.5 ClipDemo.java
import java.awt.*; import java.awt.geom.*; public class ClipDemo extends Frame { public static void main(String[] args) { (new ClipDemo()).setVisible(true); } public ClipDemo() { setSize(250, 250); } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; Ellipse2D e = new Ellipse2D.Float(10, 10, 200, 200); g2d.clip(e); g2d.fillRect(40, 60, 500, 500); } } Kompilieren Sie die Anwendung ClipDemo und f.hren Sie sie aus. Verwenden Sie hierzu die folgenden beiden Befehle: C:\javadevhdbk\ch16\javac ClipDemo.java C:\javadevhdbk\ch16\java ClipDemo
514
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Zeichenoperationen
Transformieren Das Zeichenmodell des JDK 1.1 verwendete ein streng festgelegtes Koordinatensystem. Der Ursprung befand sich immer in der oberen linken Ecke einer Komponente, wobei die x-Achse in horizontaler Richtung nach rechts und die y-Achse in vertikaler Richtung nach unten verlief. Die 2D-API erlaubt es Ihnen, Ihr Koordinatensystem zu transformieren. Die Klasse Graphics2D stellt eine Methode namens setTransform() bereit, welche als Argument eine Objektinstanz der Klasse java.awt.geom.AffineTransform erwartet. Wenn Sie mit den mathematischen Grundlagen der (affinen) Koordinatentransformationen vertraut sind, kGnnen Sie unter Verwendung der Konstruktoren der Klasse AffineTransform sehr trickreiche Effekte produzieren. Die Klasse umfasst auch eine Reihe von statischen Methoden, die die Ausf.hrung der folgenden .blichen Transformationen erheblich vereinfachen: Rotation – beh$lt den Ursprung bei, w$hrend die Achsen verschoben werden, so dass sie in beliebige Richtungen weisen kGnnen. (Dabei bleibt der rechte Winkel zwischen den Achsen erhalten.) Skalierung – $ndert die Maßeinheit, so dass beispielsweise eine Breite von 100 f.r ein Quantum von 100 Einheiten beliebiger GrGße und nicht unbedingt f.r 100 Pixel steht. Die horizontalen und vertikalen Skalen kGnnen unabh$ngig voneinander angepasst werden. Translation – verschiebt den Ursprung. Scherung – ver$ndert den Winkel zwischen den Achsen, so dass sie nicht mehr rechtwinklig zueinander verlaufen. Die statischen Methoden von AffineTransform, welche diese Transformationen leisten, haben folgende Syntax: getRotateInstance(double theta) getRotateInstance(double theta, double x, double y) getScaleInstance(double scaleX, double scaleY) getTranslateInstance(double translateX, double translateY) getShearInstance(double shearX, double shearY) Die zweite Version von getRotateInstance() liefert als Ergebnis eine Transformation, die sich aus einer Translation und einer Rotation zusammensetzt. Die Abbildungen 16.5 bis 16.9 zeigen Frames (Fenster), deren paint()-Methoden allesamt fillRect (50, 50, 150, 250) aufrufen. Abbildung 16.5 zeigt das nicht transformierte Rechteck. Die anderen Abbildungen zeigen das Rechteck, wie es sich nach der Ausf.hrung einer Transformaton darstellt. Die Abbildungen illustrieren die Aus515
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
wirkungen einer Transformation des Koordinatensystems auf das Erscheinungsbild des Rechtecks. Alle Frames wurden mit Hilfe der Anwendung TransformLab angezeigt, deren Code nachfolgend (in Listing 16.5) aufgef.hrt ist.
Abb. 16.5 Rechteck ohne Transformation
Abb. 16.6 Rechteck nach Rotation
516
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Zeichenoperationen
Abb. 16.7 Rechteck nach Skalierung
Abb. 16.8 Rechteck nach Translation
517
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Abb. 16.9 Rechteck nach Sicherung
Listing 16.6
import java.awt.*; import java.awt.geom.*;
TransformLab.java
public class TransformLab extends Frame { private int nth; private final static String[] info = { "No Transform (50, 50, 100, 50)", "getRotateInstance(Math.PI/4, 50, 50)", "getScaleInstance(2, 3)", "getTranslateInstance(50, 100)", "getShearInstance(.1, .5)" }; public static void main(String[] args) { for (int i=0; i
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
public void paint(Graphics g) { AffineTransform atrans = null; Graphics2D g2d = (Graphics2D)g; switch (nth) { case 1: atrans = AffineTransform.getRotateInstance(Math.PI/4, 50, 50); break; case 2: atrans = AffineTransform.getScaleInstance(2, 3); break; case 3: atrans = AffineTransform.getTranslateInstance(50, 100); break; case 4: atrans = AffineTransform.getShearInstance(.1, .5); break; } if (atrans != null) g2d.setTransform(atrans); g2d.fillRect(50, 50, 100, 50); } } Kompilieren Sie die Anwendung TransformLab und f.hren Sie sie aus. Verwenden Sie hierzu die folgenden beiden Befehle: C:\javadevhdbk\ch16\javac TransformLab.java C:\javadevhdbk\ch16\java TransformLab
Verallgemeinerte Pfade f,r selbstdefinierte Kurven Die bisher erGrterten Formen waren vordefiniert. Jede Klasse repr$sentierte eine andere Art von Kurve. Wenn Sie eine Kurve zeichnen mGchten, die nicht durch eine Standardform repr$sentiert ist, m.ssen Sie die Klasse java.awt.geom.GeneralPathverwenden, um eine selbstdefinierte Kurve zu erzeugen. Nach dem Erzeugen eines GeneralPath-Objekts kGnnen Sie es wie jede andere Form (z.B. Ellipse2D oder RoundRectangle2D) manipulieren, d.h. zeichnen, f.llen oder als Clipping-Bereich verwenden. 519
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Definieren einer Form Die Klasse GeneralPath besitzt mehrere Konstruktoren, deren einfachste Version .berhaupt keine Argumente erwartet. Nachdem Sie eine Objektinstanz erzeugt haben, steht eine Vielzahl von Methoden zur Festlegung einer Form zur Verf.gung. Der gebr$uchlichste Ansatz zur Festlegung einer Form orientiert sich an einer Analogie zum freih$ndigen Zeichnen. Beim Zeichnen bewegen Sie Ihren Stift in Richtung eines bestimmten Zielpunktes auf dem Papier – Sie zeichnen eine gerade Linie oder eine Kurve und anschließend wiederholen Sie diesen Vorgang mit einem anderen Zielpunkt. Bei Verwendung eines GeneralPath-Objekts simulieren Sie diese Aktivit$t mit Hilfe der folgenden Methoden: public void moveTo(float x, float y) – verschiebt den Zeichenstift an die angegebene Position. public void lineTo(float x, float y) – erweitert den Linienzug (bzw. die Kurve) durch Zeichnen eines Liniensegments bis zu dem angegebenen Punkt. public void closePath() – erweitert den Linienzug (bzw. die Kurve) durch Zeichnen eines Liniensegments bis zu dem Punkt, der durch den letzten Aufruf von moveTo() bezeichnet wurde. Die Klasse GeneralPath umfasst außer den hier aufgelisteten noch zahlreiche andere Methoden. Allerdings l$sst sich allein mit moveTo(), lineTo() und closePath() schon allerhand erreichen, so dass an dieser Stelle einige Beispiele untersucht werden sollen. Die nachfolgend aufgelistete Implementierung der Methode paint() erzeugt ein GeneralPath-Objekt, das ein gleichschenkliges (aber nicht gleichseitiges) Dreieck repr$sentiert. public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; GeneralPath path = new GeneralPath(); path.moveTo(100, 100); path.lineTo(200, 100); path.lineTo(150, 150); path.closePath(); g2d.draw(path); }
Hinweis
520
(Anmerkung des
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
Transformieren eines verallgemeinerten Pfades Die Klasse GeneralPath besitzt eine Methode transform(), die als Argument eine Objektinstanz der Klasse AffineTransform akzeptiert. Mit dieser Methode kGnnen Sie einen Pfad erzeugen, der in der Lage ist, ein regelm$ßiges Vieleck (Polygon)mit einer beliebigen Anzahl von Seiten zu zeichnen. Das n$chste Beispiel zeigt, wie dies zu bewerkstelligen ist. Es w$re f.r diesen Zweck ideal, wenn sich die Klasse GeneralPath erweitern ließe. Die Subklasse kGnnte z.B. den Namen PolygonPfad tragen und ihre Konstruktoren kGnnten die gew.nschte Anzahl von Seiten als Argument .bernehmen. Leider ist GeneralPath eine finale Klasse, so dass eine Erweiterung nicht in Frage kommt. Es bleibt also nichts anderes .brig, als eine Methode zu schreiben, die ein geeignetes GeneralPath-Objekt erzeugt und zur.ckgibt. Als Argumente der Methode werden die Anzahl der Polygonseiten (sides), der Radius (radius) und der Mittelpunkt (center) .bergeben: GeneralPath makePoly(int nSides, float radius, float centerX,float centerY) {...} Die neue Methode wird sich der Methoden moveTo() und lineTo() bedienen, um das gew.nschte Polygon zu zeichnen. Die geometrischen Berechnungen sind unkompliziert, sofern das Polygon seinen Mittelpunkt im Ursprung haben kann. Letzteres l$sst sich mit Hilfe einer affinen Transformation erreichen. Solche Transformationen wurden bereits im Kontext der Klasse Graphics2D errGtert. Der folgende Code demonstriert, wie ein Pfad durch Anwendung einer Transformation seinen Mittelpunkt hin zum Ursprung des Koordinatensystems (centerX, centerY) verlagert: AffineTransform atrans = AffineTransform.getTranslateInstance(centerX, centerY); thePath.transform(centerX, centerY); Je zwei benachbarte Eckpunkte des Polygons spannen einen Winkel von 360/AnzahlPunkte Grad auf. Es entspricht dem Standardverfahren, Winkel gegen den Uhrzeigersinn zu messen. Wenn sich der erste Eckpunkt des Polygons bei 0 Grad (also genau rechts vom Mittelpunkt) befindet, spannt der n-te Eckpunkt einen Winkel von n*360/AnzahlPunkte Grad auf. Die Koordinaten des Punktes lassen sich einfach bestimmen. Wenn der Winkel zur x-Achse mit theta bezeichnet wird, ergeben sich als Formeln f.r die Koordinaten x = radius*cos(theta) und y = radius*sin(theta). Die Methode makePoly() hat die Aufgabe, auf den ersten Eckpunkt zu positionieren, anschließend zu jedem nachfolgenden Eckpunkt eine Verbindungslinie zu zeichnen und am Ende den Pfad zu schließen. Das vollst$ndige Listing der Methode ist nachfolgend zu sehen.
521
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
GeneralPath makePoly(int nSides, float radius, float centerX, float centerY) { GeneralPath path = new GeneralPath(); // Positionieren auf ersten Eckpunkt. path.moveTo(radius, 0); // Linienzug zu :brigen Eckpunkten zeichnen. float deltaTheta = (float)(2*Math.PI/nSides); float theta = deltaTheta; for (int i=1; i
522
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
Abb. 16.10 Verallgemeinerter Pfad f1r Polygone
Das vollst$ndige Listing des Programms, welches die Abbildung 16.10 generiert hat, ist in Listing 16.7 zu sehen. import java.awt.*; import java.awt.event.*; import java.awt.geom.*; public class Poly extends Frame { GeneralPath makePoly(int nSides, float radius, float centerX, float centerY) { GeneralPath path = new GeneralPath(); // Positionieren auf ersten Eckpunkt. path.moveTo(radius, 0); // Linienzug zu :brigen Eckpunkten zeichnen. float deltaTheta = (float)(2*Math.PI/nSides); float theta = deltaTheta; for (int i=1; i
Listing 16.7 Poly.java
523
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
// Polygonmittelpunkt. AffineTransform atrans = AffineTransform.getTranslateInstance(centerX, centerY); path.transform(atrans); return path; } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D)g; g2d.draw(makePoly( 5, 70, 100, 100)); g2d.draw(makePoly( 6, 70, 300, 100)); g2d.draw(makePoly( 8, 70, 100, 300)); g2d.draw(makePoly(10, 70, 300, 300)); } public static void main(String[] args) { Poly p = new Poly(); p.setSize(400, 400); p.setVisible(true); } } Kompilieren Sie die Anwendung Poly und f.hren Sie sie aus. Verwenden Sie hierzu die folgenden beiden Befehle: C:\javadevhdbk\ch16\javac Poly.java C:\javadevhdbk\ch16\java Poly
Zeichnen einer BDzier-Kurve Eine Bzier-Kurve ist eine gegl$ttete Kurve, die durch zwei Endpunkte und zwei St.tzpunkte definiert wird. Die St.tzpunkte legen den Kurvenverlauf fest. Die Kurvenlinie verl$uft in der N$he der St.tzpunkte, geht jedoch in der Regel nicht durch sie hindurch. Die genaue Wirkung der St.tzpunkte l$sst sich schwierig beschreiben, ist aber nach dem Studium einer hinreichenden Anzahl von Beispielen intuitiv leicht zu erfassen. Das Programm BezLab zeichnet eine Bzier-Kurve und ermGglicht es Ihnen, an den End- und St.tzpunkten zu ziehen, um die Wirkung auf die Kurvengestalt auszuprobieren. Abbildung 16.11 zeigt BezLab im Anfangsstadium. Die paint()-Methode von BezLab erzeugt einen verallgemeinerten Pfad und ruft anschließend moveTo() und curveTo() auf, denen sie jeweils das Pfadobjekt als Argument .bergibt. Die Methode moveTo() ist Ihnen bereits bekannt. Die Methode curveTo() besitzt die folgende Syntax:
524
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
public void curveTo(float control0x, float control0y, float control1x, float control1y, float endx, float endy) Die Argumente bezeichnen zwei St.tzpunkte und einen Endpunkt. Der andere Endpunkt ist die aktuelle Position des verallgemeinerten Pfades.
Abb. 16.11 Das Programm BezLab in seinem Anfangsstadium
BezLab verwaltet zwei Arrays mit jeweils vier Gleitkommawerten. Die Arrays haben die Namen xs und ys und enthalten die Koordinaten des ersten Endpunkts, des ersten und zweiten St.tzpunkts und des zweiten Endpunkts. Die paint()-Methode von BezLab macht in folgender Weise Gebrauch von diesen Arrays: public void paint(Graphics g) { // Alle Punkte einf[rben. for (int i=0; i<4; i++) { if (i==0 || i==3) g.setColor(Color.blue); else g.setColor(Color.cyan); g.fillOval(xs[i]-6, ys[i]-6, 12, 12); } // Zeichnen der Kurve.
525
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Graphics2D g2d = (Graphics2D)g; g2d.setColor(Color.black); GeneralPath path = new GeneralPath(); path.moveTo(xs[0], ys[0]); path.curveTo(xs[1], ys[1], xs[2], ys[2], xs[3], ys[3]); g2d.draw(path); } Das vollst$ndige Listing f.r BezLab ist weiter unten aufgef.hrt (Listing 16.8). Experimentieren Sie mit den Auswirkungen, die das Verschieben der St.tz- und Endpunkte auf die Gestalt der Bzier-Kurve hat. Listing 16.8 BezLab.java
import java.awt.*; import java.awt.event.*; import java.awt.geom.*; class BezLab extends Frame implements MouseListener, MouseMotionListener { private int[] xs = { 75, 150, 300, 375 }; private int[] ys = { 250, 100, 350, 250 }; private int dragIndex = NOT_DRAGGING; private final static int NEIGHBORHOOD = 15; private final static int NOT_DRAGGING = -1; public static void main(String[] args) { (new BezLab()).setVisible(true); } BezLab() { setSize(500, 450); addMouseListener(this); addMouseMotionListener(this); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) {System.exit(0);} }); } public void paint(Graphics g) { // Alle Punkte einf[rben. for (int i=0; i<4; i++) {
526
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
if (i==0 || i==3) g.setColor(Color.blue); else g.setColor(Color.cyan); g.fillOval(xs[i]-6, ys[i]-6, 12, 12); } // Zeichnen der Kurve. Graphics2D g2d = (Graphics2D)g; g2d.setColor(Color.black); GeneralPath path = new GeneralPath(); path.moveTo(xs[0], ys[0]); path.curveTo(xs[1], ys[1], xs[2], ys[2], xs[3], ys[3]); g2d.draw(path); } public void mousePressed(MouseEvent e) { // Index des Punktes ermitteln, der gerade // verschoben wird. dragIndex = NOT_DRAGGING; int minDistance = Integer.MAX_VALUE; int indexOfClosestPoint = -1; for (int i=0; i<4; i++) { int deltaX = xs[i] - e.getX(); int deltaY = ys[i] - e.getY(); int distance = (int)(Math.sqrt(deltaX*deltaX + deltaY*deltaY)); if (distance < minDistance) { minDistance = distance; indexOfClosestPoint = i; } } // Mausklick muss nahe genug erfolgen. if (minDistance > NEIGHBORHOOD) return; dragIndex = indexOfClosestPoint; } public void mouseReleased(MouseEvent e) { if (dragIndex == NOT_DRAGGING) return; xs[dragIndex] = e.getX(); ys[dragIndex] = e.getY(); dragIndex = NOT_DRAGGING; repaint();
527
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
} public void mouseDragged(MouseEvent e) { if (dragIndex == NOT_DRAGGING) return; xs[dragIndex] = e.getX(); ys[dragIndex] = e.getY(); repaint(); } public void mouseClicked(MouseEvent e) { } public void mouseEntered(MouseEvent e) { } public void mouseExited(MouseEvent e) { } public void mouseMoved(MouseEvent e) { } } Kompilieren Sie die Anwendung BezLab und f.hren Sie sie aus. Verwenden Sie hierzu die folgenden beiden Befehle: C:\javadevhdbk\ch16\javac BezLab.java C:\javadevhdbk\ch16\java BezLab
Zeichnen von Fraktalen Um an einem detailreicheren Beispiel den extensiven Gebrauch von verallgemeinerten Pfaden zu demonstrieren, wird im Folgenden ein Programm namens FracLabTriangle entwickelt, das ein verschlungenes dreieckbasiertes Fraktal zeichnet.
Hinweis
Der Begriff Fraktal (oder Fractal) wurde von Benoit Mandelbrot gepr$gt, um Kurven extremer Komplexit$t zu beschreiben, deren Dimension grGßer als 2 ist. Jedoch sind die Kurven, um die es sich hierbei handelt, definitiv nicht dreidimensional. Mandelbrot schlug vor, nicht-ganze Zahlen zur Charakterisierung der Dimension derartig komplexer Kurven zu verwenden. Fractal ist eine Abk.rzung f.r fractional dimension. Eine Methode zur Erzeugung eines Fraktals w$re es, mit einer einfachen Form wie einem Dreieck zu beginnen. Die Form sollte aus Liniensegmenten bestehen. Jedes Liniensegment wird einer Transformation unterworfen, um es in eine Kette miteinander verbundener Liniensegmente zu verwandeln, die an demselben Punkt beginnt, an dem das urspr.ngliche Segment begann, und an demselben Punkt endet, an dem auch das urspr.ngliche Segment endete. Kurz gesagt: Jedes Liniensegment wird in einen etwas komplizierteren Pfad transformiert. Dieser Prozess wiederholt sich f.r die neuen (kleineren) Segmente. Ein Fraktal ist das Ergebnis der unendlich oft wiederholten Durchf.hrung dieses Vorgangs. In der Praxis gen.gen vier oder f.nf Iterationen, um ein bereits sehr kompliziertes Bild zu zeichnen, das sich gut daf.r eignet, die anmutige Fremdartigkeit von Fraktalen zu illustrieren.
528
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
FracLabTriangle beginnt mit einem Dreieck (siehe Abbildung 16.12). Die Transformation entfernt das mittlere Drittel eines jeden Liniensegments und ersetzt es durch den $ußeren Linienzug eines angrenzenden gleichseitigen Dreiecks. Die Transformation ist in Worten schwierig zu beschreiben, kann aber leicht durch einen Blick auf ein Bild verstanden werden. Abbildung 16.13 zeigt das Ergebnis der einmaligen Anwendung der Transformation auf die einzelnen Segmente des urspr.nglichen Dreiecks Im n$chsten Schritt wird dieselbe Transformation auf jedes der neu erzeugten, kleineren Liniensegemente angewendet, was zum Ergebnis in Abbildung 16.14 f.hrt. Nach einer weiteren Iteration beginnt die Form schon recht verschlungen zu wirken, wie Sie der Abbildung 16.15 entnehmen kGnnen. Nach einer vierten Iteration (siehe Abbildung 16.16) verschwinden die einzelnen „Schlingen“ schon beinahe zwischen den Pixeln.
Abb. 16.12 FracLabTriangle vor der ersten Iteration
529
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Abb. 16.13 FracLabTriangle nach der ersten Iteration
Abb. 16.14 FracLabTriangle nach zwei Iterationen
530
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
Abb. 16.15 FracLabTriangle nach drei Iterationen
Abb. 16.16 FracLabTriangle nach vier Iterationen
531
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
FracLabTriangle unterst.tzt bis zu f.nf Iterationen, doch die f.nfte Iteration benGtigt bereits sehr viel Zeit und produziert recht kleine „Schlingen“, die nur auf einem sehr großen Bildschirm gew.rdigt werden kGnnen. FracLabTriangle besteht aus vier Klassen: FracLabTriangle – die Hauptklasse der Anwendung. Sie erweitert die Klasse Frame und enth$lt ein Panel f.r Steuerelemente. FracCanvas – ein Container vom Typ Canvas, der im Hauptfenster enthalten ist. FracCanvas beinhaltet alles NGtige zum Zeichnen des Fraktals. Xform – repr$sentiert eine Transformation. FracCanvas verwendet eine Xform-Instanz als Hilfsmittel zur Konvertierung eines Liniensegments w$hrend einer Iteration. Ein Xform-Objekt enth$lt eine gewisse Anzahl von XformStep-Werten. XformStep – repr$sentiert einen einzelnen Schritt einer Transformation. Die hier verwendete Transformation ersetzt ein Liniensegment durch vier andere Liniensegmente. Dementsprechend enth$lt das zugehGrige Xform-Objekt vier Werte des Typs XformStep. Die Anwendung l$sst sich am einfachsten verstehen, indem mit den grundlegenden Elementen – Xform und XformStep – begonnen wird. Die Klasse Xform kapselt Anweisungen zur Transformation eines Liniensegments – unter der Voraussetzung, dass das Liniensegment von (0,0) nach (1,0) f.hrt. Dies ist eine sehr strenge Annahme. Sofern diese Annahme zul$ssig ist, l$sst sich das Programm recht einfach schreiben. (Wenn diese Annahme unzul$ssig w$re, w.rde sich die Programmierung als ein Albtraum herausstellen.) Was bef$higt nun zu einer derart radikalen Annahme? Offensichtlich muss das Programm darauf vorbereitet sein, jedes beliebige Liniensegment unabh$ngig von dessen Start- und Endpunkt zu transformieren. Ein Segment kGnnte beispielsweise die Punkte (14.4141,75) und (15.5115,76.1) miteinander verbinden.
532
1
Translation des Ursprungs des Koordinatensystems zum Startpunkt des Segments.
2
Skalierung des Systems, so dass die Entfernung zwischen den beiden Punkten den Wert 1 (gem$ß dem Maßstab des Systems) besitzt.
3
Rotation des Systems, so dass der Endpunkt auf einer horizontalen Linie rechts vom Startpunkt zu liegen kommt.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
Nun bleibt nur noch das Problem zu lGsen, wie ein Liniensegment zu transformieren ist, das die Punkte (0,0) und (1,0) miteinander verbindet. Abbildung 16.17 illustriert die erforderliche Transformation.
Abb. 16.17
Vorher:
Die Basistransformation
Nachher:
Der Abbildung 16.17 ist zu entnehmen, dass ein Liniensegment, welches die Punkte (0,0) und (1,0) verbindet, in die folgenden vier Liniensegmente transformiert werden muss: das Segment zwischen (0,0) und (1/3,0) das Segment zwischen (1/3,0) und (1/2,v3/6) das Segment zwischen (1/2,v3/6) und (2/3,0) das Segment zwischen (2/3,0) und (1,0) Da nun die Beschreibung der Transformation in Worten gelungen ist, kann die Entwicklung einer entsprechenden Klasse angegangen werden. Tats$chlich handelt es sich um zwei Klassen, Xform und XformStep. Die Klasse XformStep repr$sentiert einen einzelnen Eintrag der oben gezeigten Liste (von Segmentbeschreibungen). Die einzige Aufgabe dieser Klasse besteht darin, die Koordinaten des n$chsten Punktes entlang des Linienzugs zu kapseln: float float
x; y;
Außer diesen Instanzvariablen enth$lt die Klasse einen Konstruktor, einige nummerische Konstanten und einen statischen Array mit den Schritten, die die Transformation beschreiben. public class XformStep { private final static float private final static float
Listing 16.9
ONE_HALF = 1f / 2f; ONE_THIRD = 1f / 3f;
XformStep.java
533
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
private final static float ROOT_3_OVER_6 = (float)(Math.sqrt(3)) / 6; public final static XformStep[] TRIANGLE_STEPS = { new XformStep(ONE_THIRD, 0), new XformStep(ONE_HALF, ROOT_3_OVER_6), new XformStep(2*ONE_THIRD, 0), new XformStep(1, 0) }; float float
x; y;
public XformStep(float x, float y) { this.x = x; this.y = y; } } Die Klasse Xform ist nicht mehr als eine Kollektion von Instanzen des Typs XformStep. Diese Kollektion ist als ein Array implementiert. Es gibt einen Konstruktor, der als Eingabe einen Array mit XformStep-Werten erwartet (siehe Listing 16.10). Listing 16.10 Xform.java
public class Xform { public final static Xform TRIANGLE_XFORM = new Xform(XformStep.TRIANGLE_STEPS); public XformStep[]
steps;
public Xform(XformStep[] steps) { this.steps = steps; } } Das Problem der Datenrepr$sentation ist nun gelGst. Der n$chste Schritt besteht darin, die Klasse FracCanvas zu entwickeln. Aufgabe dieser Klasse ist es, ein Fraktalobjekt unter Verwendung einer Reihe von Parametern zu zeichnen: Anfangsform Transformation, die auf jedes Liniensegment anzuwenden ist Anzahl von Iterationen
534
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
Das Herzst.ck von FracCanvas ist eine Methode namens recurse(), welche die folgende Syntax besitzt: public void recurse(int depth, float x0, float y0, float x1, float y1) Diese Methode behandelt das Segment, das die Punkte (x0,y0) und (x1,y1) miteinander verbindet. „Behandelt“ kann hier einfach das Zeichnen des Segments bedeuten, aber auch das Expandieren des Segments durch Anwendung der Transformation. Es kann sogar eine wiederholte Anwendung der Transformation bedeuten. Das Argument depth teilt der Methode mit, wie oft sie iterieren soll. Die am Anfang dieses Abschnitts gezeigten Abbildungen wurden mit unterschiedlichen Werten f.r depth erzeugt: Abbildung 16.14 zeigt das Ergebnis bei einem Wert von 2 f.r depth, w$hrend Abbbildung 16.16 das Ergebnis bei einem Wert von 4 f.r depth zeigt. Wie Sie sich im Hinblick auf den Namen denken kGnnen, ist recurse() eine rekursive Methode. Falls 0 f.r depth .bergeben wird, zeichnet die Methode einfach ein Liniensegment, das die beiden Endpunkte miteinander verbindet. Hat depth einen Wert grGßer als Null (0), passt die Methode das Koordinatensystem so an, dass das Segment von (0,0) bis (1,0) reicht. Anschließend ermittelt die Methode die neuen Liniensegmente (es sind jeweils vier). F.r jedes dieser neuen Segmente ruft sich die Methode recurse()selbst auf, wobei der Wert von depth jeweils um 1 gesenkt wird. Die Methode recurse() hat folgendes Aussehen: private void recurse(int depth, float x0, float y0, float x1, float y1) { XformStep[] steps; XformStep step; int nPieces; float x, y; float nextX, nextY; float range, bearing; // Test auf Ende der Rekursion. if (depth == 0) { path.moveTo(x0, y0); path.lineTo(x1, y1); g.setStroke(thinStroke); g.draw(path); return; } // Rekursiver Aufruf f:r jedes Liniensegment. depth-; normalize(x0, y0, x1, y1); x = y = 0f; steps = xform.steps; for (int i=0; i<steps.length; i++) // F:r jeden Schritt ...
535
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
{ step = steps[i]; nextX = step.x; nextY = step.y; recurse(depth, x, y, nextX, nextY); x = nextX; y = nextY; } // Tempor[re Transformation r:ckg[ngig machen. unNormalize(); } Die Methode normalize() ver$ndert das Koordinatensystem derart, dass der Startpunkt bei (0,0) und der Endpunkt bei (1,0) zu liegen kommt. Die Methode unNormalize() macht die Wirkung des letzten Aufrufs von normalize() r.ckg$ngig. Das R.ckg$ngigmachen einer Normalisierungsoperation ist kein trivialer Vorgang. Das Programm verwaltet drei Stapel namens scaleStack, rotationStack und translationStack. Diese Datenstrukturen speichern die Informationen, welche die verschiedenen Operationen beschreiben. Jeder Stapel wird durch jeweils zwei Methoden „bedient“. Der Skalierungsstapel (scaleStack) speichert Skalierungsfaktoren und wird durch die Methoden scale() und unScale() gehandhabt: private void scale(double scaleBy) { g.scale(scaleBy, scaleBy); scaleStack.push(new Double(scaleBy)); } private void unScale() { Double d = (Double)scaleStack.pop(); double factor = 1 / d.doubleValue(); g.scale(factor, factor); } Der Rotationsstapel (rotationStack) speichert WinkelgrGßen (in Grad) und wird durch die Methoden rotate() und unRotate() manipuliert: private void rotate(double radians) { g.rotate(radians); rotationStack.push(new Double(radians)); } private void unRotate() { Double d = (Double)rotationStack.pop(); g.rotate(-(d.doubleValue())); } 536
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
Der Translationsstapel (translationStack) speichert die Translationsfaktoren (x- und y-Koordinaten) und wird durch die Methoden translate() und unTranslate() manipuliert: private void translate(double x, double y) { g.translate(x, y); translationStack.push(new Double(x)); translationStack.push(new Double(y)); } private void unTranslate() { Double y = (Double)translationStack.pop(); Double x = (Double)translationStack.pop(); g.translate(-(x.doubleValue()), -(y.doubleValue())); } Mit dieser „Infrastruktur“ als Grundlage ist es nicht schwierig, die Normalisierungsmethode zu programmieren. Es gilt, zwei Spezialf$lle zu beachten, um nummerische Singularit$ten zu vermeiden. Ansonsten ist der Code unkompliziert: private void normalize(float x0, float y0, float x1, float y1) { // Translation translate(x0, y0); // Verschieben des // Ursprungs // Rotation double deltaX = x1 - x0; double deltaY = y1 - y0; if (deltaX == 0) // Spezialfall 1 { if (y0 < y1) rotate(Math.PI / 2); else rotate(3 * Math.PI / 2); } else if (deltaY == 0) // Spezialfall 2 { if (x0 > x1) rotate(Math.PI); else rotate(0); } else { double slope = deltaY / deltaX; double theta = Math.atan(slope); rotate(theta);
537
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
} // Skalierung double length = Math.sqrt(deltaX*deltaX + deltaY*deltaY); scale(length); } Nach diesen Vorbeitungen erweist sich die Methode unNormalize() als problemlos: private void unNormalize() { unScale(); unRotate(); unTranslate(); } Damit ist fast alles behandelt, was die Klasse FracCanvas ausmacht. Es wird nur noch eine paint()-Methode benGtigt, um die Sache abzuschließen. Diese Methode verschiebt zuerst den Ursprung zum Mittelpunkt des Canvas-Containers und skaliert dann das Ganze derart, dass das kleinere der beiden Containermaße (Breite und HGhe) zwischen den beiden Werten -1 und +1 zu liegen kommt. Anschließend erzeugt die Methode eine Anfangsform und ruft dann recurse() auf. Der Code der Methode paint() ist nachfolgend aufgelistet: public void paint(Graphics g1d) { g = (Graphics2D)g1d; g.setColor(Color.blue); g.setStroke(new BasicStroke(0)); scaleStack = new Stack(); rotationStack = new Stack(); translationStack = new Stack(); path = new GeneralPath(); centerAndScale2x2(); g.setColor(Color.blue); for (int i=0; i
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
int mindim = Math.min(size.width, size.height); scale(mindim/2); } Der vollst$ndige Code von FracCanvas ist nachfolgend in Listing 16.11 zu sehen, gefolgt vom Code des Programms FracLabTriangle (Listing 16.12). F.r dieses Beispiel werden auch XformStep.java (Listing 16.9) und Xform.java (Listing 16.10) benGtigt! import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.util.*;
Listing 16.11 FracCanvas.java
public class FracCanvas extends Canvas { private Xform xform = Xform.TRIANGLE_XFORM; private int depth = 0; private Stack scaleStack; private Stack rotationStack; private Stack translationStack; private Graphics2D g; private GeneralPath path; private BasicStroke thinStroke; // //Diese Koordinaten definieren die Anfangsform //innerhalb eines Raums mit dem Ursprung //im Mittelpunkt. Die kleinere der beiden //Dimensionen erstreckt sich von -1 bis +1. // private final static float ROOT_ONE_THIRD = (float)(Math.sqrt(1d/3d)); private final static float[] INITIAL_XS = { -ROOT_ONE_THIRD, 0f, +ROOT_ONE_THIRD, -ROOT_ONE_THIRD }; private final static float[] INITIAL_YS = { -.5f, +.5f, -.5f , -.5f }; public FracCanvas() { setBackground(Color.white); thinStroke = new BasicStroke(0); } public Dimension getPreferredSize() {
539
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
return new Dimension(700, 700); } void go(int depth) { this.depth = depth; repaint(); }
540
public void paint(Graphics g1d) { g = (Graphics2D)g1d; g.setColor(Color.blue); g.setStroke(new BasicStroke(0)); scaleStack = new Stack(); rotationStack = new Stack(); translationStack = new Stack(); path = new GeneralPath(); centerAndScale2x2(); g.setColor(Color.blue); for (int i=0; i
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
steps = xform.steps; for (int i=0; i<steps.length; i++) // F:r jeden Schritt ... { step = steps[i]; nextX = step.x; nextY = step.y; recurse(depth, x, y, nextX, nextY); x = nextX; y = nextY; } // Tempor[re Transformation r:ckg[ngig machen. unNormalize(); } // // Translation um (x0, y0). // Per Rotation wird der Endpunkt (x1, x1) zu // einer Position rechts vom Startpunkt verschoben. // Der Abstand zwischen beiden Punkten wird auf // 1 Maßeinheit skaliert. // private void normalize(float x0, float y0, float x1, float y1) { // Translation translate(x0, y0); // Verschieben des //Ursprungs // Rotation double deltaX = x1 - x0; double deltaY = y1 - y0; if (deltaX == 0) // Spezialfall 1 { if (y0 < y1) rotate(Math.PI / 2); else rotate(3 * Math.PI / 2); } else if (deltaY == 0) // Spezialfall 2 { if (x0 > x1) rotate(Math.PI); else rotate(0); } else { double slope = deltaY / deltaX; double theta = Math.atan(slope); rotate(theta);
541
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
} // Skalierung double length = Math.sqrt(deltaX*deltaX + deltaY*deltaY); scale(length); } private void unNormalize() { unScale(); unRotate(); unTranslate(); } private void scale(double scaleBy) { g.scale(scaleBy, scaleBy); scaleStack.push(new Double(scaleBy)); } private void rotate(double radians) { g.rotate(radians); rotationStack.push(new Double(radians)); } private void translate(double x, double y) { g.translate(x, y); translationStack.push(new Double(x)); translationStack.push(new Double(y)); } private void unScale() { Double d = (Double)scaleStack.pop(); double factor = 1 / d.doubleValue(); g.scale(factor, factor); } private void unRotate() { Double d = (Double)rotationStack.pop(); g.rotate(-(d.doubleValue())); }
542
private void unTranslate() { Double y = (Double)translationStack.pop(); Double x = (Double)translationStack.pop(); g.translate(-(x.doubleValue()),
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
-(y.doubleValue())); } // // Skalierung des grafischen Kontexts: // der Ursprung soll im Mittelpunkt des Containers // liegen, und die kleinere Dimension von -1 bis +1 // reichen. // public void centerAndScale2x2() { // Ursprung zentrieren. Dimension size = getSize(); translate(size.width/2, size.height/2); // Skalierung int mindim = Math.min(size.width, size.height); scale(mindim/2); } }
import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.util.Vector; public class FracLabTriangle extends Frame implements ActionListener { private FracCanvas fracCanvas; private Choice depthChoice; private Button goBtn, quitBtn; private final static int MIN_DEPTH = 0; private final static int MAX_DEPTH = 5; public static void main(String[] args) { FracLabTriangle lab = new FracLabTriangle(); lab.pack(); lab.setVisible(true); } FracLabTriangle() { fracCanvas = new FracCanvas(); add(fracCanvas, BorderLayout.CENTER); Panel controls = new Panel(); controls.add(new Label("Depth:")); depthChoice = new Choice(); for (int i=MIN_DEPTH; i<=MAX_DEPTH; i++) depthChoice.addItem(""+i);
Listing 16.12 FracLabTriangle.java
543
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
controls.add(depthChoice); goBtn = new Button("Go"); goBtn.addActionListener(this); controls.add(goBtn); quitBtn = new Button("Quit"); quitBtn.addActionListener(this); controls.add(quitBtn); add(controls, BorderLayout.NORTH); } public void actionPerformed(ActionEvent e) { { if (e.getSource() == quitBtn) System.exit(0); fracCanvas.go(depthChoice.getSelectedIndex()); } } Kompilieren Sie die Anwendung FracLabTriangle und f.hren Sie sie aus. Verwenden Sie hierzu die folgenden beiden Befehle: C:\javadevhdbk\ch16\javac FracLabTriangle.java C:\javadevhdbk\ch16\java FracLabTriangle
Erweitern des Dreieck-Fraktals Das Dreieck-Fraktal ist nur ein Vertreter einer großen Familie von Kurven. Theoretisch kGnnen Sie ein Fraktal erzeugen, indem Sie mit einer beliebigen Anfangsform beginnen und eine beliebige Transformation darauf anwenden. Das im Folgenden erl$uterte Beispielprogramm ist eine fortgeschrittene Version der FracLabTriangle-Anwendung. Diese erweiterte Version heißt schlicht FracLab und sollte in einem separaten Unterverzeichnis abgelegt werden, da sie eigene Versionen der Hilfsklassen besitzt. Das Programm $hnelt weitgehend FracLabTriangle, aber seine grafische Benutzeroberfl$che bietet zus$tzlich eine Choice-Komponente zur Auswahl anderer Fraktale. Die FracLab-Version pr$sentiert sowohl das Dreieck-Fraktal als auch zwei andere Fraktale. Die neuen Figuren beginnen beide mit einem Quadrat (siehe Abbildung 16.18). Das Programm verf.gt .ber zwei Transformationen, die auf diese Form angewendet werden kGnnen. Die Box genannte Transformation bewirkt eine Art Einzug des jeweiligen Liniensegments. Diese Transformation ist in Abbildung 16.19 illustriert. Abbildung 16.20 zeigt das Ergebnis einer viermaligen Anwendung der Transformation.
544
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
Die zweite Transformation, die auf das Anfangsquadrat angewendet werden kann, ist etwas trickreicher. Auch diese zweite Transformation bewirkt einen Einzug des jeweiligen Liniensegments. Zus$tzlich wird innerhalb des durch den Einzug freigewordenen Bereichs eine „Insel“ gezeichnet. Abbildung 16.21 zeigt die Wirkung der Transformation, die hier als Coast-Transformation bezeichnet wird. In Abbildung 16.22 ist das Ergebnis einer viermaligen Anwendung der Coast-Transformation zu sehen.
Abb. 16.18 Das Programm FracLab mit einem Quadrat als Anfangsform
545
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Abb. 16.19 Die Box-Transformation von FracLab
Abb. 16.20 Die Box-Transformation von FracLab nach vier Iterationen
546
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Verallgemeinerte Pfade fr selbstdefinierte Kurven
Abb. 16.21 Die Coast-Transformation von FracLab
Abb. 16.22 Die Coast-Transformation von FracLab nach vier Iterationen
547
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die 2-D-Grafik-API
Listing 16.13 FracLab.java
import java.awt.*; import java.awt.event.*; import java.awt.geom.*; import java.util.Vector; public class FracLab extends Frame implements ActionListener{ { private FracCanvas fracCanvas; private Choice fractalChoice; private Choice depthChoice; private Button goBtn, quitBtn; private final static int MIN_DEPTH = 0; private final static int MAX_DEPTH = 5; public static void main(String[] args) { FracLab lab = new FracLab(); lab.pack(); lab.setVisible(true); } FracLab() { fracCanvas = new FracCanvas(); add(fracCanvas, BorderLayout.CENTER); Panel controls = new Panel(); controls.add(new Label("Fractal:")); fractalChoice = new Choice(); for (int i=0; i
548
public void actionPerformed(ActionEvent e) { { if (e.getSource() == quitBtn)
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Ausblick
System.exit(0); fracCanvas.go(fractalChoice.getSelectedIndex(), depthChoice.getSelectedIndex()); } } Kompilieren Sie die Anwendung FracLab und f.hren Sie sie aus. Verwenden Sie hierzu die folgenden beiden Befehle: C:\javadevhdbk\ch16\FracLab\javac FracLab.java C:\javadevhdbk\ch16\FracLab\java FracLab
Ausblick Das n$chste Thema, dem Sie sich zuwenden, heißt JavaBeans. Kapitel 17: JavaBeans: Ein Jberblick bietet Ihnen eine
549
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBeans: Ein 8berblick
Was genau sind JavaBeans?
552
Die BlackBox – Sichtweise der JavaBeans
553
Umgebungen der JavaBeans
556
Das JavaBean Development Kit und die BeanBox-Testanwendung
560
Das java.beans-Paket
569
Ausblick
572
17
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBeans: Ein 4berblick
Was genau sind JavaBeans? Aus „Mastering JavaBeans“ von Laurence VanhelsuwN, ISBN 0-78212097-0, 799 Seiten, $49,99
JavaBeans sind wiederverwendbare, plattformneutrale Softwarekomponenten, deren Erscheinungsbild mit Entwicklungswerkzeugen ver$ndert werden kann. Diese etwas merkw.rdige Definition besteht aus zwei unabh$ngigen Teilen: In erster Linie sind JavaBeans plattformneutrale Softwarekomponenten. Punkt. Zweitens „wissen“ JavaBeans, durch welche Werkzeuge sie manipuliert werden kGnnen und sind mit diesen Werkzeugen kompatibel.
Hinweis
Wenn das Wort bean (englisch f.r Bohne) bei Ihnen keine Assoziationen zu wiederverwendbarer Software hervorruft, dann ersetzen Sie es im Geiste mit IC (integrated circuit) oder Lego (aber ohne die Konnotation mit Spielzeug). Der Standard der JavaBeans ist ein so genanntes Low-level-Komponentenmodell, das auf Java zugeschnitten wurde. Das Modell ist low-level, weil es weder auf das compound document noch auf die Architektur des application frameworks hinweist noch diese spezifiziert, wie andere Komponentenstandards dies tun (zum Beispiel CORBA, OpenDOC, Taligent etc.). JavaBeans konzentrieren sich in der aktuellen Version 1.01 ausschließlich auf die Schnittstelle, die ein Bestandteil der Java-Software pr$sentieren sollte, aber es wird nicht darauf eingegangen, wie solche Bestandteile kombiniert werden sollten oder kGnnten, um eine bestimmte Applikation zu erstellen. Allerdings wird spezifiziert, wie mehrere JavaBeans untereinander kommunizieren kGnnen, ohne dabei jedoch semantische Regeln in Bezug auf die kommunizierten Informationen oder in Bezug auf die Topologie beliebiger Kommunikationsnetze unter JavaBeans aufzustellen. W$hrend Sie erwarten, dass sich die Definition auf die Softwarekomponenten konzentriert, enth$lt diese auch Aspekte der Entwicklungswerkzeuge. Ohne die entsprechenden Werkzeuge w$re die Entwicklung von Software heutzutage undenkbar. Es werden zum Teil sehr m$chtige und vielseitige Werkzeuge benGtigt, um bestimmte Probleme komplexer Applikationen, die erstellt werden sollen, lGsen zu kGnnen. Dies bezieht sich heutzutage auf Werkzeuge mit einer betont visuell orientierten Elementen. JavaBeans sind explizit dahingehend entworfen worden, sich .ber ihre Werkzeuge bewusst zu sein. Deshalb wurden viele Teile der Schnittstelle zu JavaBeans speziell auf den modernen Entwickler zugeschnitten, der JavaBeans durch visuelle Interaktionen manipuliert. Diese beiden Aspekte der JavaBeans machen die Art aus, in der sie sich der Außenwelt pr$sentieren, sei es nun bei der Kommunikation untereinander w$hrend der Laufzeit oder w$hrend der Designphase, in der sie durch ein Werkzeug von einem Designer modifiziert werden.
552
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die BlackBox – Sichtweise der JavaBeans
Was sind JavaBeans in der Praxis? Bevor tiefer in die noch abstrakteren Bilder der JavaBeans, ihrer Attribute und Umgebungen eingestiegen wird, sollte zun$chst das Gesamtkonzept erkl$rt werden, woraus JavaBeans in der allt$glichen Praxis bestehen. Eine JavaBean ist $hnlich wie ein Java-Applet einfach eine Java-Klasse, die einem strikten Protokoll gehorcht (wie init(), start(), stop() und paint() bei einem Applet). Und wie ein Applet besteht auch eine JavaBean normalerweise aus einer Anzahl (unterst.tzender) Klassen und Ressourcedateien (Bilder, TGne, Daten etc.), die normalerweise in einem Java-Archive (.jar) zusammengefasst werden. Anders als Applets, die sich nur um eine einzelne, simple Java-API-Klasse k.mmern kGnnen, n$mlich java.applet.Applet, werden JavaBeans mit der Hilfe von ungef$hr 15 Klassen und sechs Schnittstellen implementiert, die einen Teil der neuen (aber alles andere als trivialen) java.beans-Core-API darstellen.
Die BlackBox – Sichtweise der JavaBeans Sie sollten sich JavaBeans in erster Linie als BlackBox vorstellen, also als eine Software, mit bekannter Funktionalit$t, aber unbekannter Funktionsweise. Als solche werden BlackBoxes nur in Bezug auf ihre externen Charakteristika (also ihre Schnittstelle) zum „Rest der Welt“ diskutiert: KnGpfe, Buttons, nummerische oder analoge Skalen, Farbe, Form, ]ffnungen etc. Sollten Sie mit dem Konzept der BlackBox nicht vertraut sein, wird Ihnen das folgende Beispiel dabei helfen, eine Vorstellung davon zu bekommen: Die typische BlackBox ist ein schwarzes, kistenartiges Objekt mit einem Knopf und einer Lampe, die auf magischem Wege 50-DM-Scheine produziert, sobald der Knopf gedr.ckt wird. F.r jedes Dr.cken des Knopfes wird eine 50-DM-Note produziert, aber nur dann, wenn das Licht zwischenzeitlich siebenmal an- und ausgegangen ist. Wie die Box die Geldscheine produziert oder die Lampe an- und ausschaltet, ist ein vGlliges R$tsel, aber die Funktion ist klar: Geldscheine produzieren. Wie Sie die Box dazu bringen, die Geldscheine zu produzieren, ist offensichtlich: durch Dr.cken des Knopfes und warten, bis die Lampe siebenmal an- und wieder ausgegangen ist. Um Ihr Bild dieser BlackBox zu vervollst$ndigen, sollten Sie sich vorstellen, dass sie aus einem unzerstGrbaren Metall hergestellt wurde, welches Sie auf keinen Fall Gffnen kGnnen. So bleibt das Innere der Box f.r immer ein Geheimnis und – wie Sie noch feststellen werden – ein unwichtiges Detail. Das Konzept der BlackBox ist ein sehr wichtiges und auch sehr wertvolles, denn es gestattet: etwas beliebig Komplexes auf ein einzelnes Objekt mit grGßtmGglicher Einfachheit zu reduzieren und große Systeme als Ansammlung von BlackBoxes zu sehen, die miteinander ausschließlich .ber die BlackBox-Schnittstelle kommunizieren. 553
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBeans: Ein 4berblick
Die F$higkeit, etwas beliebig Komplexes auf die spielzeugartige Einfachheit einer undurchsichtigen „Kiste“ zu reduzieren, ist w$hrend der Analyse-, Design- und Implementierungsphase jedes komplexen Systems ungeheuer wichtig, da – wie uns die Forschung lehrt – Menschen nur ungef$hr sieben (plus oder minus zwei) Konzepte gleichzeitig im Ged$chtnis halten kGnnen. Dies ist ein Grund, warum Systeme in hierarchischen Ebenen erstellt werden m.ssen. Jede Ebene f.r sich schirmt den Entwickler von den verbleibenden Vielzahl der Konzepte der .brigen Ebenen ab und verwandelt alle diese Konzepte in „Griffe“, mit denen sie sehr viel einfacher gehandhabt werden kGnnen (allerdings nur auf der neuen Ebene der Abstraktion). Indem große Systeme als Kommunikationsnetze gesehen werden, die BlackBoxes miteinander verbinden, kann die Komplexit$t dadurch reduziert werden, dass ein Großteil der Systemkomplexit$t, die sich innerhalb der BlackBoxes befindet, ignoriert wird. Ein hervorragendes Beispiel ist der heutige PC. W$hrend dieses Ger$t im Innern unglaublich komplex ist (wahrscheinlich gibt es keinen einzigen Menschen auf der Erde, der jeden Aspekt der internen Operationen erkl$ren kann), kann ein PC trotzdem verstanden werden, indem er als Ansammlung von eindeutig definierten Komponenten (BlackBoxes) angesehen wird, die jeweils untereinander mit eindeutig definierten Protokollen kommunizieren. Jede Komponente des PCs kann wiederum als eigenes (kleineres) Netzwerk von Komponenten angesehen werden, die wiederum mit starren Protokollen kommunizieren und so weiter. Dieses AuflGsen in immer kleinere Komponenten kann so lange fortgesetzt werden, bis eine mikroskopische Ebene erreicht wird, auf der die Hierarchie logisch endet. Als BlackBoxes betrachtet haben JavaBeans drei Schnittstellenfacetten, die in unterschiedlichem Umfang entwickelt werden kGnnen: 1
Die Methoden, die auf eine JavaBean angewandt werden kGnnen.
2
Die lesbaren oder ver$nderbaren Eigenschaften, die eine JavaBean nach außen zeigt.
3
Die Ereignisse (engl. events), die eine JavaBean nach außen signalisieren kann oder von außen akzeptiert.
Diese drei Schl.sselaspekte werden etwas detaillierter beschrieben.
Methoden der JavaBeans Da sich jede JavaBean prinzipiell auf ein Java-Objekt reduzieren l$sst (wie es zum Beispiel Applets machen), ist das Aufrufen von Methoden auf dieses Objekt der einzige Weg, mit dem mit der JavaBean interagiert werden kann. Eine JavaBean h$ngt sehr rigide an der Philosophie der objektorientierten Klassen und macht kein einziges seiner internen Instanzfelder f.r die Außenwelt zugreifbar (keine Gffentlichen Felder). Deshalb besteht der einzige Weg, eine JavaBean anzusprechen, darin, seine Teile oder sein Verhalten anzusprechen.
554
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die BlackBox – Sichtweise der JavaBeans
Anders als bei normalen Klassen ist der Mechanismus des Aufrufens von instanzierten Methoden auf den unteren Ebenen bei einigen JavaBeans jedoch nicht der Hauptweg, um JavaBeans zu manipulieren oder zu benutzen. ]ffentlichen Methoden der JavaBeans wird eine zweitrangige Rolle f.r das Manipulieren zugewiesen, da zwei Aspekte der JavaBeans auf hGherer Ebene – Eigenschaften (engl. properties) und Ereignisse (engl. events) – der bevorzugte Weg sind, um mit JavaBeans zu interagieren. Ihre JavaBeans kGnnen deshalb Gffentliche Methoden anbieten, die Sie .ber den Client der JavaBeans benutzen mGchten, aber Sie sollten bedenken, dass JavaBeanDesigner erwarten, dass die Mehrzahl der Funktionalit$ten der JavaBeans .ber die Eigenschaften und Ereignisse reflektiert wird und nicht .ber separate Methoden, die manuell aufgerufen werden. In diesem Buch wird wenig Zeit mit dem Demonstrieren von Aufrufen Gffentlicher Methoden verbracht, da diese auf dieselbe Art hinzugef.gt werden, wie Sie jede andere Gffentliche Methode hinzuf.gen w.rden. ]ffentliche Methoden m.ssen nicht von anderen Mechanismen der JavaBeans-API aktiviert oder registriert werden.
Eigenschaften der JavaBeans JavaBeans unterst.tzen das High-level-Konzept der Eigenschaften oder Properties. Konzeptionell sind diese Eigenschaften nichts anderes als die klassischen Attribute, die Objekte haben, aber formal durch eine API unterst.tzt werden, die solche Eigenschaften auslesen und setzen kann. Eine JavaBean-Uhr kGnnte die EigenschaftenZeitzone und Alarm haben, ein JavaBean-Kalender Jahr und Monat. Ein JavaBean-Postfach f.r E-Mail kGnnte die EigenschaftenPostfach-Jberlaufalarm und Junk-Mail-Filter haben und so weiter. Jede dieser Eigenschaften w.rde durch Verbinden einer einfachen Methode formal zum Leben erweckt, die Konventionen benennt. Auf diesem Wege kGnnen Werkzeuge und Endanwender herausfinden, welche Eigenschaften eine JavaBean anbietet und dann leicht die entsprechenden Werte abfragen oder ver$ndern und so die JavaBean manipulieren. Das Sndern der Eigenschaften resultiert normalerweise in sofortigen Ver$nderungen in der JavaBean, um so die Snderung anzuzeigen. Um das vorherige Beispiel aufzugreifen: Wenn Sie die Zeitzone der Uhr $ndern, w.rde die Anzeige der Uhr sofort neu dargestellt und die aktuelle Zeit in der gew$hlten Zeitzone darstellen. Wenn Sie den Kalender auf ein zuk.nftiges Jahr einstellten, w.rde die Anzeige des Kalenders sofort entsprechend angepasst und der aktuelle Monat in dem Jahr angezeigt. Wenn Sie den
555
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBeans: Ein 4berblick
Ereignisse der JavaBeans Der Hauptweg, .ber den JavaBeans Informationen mit anderen Softwarekomponenten austauschen, besteht darin, Ereignisse oder Events abzusenden oder zu empfangen. Sie sollten sich die Unterst.tzung der Events durch JavaBeans als Beine eines ICs vorstellen: In der Welt der Elektronik verbinden Ingenieure diese Beine miteinander, um die einzelnen Komponenten miteinander kommunizieren zu lassen. Einige Beine sind ausschließlich f.r die Ausgabe zust$ndig (Absenden von Events), w$hrend andere ausschließlich f.r die Eingabe (Empfangen von Events) zust$ndig sind. Sie kGnnen sich vorstellen, dass JavaBeans vergleichbare Ein-/Ausgabebeine haben, die miteinander verbunden werden und so ein Kommunikationsnetzwerk erzeugen. Abbildung 17.1 zeigt diese Analogie.
Abb. 17.1 Elektronische Komponenten werden 1ber deren periphere Beine miteinander verbunden, um so Systeme h
Umgebungen der JavaBeans Eine handels.bliche Gl.hlampe ist flexibel und robust genug, um in einem Forschungslabor der NASA, in einem Casino, Ihrer K.che oder im Freien an einer Reklametafel verwendet zu werden. Generische Objekte werden in großen Mengen in teilweise sehr unterschiedlichen Umgebungen eingesetzt. Deshalb sollten sie in all diesen verschiedenen Bereichen die gleiche Leistung liefern. Dies trifft auch auf JavaBeans zu: Sie kGnnen nicht vorhersagen, wann, wo und unter welchen Bedingungen Ihre JavaBean verwendet wird oder in welche Applikation sie eingebettet wird. Trotzdem gibt es einige grunds$tzliche Fakten, die immer gelten: JavaBeans laufen immer in einer einzelnen Java Virtual Machine (der Standard der JavaBeans ist kein Standard f.r verteilte Software, wie dies zum Beispiel bei CORBA der Fall ist). JavaBeans leben immer in einer Multithread-Umgebung und kGnnen deshalb von mehreren Threads gleichzeitig angesprochen werden. 556
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Umgebungen der JavaBeans
Der erste Punkt bedeutet, dass JavaBeans keine Konkurrenz zum Industriestandard CORBA sind, weil CORBA ein Standard f.r verteilte Objekte ist. Systeme, die mit JavaBeans erstellt werden, kGnnen trotzdem .ber eine JavaBeans-CORBA-Bridge CORBA-tauglich sein. Da JavaBeans nicht direkt in Netzwerke eingreifen, ist dieser Standard effizienter (innerhalb der vorgesehenen Anwendungsbereiche), weil alle Ver$nderungen und Kommunikationen als lokal definiert sind. Der zweite Punkt ist eine Konsequenz aus dem ersten und trifft auf alle JavaEntities (Applets, Applikationen etc.) zu. Das Kapitel 18: JavaBean-Eigenschaften erkl$rt einige Multithread-Szenarios, die Sie kennen sollten, wenn Sie Ihre JavaBeans entwerfen. Das Verst$ndnis .ber die Anwendung der Java-Multithreading-API und das Schl.sselwort synchronized sollte als fundamentale F$higkeit f.r jeden ernsthaften JavaProgrammierer angesehen werden. Im gleichen Zusammenhang sollten das Verst$ndnis .ber das Zustandekommen so genannter race conditions, die Anwendung der verschiedenen Synchronisationsmechanismen, die vor solchen race conditions sch.tzen, und das Wissen um die sichere und effiziente Implementierung einer Kommunikation der Threads untereinander nicht als fortgeschrittene F$higkeit, sondern als Basiswissen angesehen werden. Lesen Sie in einem Buch .ber moderne Betriebssysteme nach, um die oft sehr dornigen Umst$nde im Zusammenhang mit Multithreading (und Multitasking) zu verstehen. Ein gutes Buch .ber das Multithreading von Java ist Java Threads von Scott Oaks und Henry Wong (O’Reilly, 1997).
Hinweis
Die Umgebung w5hrend des Designs W$hrend es offensichtlich ist, dass JavaBeans in Applikationen zur Laufzeit aktiv werden, wird es Sie vielleicht .berraschen zu erfahren, dass JavaBeans in Entwicklungsumgebungen ebenso aktiv sind. Anders als eine Routine oder ein Modul oder Softwarebibliotheken vorhergegangener Generationen werden Instanzen von JavaBean-Komponenten bereits w$hrend der Designphase gebildet, denen das Ausf.hren ihrer .blichen Funktionen gestattet wird. Dies ist ein sehr wichtiger Unterschied zur Analogie der elektrischen Schaltkreise: Wenn ein Ingenieur die einzelnen Bauteile zusammenf.gt, sind diese „kalt“ – kein elektrischer Strom fließt durch die Komponenten, die demzufolge inaktiv sind. Wenn ein Softwaredesigner JavaBeans ausw$hlt, um diese zu verwenden, sind diese bereits „heiß“, d.h., sie funktionieren bereits, als w$ren sie in eine Applikation eingebettet. Wenn Sie zum Beispiel eine JavaBean-Uhr n$hmen und diese auf Ihrer Arbeitsoberfl$che platzierten, w.rde diese bereits ticken (unter Verwendung eines separaten Java-Threads). N$hmen Sie ein JavaBean-Licht, kGnnte dieses bereits an sein. W$ren diese beiden JavaBeans kompatibel, kGnnten Sie sie direkt miteinander verbinden. Sobald diese Verbindung etabliert ist, w.rde das Licht im Takt der Uhr pulsieren. Dieses Eigenleben der JavaBeans unterscheidet sich sehr deutlich von Codebibliotheken der Vergangenheit, die w$hrend der gesamten Designphase inaktiv blieben, bis die Applikation selbst gestartet wurde. Dieser Aspekt der JavaBeans sorgte selbst
557
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBeans: Ein 4berblick
daf.r, in der Definition der JavaBeans genannt zu werden, die am Anfang dieses Kapitels zu lesen war: JavaBeans m.ssen so entworfen sein, dass sie bereits w$hrend der Designphase aktiv sein kGnnen und so eine Interaktion zwischen dem Entwickler, den Werkzeugen und den JavaBeans ermGglichen. Das neue Modell der Softwareentwicklung hat einige sehr weitreichende Implikationen: Anders als in der klassischen Softwarekonstruktion, wo zun$chst kompiliert, dann gelinkt und dann .berpr.ft wird, ob das monolithische Endprodukt auf eigenen Beinen stehen kann, gibt es in der Softwareentwicklung mit JavaBeans diese konkrete Unterscheidung zwischen aktiver und inaktiver Software nicht. Mit JavaBeans arbeiten Sie innerhalb Ihrer visuellen Entwicklungsumgebung und erwecken die Software so zum Leben. Dieser Prozess f.hrt zu einer sehr viel iterativeren Ann$herung an die Entwicklung, wo Sie ein System entwickeln und wo Sie schnell eine Idee in einen Prototypen umsetzen kGnnen. Da JavaBeans per Definition bereits zu „laufen“ beginnen, w$hrend sie durch den Designer oder dessen Werkzeuge manipuliert werden, befassen sich einige Aspekte des Standards der JavaBeans mit Formalisierungen einiger Typen von Interaktionen w$hrend der Designphase, von denen erwartet wird, dass diese sehr weit verbreitet sind. Deshalb haben JavaBeans meistens explizite Informationen .ber ihre BlackBoxSchnittstellen, dies ist jedoch nicht zwingend. Die Eigenschaften der JavaBeans zum Beispiel, kGnnen sich in der Art, wie sie von Werkzeugen erkannt werden, unterscheiden. So kann zum Beispiel eine Eigenschaft als eine fortgeschrittene (expert) oder eine normale (normal) Eigenschaft klassifiziert sein. Abh$ngig von der Stufe der Erfahrung des Entwicklers kann das Werkzeug die fortgeschrittenen Eigenschaften aller JavaBeans einzeln verbergen oder zeigen. Eine einzelne Eigenschaft und ein einzelner Event kGnnen jeweils als Vorgabe gekennzeichnet werden. Dies teilt dem Entwickler mit, dass diese Eigenschaft oder dieser Event der wahrscheinlich am h$ufigsten benGtigte ist. Stellen Sie sich Entwickler vor, die den ganzen Tag lang nicht anderes gemacht haben, als Komponenten zu entwerfen, diese zu verbinden und deren Eigenschaften zu verbessern. Zum Schluss haben diese Entwickler ein Problem, das Entwickler elektronischer Schaltungen nicht haben: Wenn sie den Computer ausschalten, verschwindet ihr Design, w$hrend der physikalische Prototyp der Schaltung erhalten bleibt. Deshalb benGtigen JavaBeans eine MGglichkeit, dauerhaft erhalten zu bleiben, also auf einem Datentr$ger gespeichert zu werden. Vor Java 1.1 gab es keinen standardisierten Weg dies zu erreichen, weshalb es f.r in Werkzeug unmGglich war, allen JavaBeans zu sagen: „Speichert euch in dieser Datei.“ Mit Einf.hrung der Serialisierungs-API kGnnen alle Java-Objekte (also nicht nur JavaBeans) in einen (seriellen) Datenstrom umgewandelt werden, der dann in Dateien gespeichert und .ber das Netz .bertagen werden kann. Um das Laden, Speichern und Copy-andPaste zu unterst.tzen, m.ssen auch JavaBeans serialisierbare Objekte sein.
558
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Umgebungen der JavaBeans
Laufzeitumgebung Wie schon vorher erw$hnt kGnnen JavaBeans in beinahe jeder Java-Umgebung eingesetzt werden. Ein sehr schwieriges und extremes Beispiel einer mGglichen Umgebung sind eingebettete Systeme, die keine Anzeige haben (zum Beispiel Raumsonden). JavaBeans, die Teil eines solchen eingebetteten Systems sind, w.rden deshalb das AWT nicht nutzen kGnnen: weil niemand das Ergebnis der Darstellungen oder das GUI selbst sehen kGnnte und weil das Paket AWT im System selbst nicht vorhanden w$re. JavaBeans m.ssen eindeutig wissen, ob sie ein GUI nutzen kGnnen oder nicht. Der Standard ber.cksichtigt dies ebenfalls. Ein anderer Umstand der Laufzeit ist die Kultur und Sprache des Anwenders. Der heutige Softwaremarkt ist ein globaler wie fast jeder andere Markt auch. Deshalb m.ssen JavaBeans flexibel genug sein, von franzGsisch-kanadischen, brasilianischen, englischen, japanischen, vietnamesischen, deutschen (und so weiter) Anwendern benutzt werden zu kGnnen. Dies impliziert, dass wenn JavaBeans zum Beispiel die Zeit oder das Datum anzeigen, diese Zeit- oder Datumsanzeige entsprechend der lokalen Gegebenheiten das Anwenders formatiert dargestellt wird. So ist zum Beispiel die Verwendung von Zeitangaben mit AM oder PM in nur wenigen Teilen der Welt .blich, da fast .berall eine 24-Stunden-Darstellung f.r Zeitangaben verwendet wird. Wenn JavaBeans mit lokalen W$hrungen umgehen, sollten diese entsprechend der lokalen Standards angezeigt werden. So haben zum Beispiel indische Rupien eine HauptgrGßenordnung von 100.000, genannt Lakh, weshalb eine Millionen Rupien niemals als 1.000.000 Rupien dargestellt werden, sondern als 10 Lakh. Die Internationalisierung von Software ist ein sehr komplexes und vielschichtiges Problem: Da es sehr viele Permutationen von Sprache und Kultur gibt, kann dieses Buch darauf nicht weiter eingehen. Sie sollten diesen Umstand jedoch immer bedenken, wenn Sie Software entwickeln.
Umgebungen von Applets und Applikationen Das zentrale Ziel von JavaBeans besteht darin, dass die Softwareentwicklung einfacher, schneller und preiswerter sein soll, indem zur Wiederverwendung von Code in einem bislang in der Softwareindustrie nicht umgesetzten Umfang ermutigt wird. Es gibt deshalb keinen Grund, weshalb es nicht mGglich sein sollte, Java-Applets aus JavaBeans zu erstellen. Dies impliziert, dass JavaBeans denselben Sicherheitsrichtlinien unterworfen werden kGnnen, die auch f.r Applets gelten. Es ist mGglich, dass JavaBeans nicht auf das lokale Dateisystem oder die lokalen Netzwerkverbindungen zugreifen kGnnen.
559
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBeans: Ein 4berblick
Wie bei der Verf.gbarkeit eines Fenstersystems, das verwendet wird, um mit dem Anwender zu kommunizieren, kann es nGtig sein, dass JavaBeans wissen, ob sie als Teil eines Applets oder als Applet selbst ablaufen, damit sie so Aktionen vermeiden kGnnen, die vom Sicherheitsmanager des Browsers blockiert werden, was normalerweise zur sofortigen Terminierung des Applets f.hrt. Zus$tzlich verhindert der Standard der JavaBeans nicht, dass Java-Applets nicht auch JavaBeans sein kGnnen und umgekehrt. Die Unterscheidung zwischen Applets und JavaBeans verschwindet, wenn ein Applet um die zentralen Eigenschaften von JavaBeans (Events und Properties) erweitert wird oder wenn JavaBeans dahingehend erweitert werden, dass sie dem bekannten init()/start()/stop()/paint()-Protokoll der Applets entsprechen. Dies bedeutet, dass Sie sehr einfach Applet-JavaBean-Hybriden in einer JavaBeans-Entwicklungsumgebung (oder einem fertigen, auf JavaBeans basierten Produkt) verwenden kGnnen. Genauso kGnnten Sie einen solchen Hybriden in einer WWW Seite einbetten.
Das JavaBean Development Kit und die BeanBox-Testanwendung JavaSoft, eine Unterabteilung von Sun Microsystems, ist f.r die Entwicklung von Java und den dazugehGrenden APIs, Designs und Implementierungen zust$ndig. Weiterhin werden von hier verschiedene Referenzimplementierungen vertrieben, damit die verschiedenen Lizenznehmer und andere Drittanbieter ihre eigenen JavaPortierungen getestet und .berpr.ft werden kGnnen. Das vorrangige Referenzprodukt, das JavaSoft produziert, ist das Java Development Kit (JDK). Das JDK enth$lt die Referenzimplementierungen der Java Virtual Machine (JVM), einen Java-Compiler, -Debugger, -Profiler und so weiter. Wenn ein Java-Programm unterschiedliche Resultate auf verschiedenen Java-Plattformen produziert, ist mindestens eine der getesteten Umgebungen nicht zu 100 Prozent Java-kompatibel. Herauszufinden, welche Implementierung fehlerhaft ist, ist eine Sache des Vergleichs der produzierten Resultate eines Java-Programms mit den bei Verwendung des JDK entstehenden Resultaten. Die Implementierung, die bei Verwendung des JDK zu denselben Resultaten f.hrt, ist korrekt, alle Implementierungen von Drittanbietern, die vom JDK abweichende Resultate produzieren, sind fehlerhaft.
Hinweis
560
Der einzige Weg, den Umfang der Java-Kompatibilit$t einer Implementierung eines Drittanbieters eindeutig festzustellen, besteht darin, die von JavaSoft angebotenen Testprogramme zu durchlaufen. Diese Sammlung von Programmen, die in ihrer Gesamtheit alle Aspekte von Java (wie durch The Java Language Specification festgelegt) und der darunter liegenden JVM (wie durch The Java Virtual Machine Specification spezifiziert) .berpr.ft. Beide Spezifikationen sind unter http://java.sun.com erh$ltlich.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das JavaBean Development Kit
JavaSoft produziert außerdem das Bean Development Kit (BDK). Das BDK spielt dieselbe Rolle f.r den Standard der JavaBeans wie das JDK f.r Java selbst: Es ist eine Referenzumgebung, in der JavaBeans entwickelt und getestet werden (das BDK ist auf der Webseite von JavaSoft erh$ltlich: http://www.javasoft.com/beans/).
Die BeanBox Wenn Sie die BeanBox-Applikation unter Verwendung der mitgelieferten Batchdatei starten, sollten sich drei Fenster Gffnen, wie in Abbildung 17.2 dargestellt. Im grGßten Fenster finden alle Aktionen statt: Dies ist die eigentliche BeanBox. Hier lassen Sie Ihre JavaBeans hineinfallen und auf unterschiedliche Art testen. Das kleinere ToolBox-Fenster repr$sentiert eine JavaBean-Palette, aus der Sie – wie aus einer wirklichen Kiste mit Einzelteilen – verschiedene JavaBeans ausw$hlen kGnnen, mit denen Sie arbeiten wollen. Das letzte Fenster, genannt PropertySheet, listet die Eigenschaften und Werte der aktuell gew$hlten JavaBeans auf. Im Fall der folgenden Abbildung ist die BeanBox selbst die aktuell gew$hlte JavaBean (weil sie selbst eine JavaBean ist), weshalb Vordergrund, Hintergrund, Zeichensatz und Namen der Eigenschaften, die Sie sehen kGnnen, den gegenw$rtig sichtbaren Eigenschaften der BeanBox entsprechen. Beachten Sie, wie einige JavaBeans in der ToolBox von einem kleinen Icon flankiert werden, welches die jeweiligen JavaBeans repr$sentiert. Das Juggler (Jongleur)-JavaBean-Icon ist wahrscheinlich das hervorstechendste der gezeigten Icons.
Abb. 17.2 Die drei Fenster der BeanBox
561
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBeans: Ein 4berblick
Die BDK Demonstration Beans Das ToolBox-Fenster ist der Ort, an dem die BeanBox-Applikation alle JavaBeans auflistet, die Sie zur Verwendung innerhalb Ihrer JavaBean-basierten Applikationen ausw$hlen kGnnen. Nachdem Sie die BeanBox zum ersten Mal gestartet haben (nach einer frischen Installation des BDK), sind alle von der ToolBox aufgelisteten JavaBeans Demonstrationsobjekte (Demonstration Beans). Ihre Hauptaufgabe besteht darin, die Eigenschaften des Standards der JavaBeans zu demonstrieren; sie sind nicht als Bausteine tats$chlicher Software gedacht. Als Ergebnis sind die meisten Demonstration Beans des BDK merkw.rdig und durchgehend nicht funktional. Es ist wert, dies erneut durchzugehen, da sich diese JavaBeans jeweils auf einen oder mehrere Aspekte des JavaBeans-Standards konzentrieren und nicht auf die herausragenden Belange z.giger Softwareentwicklung, die .bergreifend operierende Softwarekomponenten verwendet. Der vollst$ndige Quellcode der Demonstration Beans ist im BDK enthalten, genauso wie der Quellcode der BeanBox selbst. Es wird dringend empfohlen, diesen Code zu studieren. Als Einstieg sollten Sie den Code der Demonstration Beans lesen. Sp$ter, wenn Sie eine klarere Vorstellung .ber die Schl.sselrolle des Werkzeugs innerhalb des Standards der JavaBeans haben, sollten Sie einige Zeit in den Quellcode der BeanBox investieren, damit Sie eine Vorstellung davon erhalten, wie sie funktioniert. Der Rest dieses Abschnitts wird Ihnen eine umfangreiche Einf.hrung in die herausragenden Demonstration Beans des BDK liefern.
Die Button Beans Das BDK kommt mit nicht weniger als vier Demonstration Button Beans: OurButton, OrangeButton, BlueButton und ExplicitButton. Obwohl alle vier als Quellen f.r Button Klick Events verwendet werden kGnnen, dienen sie haupts$chlich der Demonstration der folgenden Aspekte der JavaBeans-API: OurButton ist eine JavaBean, die als Unterklasse von Canvas implementiert wurde. OrangeButton ist eine Instanz von OurButton, die serialisiert (also gespeichert) wurde, w$hrend die Eigenschaft der Hintergrundfarbe auf orange gesetzt wurde. BlueButton ist eine Instanz von OurButton, die ausgelagert wurde und deren Eigenschaft der Hintergrundfarbe auf blau gesetzt wurde.
562
ExplicitButton zeigt, wie JavaBeans ihre Blackbox-Schnittstellen kontrollieren kGnnen. W$hrend OurButton-Eigenschaften f.r Vordergrund, Hintergrund, Zeichensatz und Name hat, die von Canvas geerbt werden, kontrolliert ExplicitButton seine Eigenschaften explizit, wodurch zum Beispiel der von Canvas geerbte Name nicht in der Liste der Eigenschaften erscheint. ExplicitButton hat außerdem ein eigenes Icon f.r die Werkzeugpalette und einen Customizer.
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das JavaBean Development Kit
All diese Buttons exportieren Eigenschaften des Typs bound. Dies bedeutet, dass andere Entities dar.ber informiert werden kGnnen, wenn sich Eigenschaften des Buttons $ndern. Beachten Sie, dass keiner dieser Buttons etwas mit dem java.awt.Button zu tun hat. Sie sind keine Unterklassen der Klasse Button. Abbildung 17.3 zeigt die BeanBox, die eine Instanz jedes Button-Typs enth$lt. Diese JavaBeans-Buttons sind tats$chlich n.tzliche Quellen f.r den AWT-ActionEvent-Typ, so dass die BDK-Demo-Buttons ab und zu verwendet werden, um JavaBeans anzutreiben, die Eingaben von Action Events benGtigen, wie zum Beispiel der Juggler (wird als N$chstes beschrieben).
Abb. 17.3 Die vier Typen der BDKDemo-Buttons werden hier gezeigt. Der Button unten rechts ist zurzeit selektiert (die BeanBox f1gt eine Umrandung zur selektierten JavaBean hinzu).
Der JavaBean-Jongleur Der JavaBean-Jongleur, der Juggler, ist eine so genannte self-contained Animation, die .ber zwei Methoden gestartet und gestoppt werden kann: start() und stop(). Der Juggler ist n.tzlich f.r das Testen der korrekten Propagierung von ActionEventJavaBean-Events. Wenn Sie ein System aus mehreren JavaBean-Komponenten konstruieren, von denen aber keine Komponente visuelle Ausgaben produziert, wenn sie korrekt arbeitet, kGnnten Sie den Juggler an die letzte Stufe der JavaBeanKette koppeln, der dann mit der Animation beginnt (oder aufhGrt), um das korrekte Funktionieren Ihres Designs zu best$tigen. Abbildung 17.4 zeigt einen animierten Duke, der JavaBeans jongliert und den Sie mit den angebotenen Buttons starten und stoppen kGnnen.
563
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBeans: Ein 4berblick
Abb. 17.4 Der JavaBean-Juggler mit zwei Buttons, 1ber die Sie die Animation steuern k
Die JavaBean TickTock Die TickTock-JavaBean ist n.tzlich, weil sie demonstriert, dass JavaBeans keine grafischen Elemente oder ein GUI haben m.ssen. Sie kGnnen ebenso gut vGllig unsichtbar sein. Nat.rlich w$re vollst$ndige Unsichtbarkeit w$hrend des Designs eher unpraktisch, deshalb verwendet diese JavaBean den Namen als Handle f.r die ansonsten unsichtbare JavaBean. Dies ist in Abbildung 17.5 zu sehen. Die JavaBean TickTock produziert einen konstanten Strom von clock events, die Rate ist programmierbar. Wie ein Quarz in einer elektronischen Schaltung kann diese JavaBean als Zeitgeber verwendet werden, wenn Sie zum Beispiel eine Uhr zum Synchronisieren von Events benGtigen. Der Quellcode von TickTock (im BDK enthalten) ist sehr kurz und Sie sind angehalten, diesen zu analysieren, sobald Sie das Kapitel 18: JavaBean-Eigenschaften gelesen haben. Die JavaBean TickTock ist ein einfaches Beispiel daf.r, dass zwei einfache Eigenschaften (interval und seconds), das Absenden von Events und die Verwendung von Threads kombiniert. Beachten Sie, dass das PropertySheet in der folgenden Abbildung nur die interval-Eigenschaft zeigt, weil die BeanBox nur Schreib-/Leseeigenschaften zeigt (siehe Kapitel 18: JavaBean-Eigenschaften). Da die Eigenschaft seconds nur lesbar ist, wird sie nicht dargestellt.
564
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das JavaBean Development Kit
Abb. 17.5 Die JavaBean TickTock, die durch die Eigenschaft Show Invisible Beans der BeanBox sichtbar gemacht wurde
Die JavaBean ChangeReporter Die einzige Funktion der JavaBean ChangeReporterbesteht darin, eigenschafts$ndernde Events so anzuzeigen, wie diese von anderen JavaBeans abgeschickt werden. (In Kapitel 18: JavaBean-Eigenschaften finden Sie alle Details .ber eigenschafts$ndernde Events). Die große Anzahl der Eigenschaften, die in dem PropertySheet (siehe Abbildung 17.6) aufgelistet werden, haben nichts mit den Kernfunktionen von ChangeReporter zu tun, sind aber ein Resultat des Umstands, dass ChangeReporter eine Unterklasse von TextField ist. Mit anderen Worten: Die Eigenschaften, die Sie sehen, sind geerbte Eigenschaften von TextField.
Abb. 17.6 Eine JavaBean ChangeReporter, welche die periodischen Ver7nderungen einer verborgenen seconds-Eigenschaft einer JavaBean TickTock zeigt
565
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBeans: Ein 4berblick
Die JavaBean JellyBean Die JavaBean JellyBean (gezeigt in Abbildung 17.7) enth$lt die Eigenschaft priceInCents, die ein Beispiel eines speziellen Eigenschaftstyps ist, das constrained-Eigenschaft genannt wird. Diese JavaBean hat keinen anderen Zweck, als die Demonstration einer constrained-Eigenschaft.
Abb. 17.7 Die JavaBean JellyBean mit ihrer constrained-Eigenschaft priceInCents
Die JavaBean Voter Die JavaBean Voter wird verwendet, um zu demonstrieren, wie constrained-Eigenschaften, auch bekannt als widersprechbare Eigenschaften, vor Ver$nderungen gesch.tzt werden kGnnen. Die Voter-JavaBean hat eine Eigenschaft vetoAll, die Sie verwenden kGnnen, um ihr bin$res Abstimmverhalten (ja oder nein) zu kontrollieren. Wenn vetoAll auf true gesetzt ist und Sie diese JavaBean korrekt mit einer JavaBean verbunden haben, die constrained-Eigenschaften hat (wie zum Beispiel von JellyBean gezeigt), dann wird die Voter-JavaBean verhindern, dass irgendwelche dieser Eigenschaften ver$ndert werden. Abbildung 17.8 zeigt eine Instanz der Voter-JavaBean, die eine Ver$nderung der Eigenschaft priceInCents in der Instanz einer JellyJavaBean verhindert.
566
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das JavaBean Development Kit
Abb. 17.8 Die Voter-JavaBean verhindert eine Onderung des Wertes von priceInCents in einer anderen JavaBean
Die JavaBean Molecule Die JavaBean Molecule (siehe Abbildung 17.9) wird verwendet, um zu zeigen, wie JavaBeans das Aussehen und Editierverhalten aller ihrer Eigenschaften kontrollieren kGnnen, wie der PropertySheet jedes Werkzeugs zeigt. Ein weiterer interessanter Aspekt dieser JavaBean ist das JDK MoleculeViewer Applet, das in eine JavaBean transformiert wurde.
Abb. 17.9 Die JavaBean Molecule mit einem angepassten Property Editor (das Auswahlmen1)
567
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBeans: Ein 4berblick
Die JavaBean EventMonitor Die JavaBean EventMonitor wird verwendet, um Events aufzufangen, die von anderen JavaBeans abgeschickt wurden. Die Dynamik von JavaBean-Events stellt die wichtigste Laufzeitaktivit$t innerhalb jedes Netzwerks von JavaBeans dar, so dass die MGglichkeit, diese zu betrachten, aufzuzeichnen und zu .berwachen ein sehr wichtiges Werkzeug bei der Entwicklung und Fehlersuche ist. W$hrend EventMonitor sehr m$chtig ist, weil es Typen von Events .berwachen kann, von denen es bisher nichts weiß, wird diese F$higkeit sehr teuer erkauft: Die Implementierung ist $ußerst komplex, da es auf Aspekten der untersten Ebenen von Java basiert, mit denen sich nur wenige Java-Programmierer auseinander setzen wollen (unter anderem das Dateiformat von Java .class). Abbildung 17.10 zeigt die Aufzeichnung von Events, die von EventMonitor erzeugt wird, wenn Sie diese JavaBean an eine beliebige Quelle von Events anbinden. In diesem Beispiel wurden jeweils eine JavaBean OurButton, ChangeReporter und TickTock verwendet.
Abb. 17.10 Eine JavaBean EventMonitor, die alle von einer JavaBean OurButton, ChangeReporter und TickTock erzeugten Events 1berwacht
Weitere BDK Demonstration Beans Version 1.01 der BeanBox wird mit einer Reihe weiterer JavaBeans, die weitere Aspekte des Potenzials der JavaBeans ber.hren. So kann sich zum Beispiel die JavaBean JDBCSelectmit einer SQL-Datenbank verbinden und so einem Anwender das Anwenden von SQL-SELECT-Statements ermGglichen, um Abfragen an die Daten568
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das java.beans-Paket
bank zu formulieren. Obwohl dies eine sehr beeindruckende F$higkeit ist, wird sie hier nicht weiter vorgestellt, da nur wenige Anwender Zugriff auf einen SQL-Datenbankserver haben werden. Die JavaBean QuoteMonitor ist eine weitere JavaBean, die auf Datenkommunikation aufbaut, um ihre spezielle Aufgabe zu erf.llen: Sie verwendet die Remote Method Invocation (RMI)-API, um .ber das Netz zu einem entfernt stehenden Server f.r Kurse aus der Finanz- und BGrsenwelt Kontakt aufzunehmen. Auch hier gilt wieder: Obwohl dies sehr beeindruckend ist, ist das Initialisieren der RMI-Laufzeitumgebung, die benGtigt wird, um diese JavaBean benutzen zu kGnnen, etwas, das die meisten Leser nicht durchmachen wollen werden. Das TransitionalBean ist ein Beispiel, das keine einzige der Java2-Methoden (inklusive JavaBeans-API-Methoden!) verwendet und deshalb auch in einer reinen Java1.0.2-Umgebung entwickelt worden sein kGnnte. Aus der Sicht des Anwenders betrachtet ist TransitionalBean ein Beispiel f.r JavaBeans, die mit veralteten 1.0.2-Umgebungen kompatibel sein m.ssen.
Das java.beans-Paket Viele Schreiber von Applets sind sich nicht dar.ber bewusst, dass die Applet-Basisklasse Teil eines separaten Applet-Pakets genannt java.applet ist. Autoren von JavaBeans werden keinen Zweifel daran haben, dass es ein Paket geben wird, welches ausschließlich f.r die Entwicklung von JavaBeans existiert. Sie kGnnen das Paket java.beans nur schwerlich .bersehen, da es sehr groß und komplex ist. Der Inhalt des Pakets java.beans wird in der Abbildung 17.11 gezeigt.
Abb. 17.11 Die Hierarchie des Pakets java.beans und seiner Schnittstellen
569
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBeans: Ein 4berblick
Die vorangegangene Abbildung mag auf den ersten Blick imposant wirken, aber Sie benGtigen keine einzige der gezeigten Klassen, um g.ltige JavaBeans zu erstellen, wie die Demonstration Bean TransitionalBean beweist (es verl$sst sich ausschließlich auf die 1.0.2-Core-API). So weit es der Standard spezifiziert, kGnnen alle Java-Klassen als g.ltige JavaBeans angesehen werden, obwohl solche JavaBeans mGglicherweise keine JavaBean-Eigenschaften oder JavaBean-Events unterst.tzen, sondern nur Gffentliche Methoden, die darauf angewendet werden kGnnen. Das wirkliche Herz des Pakets java.beans ist nicht die Klassenhierarchie, sondern sind die Schnittstellen, die davon spezifiziert werden, besonders aber die Schnittstelle BeanInfo. Die konkreten Klassen des Pakets java.beans kGnnen wie folgt gruppiert werden: Konkrete Klassen (PropertyChangeSupport, VetoableChangeSupport, PropertyEditorSupport, SimpleBeanInfo) Descriptor-Klassen (alle Klassen, die auf Descriptor enden) Verschiedene Klassen (Beans, PropertyChangeEvent, Property-EditorManager, Introspector) Die unterst.tzenden Klassen existieren, wie der Name bereits vermuten l$sst, um Sie bei der Aufgabe zu unterst.tzen, JavaBeans zu implementieren, aber es ist nicht unbedingt notwendig, dass Sie diese Klassen benutzen. Entsprechend sind alle Descriptor-Klassen optional, zumindest zu Anfang. Die unter „Verschiedene Klassen“ aufgez$hlten Klassen kGnnen ebenfalls ignoriert werden, wenn Sie g.ltige JavaBeans schreiben. Daraus folgt, dass Sie Ihre eigene Lernkurve so einfach oder anspruchsvoll gestalten kGnnen, wie Sie wollen. Anfangs werden Sie JavaBeans schreiben, die vollst$ndig dem Standard entsprechen und sich nicht auf irgendwelche java.beans-Klassen oder -Schnittstellen verlassen. Nur ab und zu werden Sie die fortgeschrittenen MGglichkeiten erkunden, die zus$tzliche Klassen und/oder Schnittstellen verwenden. In dieser fr.hen Stufe werden Sie zun$chst nur eine Klasse des java.beans-Pakets verwenden: die Klasse Beans.
Die Klasse Beans Mit einem Namen wie Beans werden Sie denken, dass dies die Oberklasse ist, von der aus alle Beans als Unterklasse gebildet werden m.ssen. JavaBeans m.ssen aber nicht als Unterklasse irgendeiner anderen Klasse gebildet werden oder auch nur eine einzige Schnittstelle implementieren. Deshalb ist Beans nicht die Ursprungsklasse aller JavaBeans, da es keine allm$chtige Oberklasse f.r JavaBeans gibt. Die Klasse Beans ist eine globale Klasse, die nur ein paar wichtige Methoden enth$lt, welche JavaBeans oder Werkzeuge verwenden kGnnten. Hier die Definition der Klasse Beans: 570
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Das java.beans-Paket
public class Beans extends Object { public Beans(); public static Object instantiate(ClassLoader cls, StringbeanName) throws IOException, ClassNotFoundException; public static Object getInstanceOf(Object bean, ClasstargetType); public static boolean isInstanceOf(Object bean, ClasstargetType); public static boolean isDesignTime(); public static boolean isGuiAvailable(); public static void setDesignTime(boolean isDesignTime)throws SecurityException; public static void setGuiAvailable(boolean isGuiAvailable)throws SecurityException; } Die Methoden xxxDesignTime() und xxxGuiAvailable() sollten Ihnen klar sein. Die Methoden isXxx() erlauben es den JavaBeans herauszufinden, ob eine Instanz von ihnen in einem Werkzeug gebildet wurde oder nicht und ob ein GUI verwendet werden kann (falls eine Instanz von ihnen in der finalen Applikation gebildet wurde, von der sie ein Teil sind). Die Methoden setXxx() erlauben es Werkzeugen oder Applikationen (aber niemals Applets), diese globalen Flags zu setzen. Die Methoden isInstanceOf() und getInstanceOf() bed.rfen einiger Erkl$rung: Von JavaBeans wird erwartet, dass sie in einer anderen „standardm$ßigen“ komponentenbasierten Umgebung eingebettet werden, wie zum Beispiel OpenDOC von Apple/IBM oder COM/OLE von Microsoft. Um diese .bergreifende Arbeitsweise zu unterst.tzen, werden einige JavaBeans zu verschiedenen Anl$ssen unterschiedliche Schnittstellenmasken tragen. Diese Masken bilden die Typen der Java-Schnittstellen ab. Aus diesem Grund sollten Sie den Java-Operator instanceOf nicht verwenden, um herauszufinden, ob Objekte JavaBeans sind oder nicht. Sie kGnnen instanceOf nicht auf JavaBeans anwenden, um herauszufinden, ob ein Objekt dem Typ Bean X, Y oder Z entspricht. Stattdessen sollten Sie die Methode Beans.isInstanceOf() verwenden.
Hinweis
Aus Gr.nden, die den oben angef.hrten entsprechen, sollten Sie niemals einen simplen Ausdruck zur Erzeugung einer Instanz einer Klasse new verwenden, um eine neue Instanz von JavaBeans zu erhalten. Sie kGnnen new nicht verwenden, um ein neues Bean-Objekt zu erzeugen. Stattdessen sollten Sie die Methode Beans.instantiate() benutzen. Hinweis Um unterschiedliche „Typenansichten“ von einzelnen JavaBeans zu erhalten, sollten Sie Beans.getInstanceOf() verwenden.
571
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBeans: Ein 4berblick
Ausblick Nun haben Sie ein grunds$tzliches Verst$ndnis von JavaBeans. Kapitel 18: JavaBeanEigenschaften werden die Eigenschaften und Methoden von JavaBeans vorgestellt.
572
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
Einleitung
574
Die Zugriffsmethoden setXXX() und getXXX()
575
Kategorien der JavaBean-Eigenschaften
579
Eigenschaften und Multithreading
616
18
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
Einleitung Aus „Mastering JavaBeans“ von Laurence VanhelsuwN, ISBN 0-78212097-0, 799 Seiten, $49,99
Der einfachste und am deutlichsten sichtbare Aspekt der JavaBeans sind die Eigenschaften oder Properties. Diese Properties gestatten es einem Programmieranf$nger, JavaBeans den eigenen Bed.rfnissen anzupassen, ohne daf.r Programmierexkurse zu benGtigen. Sie kGnnen JavaBeans unmittelbar .ber das GUI Ihrer Entwicklungsumgebung anpassen. Ein typisches Entwicklungswerkzeug w.rde die einzelnen Properties der JavaBeans in einem so genannten Property Sheet zusammenfassen und darstellen und ein interaktives Ver$ndern dieser Properties ermGglichen. Jede Snderung an den JavaBeans wird sofort von der gegenw$rtig dargestellten JavaBean .bernommen. JavaBeans spiegeln .blicherweise die Sorte Klassenvariablen wider, die den internen Zustand eines Objekts definieren, zum Beispiel: class StuffContainer { // Einige Container-Eigenschaften Color color; Shape shape; Volume volume; } Die Instanzen dieser Beispielklasse StuffContainer w.rden .ber deren Properties Farbe (color), Form (shape) und LautstGrke (volume) definiert. Um diese Properties in solche echter JavaBeans umzuwandeln, m.ssen Sie lediglich die .bliche objektorientierte Technik zum Verbergen von Informationen durch Anbieten der Zugriffsmethoden (auch Getter und Setter genannt) f.r diese Felder anwenden. Anschließend m.ssen Sie diese Felder vor direktem (also Gffentlichem) Zugriff sch.tzen: public class StuffContainerBean { // Sie "physikalischen" Properties einer // StuffContainerBean sind nicht zugreifbar protected Color color; protected Shape shape; protected Volume volume; // aber sie sind durch die accessor-Methoden // logisch zugreifbar public Color getColor() {...} public Shape getShape() {...} public Volume getVolume() {...} public void setColor(Color color) {...} public void setShape(Shape shape) {...} public void setVolume(Volume volume) {...} } Die Originalfelder sind jetzt als gesch.tzt gekennzeichnet, was bedeutet, dass .bliche Clients keinen Zugriff darauf haben. Lediglich Unterklassen haben weiterhin direkten Zugriff. Wie der Name der Klasse StuffContainerBean vermuten l$sst, erg$be die oben gezeigte Klasse eine legale JavaBean, woraus folgt, dass JavaBeans sehr einfache Softwareentit$ten sein kGnnen. Betrachten Sie zum Beispiel die Klas-
574
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Zugriffsmethoden setXXX() und getXXX()
sendeklaration der JavaBean: JavaBeans m.ssen keine Unterklassen einer .bergeordneten Bean-Klasse sein. Der Standard der JavaBeans setzt nicht voraus, dass JavaBeans als Unterklasse einer anderen Klasse gebildet werden. Der Grund daf.r ist, dass JavaBeans so klein wie mGglich sein sollen. Die StuffContainerBean erweitert einfach Object, wodurch es in der Tat sehr klein ist. Eine Modifikation wurde zus$tzlich eingef.gt: Die Klasse StuffContainerBean wurde als public (Gffentlich) deklariert, weil alle Klassen von JavaBeans public sein m.ssen. Allerdings sind die Methoden setXXX()/getXXX() das Herzst.ck des Property-Mechanismus, weshalb diese jetzt n$her betrachtet werden sollen.
Die Zugriffsmethoden setXXX() und getXXX() JavaBean-Properties werden insgesamt .ber die Anwesenheit oder Abwesenheit von Zugriffsmethoden definiert. In dem oben gezeigten Beispiel StuffContainerBean macht es keinen Unterschied, ob die Felder f.r Farbe, Form oder Lautst$rke vorhanden sind. So weit der Standard der JavaBeans es zul$sst, w.rde die modifizierte JavaBean trotzdem dieselbe Erscheinungsform (d.h. dieselben Schnittstellen) f.r Anwender und Werkzeuge gleichermaßen haben: Sie w.rde noch immer ihre drei Propertiescolor, shape und volume haben. Jede beliebige Eigenschaft von JavaBeans existieren einzig als Resultat des Hinzuf.gens einer simplen Namenskonvention f.r get- und/oder set-Zugriffsmethoden. Die Mitarbeiter bei JavaSoft bevorzugen es, diese Konventionen Designmuster zu nennen. Da Designmuster normalerweise sehr viel mehr umfassen, als ein paar Konventionen, die sich mit dem Benennen von Methoden befassen, wird auf diese in diesem Buch in den meisten F$llen als Namenskonventionen oder schlicht Templates Bezug genommen. Weitere Informationen .ber Designmuster kGnnen Sie dem Buch „Design Patterns“ von Gamma, Helm Johnson und Vlissides (AddisonWesley, 1995) entnehmen.
Hinweis
Definieren von Leseeigenschaften Eine Leseeigenschaft wird mit der folgenden Signatur .ber die Anwesenheit einer Methode definiert: public <propertyType> get<propertyName>( ) Alle Zugriffsmethoden m.ssen als public deklariert werden. Package-, protectedoder private-Methoden werden nicht zur Schnittstelle der JavaBeans exportiert. Nur als public deklarierte Methoden werden exportiert. Der propertyType, der von einer Methode getXXX() zur.ckgeliefert wird, definiert den Java-Typ der Eigenschaft. Hier einige Beispiele: 575
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
// Definiere eine 'width'-Leseeigenschaft vom Typ // 'int' public int getWidth() { ... } // Definiere eine 'backgroundColor'-Leseeigenschaft public Color getBackgroundColor() { ... } Sie m.ssen sehr sorgf$ltig vorgehen, wenn Sie Eigenschaften definieren, damit sie nicht vom Template einer Getter- oder Setter-Methode abweichen. Das folgende Beispiel sieht zwar aus, wie eine Getter-Methode f.r JavaBeans, ist aber keine: // ERROR: Dies definiert KEINE 'fontMetrics' read// Eigenschaft public FontMetrics getFontMetrics(Font font) { ... } Der zus$tzliche harmlos wirkende, formale Parameter font disqualifiziert diese Methode als JavaBean-Eigenschaft Getter. Eine Eigenschaft Getter nimmt keine Argumente an (beachten Sie aber auch den Abschnitt „Indizierte Eigenschaften“ in diesem Kapitel). Beachten Sie auch die Setzung der Großbuchstaben im Namen der Eigenschaft, wenn diese alleine verwendet wird. Der Name einer Eigenschaft folgt immer dieser Regel: Der Name einer Eigenschaft beginnt mit einem Kleinbuchstaben (zum Beispiel color), es sei denn, er wird im Namen einer Methode benutzt. In diesem Fall ist der erste Buchstabe ein Großbuchstabe (zum Beispiel getColor). Diese Regel folgt schlicht dem Java-Standard f.r die Namensgebung der Identifier, die aus mehreren zusammengesetzten WGrtern bestehen. Sie m.ssen wissen, dass sich JavaBeans auf diese Konvention der Namensgebung verlassen, um Eigenschaften zu identifizieren (und auch Events).
Definieren von Schreibeigenschaften Eine Schreibeigenschaft wird definiert durch die Anwesenheit einer Methode mit der folgenden Signatur: public void set<propertyName>( <propertyType> formalParamName) Hier einige Beispiele: // Definiere eine Schreibeigenschaft 'width' vom Typ // 'int' public void setWidth(int width) { ... } 576
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Die Zugriffsmethoden setXXX() und getXXX()
// Definiere eine Schreibeigenschaft 'backgroundColor ' public void setBackgroundColor(Color color) { ... } Die folgenden Beispiele definieren keine Schreibeigenschaften: // ERROR: Dies definiert KEINE Schreibeigenschaft // 'polarPosition' public void setPolarPosition(float angle, float distance) { ... } // ERROR: Dies definiert KEINE Schreibeigenschaft // 'position' public void setPosition(int x, int y, int z) { ... } Eigenschaften sind atomare Einheiten und kGnnen nur durch
Definieren von Schreib-/Leseeigenschaften Die Methoden getXXX() und setXXX() sind Ihnen als separate Definitionen von Schreib- und Leseeigenschaften vorgestellt worden. In den meisten F$llen sind solche Properties jedoch gleichzeitig lesbar und beschreibbar. Damit JavaBeans in dieser Form erkannt werden, muss eine Property auf die Getterund Setter-Methoden passen. Hier noch einmal das Template: // Eine Schreib-/Leseeigenschaft braucht einen Getter // und einen passenden Setter public <propertyType> get<propertyName>( ) public void set<propertyName>( <propertyType> formalParamName) Die erforderlichen passenden Kriterien sind, dass der Eigenschaftsname, der im Namen der Methode eingebettet ist, und der Java-Typ jeweils innerhalb von Getter als auch innerhalb von Setter identisch sind. Hier einige g.ltige Beispiele: 577
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
// Definiere eine 'discountedValue'-Schreib-/Leseei-// genschaft public Price getDiscountedValue( ) {...} public void setDiscountedValue( Price bargainValue ) {...} // Definiere eine 'sex'-Schreib-/Leseeigenschaft public Sex getSex( ) {...} public void setSex(Sex maleOrFemale ) {...} Hier Beispiele f.r fehlerhafte Definitionen: // ERROR: Definiert KEINE 'discountedValue'-Schreib// /Leseeigenschaft public Price getDiscountedValue( ) {...} public void setDiscountValue( Price bargainValue ) {...} Dieses Beispiel funktioniert nicht, weil die Namen der Eigenschaften nicht identisch sind. Die Methode Getter verwendet discountedValue, w$hrend die Methode SetterdiscountValue nutzt. Die Netzeffekte dieses Fehlers w$ren, dass JavaBeans, die diese Methoden verwenden w.rden, zwei verschiedene Eigenschaften exportieren w.rden: eine Leseeigenschaft mit dem Namen discountedValue und eine davon vGllig unabh$ngige und separate Schreibeigenschaft discountValue. Das folgende Beispiel f.hrt zu einem $hnlichen Fehler, jedoch aus anderen Gr.nden: // ERROR: Definiert KEINE 'sex'-Schreib-/Leseeigen// schaft public Sex getSex( ) {...} public void setSex(boolean maleOrFemale ) {...} In diesem Beispiel ist der Name der Eigenschaft zwar in beiden Methoden korrekt geschrieben, aber der Typ der Eigenschaft ist nicht identisch. Dieses Mal h$tten Sie zwei Eigenschaften mit demselben Namen, aber unterschiedlichen Typen erhalten: eine nur-lesbare und eine nur-beschreibbare Eigenschaft.
Hinweis
578
Die
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
Templates und Makros des Programmierers Dieser Tipp wird Ihnen wahrscheinlich viele Tage des Debuggings ersparen: Wenn Sie einen Editor fr Programmierer verwenden, der sehr große Bl"cke anwenderdefinierten Textes einfgen kann, entweder whrend Sie schreiben oder durch Aktivieren einer bestimmten Tastenfunktion, dann sollten Sie sich ein Template fr ein Paar von get/set-Methoden definieren. Sie m"chten etwas wie das hier haben: public TYPE getNAME( ) {} public void setNAME(TYPE value) {} Sobald Sie zu JavaBeans neue Eigenschaften hinzufgen wollen, k"nnen Sie dieses Template aufrufen und die Platzhalter TYPE und NAME ersetzen. Abhngig von dem von Ihnen verwendeten Editor k"nnen Sie dies in wenigen Augenblicken erledigen, ohne die Gefahr, einen Bug in Ihr Programm einzufhren. Sollte Ihr Editor sogar anwenderdefinierbare Dialogboxen und Makroerweiterung untersttzen, k"nnten Sie sich ein Makro erstellen, das Sie nach dem Namen und dem Typ der Eigenschaft fragt und so jeglichen Fehler ausschließt. Sollte Ihr gegenwrtig verwendeter Editor nicht ber diese Fhigkeiten verfgen, sollten Sie ihn durch einen leistungsstrkeren ersetzen.
Kategorien der JavaBean-Eigenschaften Der vorangegangene Abschnitt hat Sie in die Grundlagen der Eigenschaften von JavaBeans eingef.hrt: die Getter- und Setter-Methoden. Das get/set-Schema wurde weiter angepasst, um in Alltagssituationen eines Programmierers anwendbar zu sein und das Leben eines Programmierers zu erleichtern. So gibt es zum Beispiel zur Befriedigung der Bed.rfnisse der Programmierer eine explizite Unterst.tzung f.r Eigenschaften vom Typ boolean, die bedingt durch eine bereits existierende Namensgebungskonvention in Java durch Verwendung einer isXXX()-Methode statt getXXX() lesbar sein sollte. JavaBeans erkennen, dass Eigenschaften die Form von Arrays haben kGnnen. Indizierte Eigenschaften unterst.tzen diese Kategorie von Eigenschaften explizit. Zwei weitere Szenarios, denen Sie beim Umgang mit JavaBeans tats$chlich begegnen kGnnten, sind Eigenschaften, die die Außenwelt automatisch dar.ber informieren, wenn sie ver$ndert werden (entweder durch den Anwender, durch ein Programm zur Laufzeit oder w$hrend der Designperiode), und Eigenschaften, die die F$higkeit haben, sich dagegen zu wehren, bestimmte Werte anzunehmen. Die letzteren beiden Typen von Eigenschaften sind so genannte bound properties (gebundene Eigenschaften) respektive constrained properties (beschr$nkte Eigenschaften).
579
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
Diese sind, wie Sie wahrscheinlich bereits vermuten, die komplexesten, die eingesetzt werden kGnnen. In den n$chsten Abschnitten werden diese Typen von Eigenschaften n$her vorgestellt.
Hinweis
Verwechseln Sie nicht die Eigenschaften der Java-Sprachebene (zum Beispiel int, String, double, Color, Applet) mit der Klassifikation der hGheren Ebene dieses Abschnitts.
Einfache Eigenschaften Die grundlegende Namensgebungskonvention der get/set-Methoden, die Sie in den vorangegangenen Abschnitten kennen gelernt haben, definieren die so genannten einfachen Eigenschaften (simple properties). Einfache Eigenschaften sind solche Eigenschaften, die keine besonderen Zust$nde haben, also per Definition weder gebunden noch beschr$nkt sind.
Boolesche Eigenschaften Boolesche Eigenschaften (boolean properties) sind einfache Eigenschaften, die andere befriedigende Namensgebungskonventionen f.r Getter verwenden, die einen booleschen Java-Typ zur.ckliefern. Das modifizierte Template f.r einen Getter sieht so aus: public boolean is<propertyName>( ) Einige Beispiele der 1.1-API f.r die Klasse java.awt.Component: public boolean isValid( ) { ... } public boolean isVisible( ) { ... } Da diesen beiden booleschen Getter-Methoden durch ihre Partner Setter-Methoden entsprochen wird public void setValid(boolean x ) { ... } public void setVisible(boolean x ) { ... } sind sowohl valid als auch visible per Definition Schreib-/Leseeigenschaften innerhalb der Klasse java.awt.Component. Daraus folgt, dass Standard-Java-API-Klassen ihrerseits JavaBeans sein kGnnen. Es gibt nichts, was dies verhindern kGnnte. Tats$chlich kGnnten bereits in dem Augenblick, in dem Sie dies lesen, alle AWT-Komponenten in vollwertige JavaBeans umgewandelt worden sein!
Indizierte Eigenschaften Um Arrays und deren individuelle Elemente intuitiv verwenden zu kGnnen, gibt es indizierte Eigenschaften (indexed properties). Wenn man es eng auslegt, benGtigen „anst$ndige“ Arrays keine erweiterten Mechanismen jenseits der Konventionen, die f.r die einfachen Eigenschaften gelten: 580
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
public void set([] array)// array Setter public [] get() // array Getter Das folgende Beispiel demonstriert die Verwendung eines Arrays als JavaBeanSchreib-/Leseeigenschaft: public class GameManager { protected Score[] playersTop10; public void setTop10(Score[] scores) { playersTop10 = scores; } public Score[] getTop10() { return playersTop10; } } // Ende der Klasse/JavaBean GameManager Dieser JavaBean-GameManager exportiert eine top10-Schreib-/Leseeigenschaft als Array von Werten. Sie kGnnen diese erweitern, indem Sie den Anwendern Zugriff auf die individuellen Elemente des Arrays ermGglichen. Da dies von der einfachen Konvention der Namensgebung nicht unterst.tzt wird, gibt es eine Methode indexed property, deren Signatur wie folgt aussieht: // Indizierter Setter public void set(int index, value); // Indizierter Getter get(int index); Das folgende Codebeispiel erweitert GameManager durch Hinzuf.gen einer indizierbaren Eigenschaft top10score: public void setTop10Score(int index, Score aScore) { playersTop10[index] = aScore; } public Score getTop10Score(int index) { return playersTop10[index]; } Indizierte Eigenschaften sind haupts$chlich f.r Skriptumgebungen gedacht (wie zum Beispiel das propriet$re JavaScript von Netscape). Deshalb bietet JavaSoft keine Unterst.tzung f.r indizierte Eigenschaften im PropertySheet der BeanBox an. Der Report der BeanBox gibt Informationen .ber indizierte Eigenschaften aus, aber nur als Teil eines vollst$ndigen Dumps der JavaBean-Informationen, der sehr stark an einen Core-Dump erinnert, da dieser Dump selbst minimale Formatierungen vermissen l$sst, mit denen die Informationen besser lesbar w$ren. (Auf Windows 95/98/2000- oder NT4-Systemen erscheint der Report in einer DOS-Box. Um in
581
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
dem DOS-Fenster scrollen zu kGnnen, Gffnen Sie den Eigenschaften-Dialog des DOSFensters (Fenster-Symbol oben links anklicken) und stellen Layout>Fensterpuffergr ße auf 350 Zeilen ein).
Hinweis
JavaScript ist nicht mit Java kompatibel. JavaScript ist die Skriptsprache der Firma Netscape, die (mit einiger Sicherheit von der Marketingabteilung) umbenannt wurde, um die Popularit$t von Java auszunutzen.
Gebundene Eigenschaften Gebundene Eigenschaften (bound properties) verlassen sich auf das Senden von Events, um anderen Teilen eines Programms mitzuteilen, dass sich ihr Wert ver$ndert hat. Dieses Verhalten wird h$ufig benGtigt und vereinfacht das Design eines Programms insgesamt. Hier einige Beispielanwendungen: In einer Tabellenkalkulation mit Darstellung von Graphen wird das Ver$ndern beliebiger Zellen, die direkt oder indirekt Teil des Graphen sind, unmittelbar dazu f.hren, dass der Graph erneut ausgegeben wird, um die Snderungen wiederzugeben. Dies wurde implementiert, indem die Komponente des Graphen auf Snderungen der Werte innerhalb der Zellen der Tabellenkalkulation achtet und diese gegebenenfalls entsprechend neu skaliert und neu darstellt. In einem komplexen Dialogfenster kann ein ganzes Teilfenster in Abh$ngigkeit vom Zustand einer Checkbox aktiviert oder deaktiviert werden. Das Teilfenster kGnnte in diesem Fall auf die Snderung der Eigenschaft achten, die mit dieser Checkbox verbunden ist und so den neuen Zustand wiedergeben, indem es selbst aktiviert oder deaktiviert. In einer visuellen Entwicklungsumgebung resultiert die Snderung einer Eigenschaft einer Komponenten sofort in einer Ver$nderung der Komponente selbst (um so die Ver$nderung anzuzeigen) und auch eine Ver$nderung im Quellcode der Komponente wird entsprechend behandelt. Das Verwaltungssubsystem der Quellcodeverwaltung w.rde auf Snderungen in den Eigenschaften der JavaBeans achten, damit Snderungen im Code entsprechend dargestellt werden kGnnen. Sie kGnnten sich fragen, warum Sie so weit gehen sollten, wenn einige dieser Beispiele auf anderem Wege „einfacher“ im Sinne von „direkter“ gehandhabt werden kGnnen? Die Antwort daf.r ist zweiteilig: Enges (direktes) Verkn.pfen von Teilen eines Programms macht dieses sprGde und erschwert die Wartung der Systeme. Enges Verkn.pfen ist eine Sache, die Sie in Ihren Programmen vermeiden sollten. Lockere Verbindungen von Teilen eines Programms macht das Programm insgesamt flexibler und erleichtert die Wartung. Lose Verkn.pfungen sollten Sie in Ihren Programmen immer den engen Verkn.pfungen vorziehen. 582
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
Das Verwenden anonymer ZuhGrer (Listener), die aufgrund von Snderungen eine Zustands des Objekts agieren, ist ein sauberer und einfacher Weg, Subsysteme zu verbinden. Die Alternative, die einzelnen Eigenschaften selbst in die umliegenden Komponenten eingreifen und dort Snderungen vornehmen zu lassen, widerspricht der objektorientierten Philosophie des Zusammenstellens von Programmen aus lose verbundenen, (intern) hochgradig zusammenh$ngenden Subsystemen. Gebundene Eigenschaften kGnnen aus zwei entgegengesetzten Perspektiven gesehen werden: Aus der Perspektive des JavaBean-Clients (von außen) und aus der Sicht des die JavaBeans Implementierenden (von innen). Diese beiden Sichtweisen sollen nun n$her vorgestellt werden.
Gebundene Eigenschaften: Die Perspektive des JavaBean-Clients Das System der Snderungs-Events der JavaBeans ist eine direkte Anwendung des Event-Modells. Als solches sollten die folgenden beiden Methoden, die in allen JavaBeans vorhanden sein sollten, welche gebundene Eigenschaften unterst.tzen, $hnlich aussehen: public void addPropertyChangeListener(PropertyChange Listener x); public void removePropertyChangeListener(PropertyChan ge Listener x); Diese beiden Methoden erlauben interessierten Parteien, sich bei JavaBeans zu registrieren (und wieder abzumelden, sprich zu deregistrieren), um Mitteilungen vom Typ PropertyChangeEvent empfangen zu kGnnen, sobald sich eine gebundene Eigenschaft ver$ndert. Beachten Sie, dass die Granulierung der Registrierung relativ grob ist: Jede Registrierung erfolgt „pro JavaBean“ und nicht „pro Eigenschaft“ (sp$ter in diesem Kapitel wird das Vorgehen zur Registrierung auf Basis einzelner Eigenschaften vorgestellt). Der Weg, .ber den Sie feststellen, welche Eigenschaften sich ge$ndert haben, wird Ihnen sofort klar werden. Bevor Sie eine Eigenschaft als Beobachter von Snderungen der Eigenschaften (PropertyChangeListener) registrieren, muss die entsprechende Schnittstelle implementiert werden: public interface PropertyChangeListener extends EventListener { public void propertyChange(PropertyChangeEvent evt); }
583
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
// Instanzen der Methoden bilden public String getPropertyName(); public Object getNewValue(); public Object getOldValue(); public void setPropagationId(Object propagationId); public Object getPropagationId(); } Die verschiedenen get-Methoden sind alles, was Sie zum Identifizieren der ver$nderten Eigenschaften und Herausfinden der alten und neuen Werte benGtigen. Das folgende Beispiel verl$sst sich auf eine der BDK Demonstration Beans, OurButton, um diese Methoden zu demonstrieren, indem auf einen Snderungs-Event einer Eigenschaft geachtet wird, die von der JavaBean als Resultat des Testprogramms, das eine der Eigenschaften ver$ndert, ausgelGst wird. Um das Programm PropertyChangeDemo kompilieren zu kGnnen, m.ssen Sie OurButton aus der .jar-Datei auspacken (/BDK/jars/buttons.jar) und sicherstellen, dass Ihre Umgebungsvariable CLASSPATH die Klasse sunw.demo.buttons.OurButton erreichen kann. Um JavaBeans aus .jar-Dateien auszupacken, wechseln Sie zun$chst in das Verzeichnis jars und rufen dann das jar-Werkzeug mit dessen Option zum Entpacken auf, wie hier gezeigt: jar xf buttons.jar Dies wird alle Dateien aus der Datei buttons.jar auspacken und zwei neue Verzeichnisb$ume im gegenw$rtigen Verzeichnis erzeugen: META-INF und sunw. Ihr Verzeichnis sollte jetzt ungef$hr so aussehen: Directory of E:\Bdk\jars . .. META-INF SUNW BUTTONS JAR 10,887 ar EVENTM~1 JAR 43,010 tor.jar JELLY JAR 6,886 JUGGLER JAR 19,960 ar MISC JAR 3,293 MOLECULE JAR 14,725 jar QUOTE JAR 30,138 SELECT JAR 12,617 r SORT JAR 7,516 TEST JAR 16,235 TRANSI~1 JAR 4,030 584
19/02/97 19/02/97 22/02/97 22/02/97 17/02/97
10:53 10:53 16:41 16:41 18:04
. .. META-INF sunw buttons.j
17/02/97
18:04 eventmoni
17/02/97 17/02/97
18:04 jelly.jar 18:04 juggler.j
17/02/97 17/02/97
18:04 misc.jar 18:05 molecule.
17/02/97 17/02/97
18:05 quote.jar 18:05 select.ja
17/02/97 17/02/97 17/02/97
18:06 sort.jar 18:06 test.jar 18:06
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
transitional.jar 4 dir(s)
11 file(s) 169,297 bytes 7,071,744 bytes free
Das Verzeichnis META-INF ist f.r Sie nutzlos, darum lGschen Sie es bitte vollst$ndig, um sp$tere Verwechselungen zu vermeiden. Das Verzeichnis sunw enth$lt den Zweig des sunw-Pakets, das OurButton enth$lt. Sie m.ssen dieses Verzeichnis an eine Stelle verschieben, wo es von der CLASSPATH-Variablen erreicht werden kann (da viele Java-Programmierer ihre CLASSPATH der jeweiligen Maschine anpassen, auf der sie arbeiten und so die Hierarchie f.llen, wird an dieser Stelle davon ausgegangen, dass Sie wissen, wie dieser Schritt ausgef.hrt wird). Wenn Sie OurButton korrekt ausgepackt und an eine Stelle verschoben haben, wo javac es „sehen“ kann, kGnnen Sie das folgende Programm ohne Fehler kompilieren:
Bevor Sie anfangen ... Um auf die fr dieses Beispiel ben"tigten Klassen zugreifen zu k"nnen, mssen Sie sicherstellen, dass Ihre Umgebungsvariable CLASSPATH korrekt gesetzt ist und dass sowohl das Verzeichnis des Pakets sunw als auch Ihr Quellverzeichnis fr diese Beispiele enthalten ist. Ihr Dateisystem k"nnte zum Beispiel so aussehen: C: ... Jdk1.3 \bin \demo ... Bdk \beanbox \jars ... MasteringBeans \appendix \utilities \ch01 ... JavaComplete \sunw class PropertyChangeDemo.java PropertyChangeDemo.class
585
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
Listing 18.1 PropertyChangeDemo. java
import sunw.demo.buttons.OurButton; import java.beans.*; import java.awt.*; import java.awt.event.*; import java.io.*; import utilities.*; public class PropertyChangeDemo extends Frame implements PropertyChangeListener { protected OurButton aButtonBean; public static void main (String[] args) { new PropertyChangeDemo(); } //--------------------------------------------------// Constructor: Erzeuge neues Fenster mit einem // einzelnen JavaBean-Button. //--------------------------------------------------public PropertyChangeDemo() { super("Just sit back and watch.."); setLayout(new FlowLayout()); // Instanz f:r Testwecke bilden // (niemals mit "new"!) try { aButtonBean = (sunw.demo.buttons.OurButton) Beans.instantiate(null, "sunw.demo.buttons.OurButton"); } catch (ClassNotFoundException noSuchBean) { System.out.println("OurButton bean not found: " + noSuchBean); System.exit(10); } catch (IOException beanIOerror) { ystem.out.println("I/O error while loading bean:" + beanIOerror); System.exit(10); } // Anders als normale Buttons kann f:r OurButton // KEIN initiales Label gegeben werden, darum // sollten Sie die Labels jetzt setzen. aButtonBean.setLabel("When my properties change.. "); add(aButtonBean); // Registrieren Sie dieses Programm als interes// siert in button property changes. aButtonBean.addPropertyChangeListener(this);
586
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
// cffnen Sie ein neues Fenster mit <Button> setSize(new Dimension(300,75)); setVisible(true); // Warten Sie einen Moment, dann modifizieren Sie // eine der Eigenschaften des Buttons // System.out.println("No need to push that // button I'll do that."); MiscKit.delay(6*1000); aButtonBean.setLabel("anyone can know" + (" about it !"); MiscKit.delay(12*1000); // close window, quit setVisible(false); System.exit(0); } //--------------------------------------------------// propertyChange() ist die Methode, die von der // Schnittstelle des PropertyChangeListeners // implementiert werden muss. Dies ist die Stelle, // wo auf die Eigenschaft OurButton geachtet wird. //--------------------------------------------------public void propertyChange( PropertyChangeEvent pcEvent) { String changedProperty; Object oldPropertyValue, newPropertyValue; changedProperty = pcEvent.getPropertyName(); oldPropertyValue = pcEvent.getOldValue(); newPropertyValue = pcEvent.getNewValue(); System.out.println("OurButton property '" + changedProperty +"'"); System.out.println(".. changed from '" + oldPropertyValue +"'"); System.out.println(".. to '" + newPropertyValue +"'"); } } // Ende der Klasse PropertyChangeDemo Wenn Sie dieses Programm von der Konsole starten, sollten Sie die folgende Textausgabe sehen: C:\MasteringBeans\ch04>javac PropertyChangeDemo.java C:\ MasteringBeans\ch04>java PropertyChangeDemo No need to push that button.. I'll do that. 587
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
OurButton property 'label' .. changed from 'When my properties change...' .. to 'anyone can know about it !' Die folgende Abbildung zeigt das Fenster mit dem Button, den PropertyChangeDemo konstruiert.
Abb. 18.1 OurButton BDK-Demonstration-Bean bereit zum Absenden einer Eigenschafts7nderung
Um den PropertyChangeEvent wahrnehmen zu kGnnen, der durch OurButton erzeugt wird, muss die Klasse PropertyChangeDemo die Schnittstelle PropertyChangeInterface implementieren. Bevor JavaBeans Events absenden kGnnen, m.ssen diese per Beans.instantiate()-Instanzen gebildet werden. Wie bereits in Kapitel 17: JavaBeans: Ein Jberblick angemerkt wurde, sollten Sie niemals eine Instanz von JavaBeans mit einem einfachen new bilden. Die Methode Beans.instantiate() kann zwei gepr.fte Exceptions auswerfen, so dass diese Methode in ein try-catch-Statement eingebettet werden muss, das die entsprechende Fehlerbehandlung enth$lt. Der erste Parameter der Methode instantiate() bezeichnet den ClassLoader, der f.r die Bildung dieser Instanz verwendet werden soll. Solange Sie keine triftigen Gr.nde daf.r haben, einen anderen Klassenlader zu verwenden, als den vom System vorgegebenen, kGnnen Sie dieses Argument auf Null setzen. Das zweite Argument spezifiziert die JavaBean-Klasse selbst als voll qualifizierten Klassennamen. Gegenw$rtig nimmt instantiate() zuerst an, dass die JavaBean als serialisierte Instanz gespeichert wurde, mit der Dateiendung .ser. In diesem Fall ist OurButton als .class-Datei gespeichert. (Als farbige Alternative ist OrangeButton als .ser-Datei gespeichert, so dass Sie die Instanzbildung von serialisierten JavaBeans sehr leicht testen kGnnen, indem Sie den String, der an instantiate() .bergeben wird, in sunw.demo.buttonsOrangeButton $ndern.)
588
Beachten Sie, dass es keine MGglichkeit gibt, Instanzen von JavaBeans in bestimmten Zust$nden zu bilden (es sei denn, Sie haben JavaBeans in der Vergangenheit komplett in einem bestimmten Zustand serialisiert), wie Sie es kGnnten, wenn Sie new verwenden d.rften, um neue, nicht vordefinierte Konstruktoren zu erschaffen (Konstruktoren, die Parameter .bernehmen). Dies ist der Grund, warum Sie setlabel() auf die JavaBean anwenden m.ssen, um das Label des Buttons zu setzen, nachdem es erzeugt wurde. Vergleichen Sie dies mit einem normalen java.awt.Button, dessen Label im Augenblick der Instanzbildung spezifiziert werden kann (aber nicht, falls dies innerhalb einer Bean-Umgebung verwendet wird und als eine ordentliche JavaBean behandelt wird).
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
Sobald der Button erzeugt und initialisiert wurde, registrieren Sie das Objekt PropertyChangeDemo bei der JavaBean als einen PropertyChangeListener. Wenn Sie nun eine Eigenschaft von OurButton $ndern (wie zum Beispiel durch ein zweites setLabel()), wird die JavaBean einen PropertyChangeEvent absenden, der von Ihrem Programm mit der Methode propertyChange() abgefangen wird – der einzigen Methode, die implementiert werden musste, um der Schnittstelle PropertyChangeListener zu entsprechen. Hier verwendet es getProperty(), getOldValue() und getNewValue(), um einige Informationen .ber den Event auszugeben. Beachten Sie, dass in diesem einfachen Beispiel PropertyChangeDemo selbst die Eigenschaft ver$ndert, f.r die es sp$ter den Event empf$ngt. In echten Programmen wird es nur selten solche reflektiven Beziehungen zwischen der Entit$t, die die Eigenschaft $ndert, und der, die den Event empf$ngt und daraufhin handelt, geben.
Gebundene Eigenschaften: Die Perspektive des die JavaBeans Implementierenden Nachdem Sie erfahren haben, wie Sie mit gebundenen Eigenschaften umgehen, die von existierenden JavaBeans abgeschickt werden, werden Sie nun sehen, wie gebundene Eigenschaften zu Ihren eigenen JavaBeans hinzugef.gt werden. Um eine einfache Eigenschaft in eine gebundene umzuwandeln, m.ssen Sie eine Reihe von Methoden zu den jeweiligen JavaBeans hinzuf.gen. Zun$chst benGtigen Sie die Methoden des Listener Managements, die Ihre JavaBean-Clients erwarten, wenn die JavaBeans irgendwelche gebundenen Eigenschaften enthalten: public void addPropertyChangeListener( PropertyChangeListener x); public void removePropertyChangeListener( PropertyChangeListener x);
Die Asymmetrie zwischen Event Source und Event Listener Beachten Sie die zwischen Event Source und Event Listener existierende Asymmetrie: Event Listeners mssen (von vornherein) eine Java-Schnittstelle implementieren, Event Sources nicht. Schnittstellen auf der Seite der Event Source sind nicht fr das Funktionieren der Listener-Metapher notwendig, obwohl sie aus einigen Grnden sehr ntzlich sein k"nnen: Event Listener k"nnen Sources ber ihren Schnittstellentyp EventSource auf generischem Wege behandeln statt des eher rigiden Wegs eines konkreten Klassentyps. Wie es immer der Fall bei der Verwendung solcher Schnittstellen ist, wrde Ihnen dies eine flexiblere Programmierung erm"glichen. 589
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
Im Augenblick fhrt ein einziger Schreibfehler whrend des Programmierens der Signaturen der Listener-Managementmethoden zu einem Bug. Wenn Schnittstellen mit einbezogen werden, wrde der Compiler sofort den Fehler dadurch kennzeichnen, dass er ausgibt, die Schnittstelle der Event Source sei nicht implementiert. Obwohl der Standard der JavaBeans keine Schnittstellen auf der Seite der Source verwendet, gibt es keinen Grund, warum Sie sich nicht auf diese verlassen sollten, um sich das Leben zu erleichtern.
Als N$chstes m.ssen Sie die Setter-Methode(n) der Eigenschaft(en), die Sie in einen gebundenen Typ wandeln, anpassen, damit alle registrierten Listener von einer Snderung der Eigenschaftswerte erfahren. Beachten Sie, das Eigenschafts$nderungen so genannte multicast Events sind, wodurch Sie die MGglichkeit einer langen Liste von registrierenden Listenern managen m.ssen, die benachrichtigt werden m.ssen. Gl.cklicherweise sind gebundene Eigenschaften sehr h$ufig und es gibt eine unterst.tzende Klasse (analog zu AWTEventMulticaster zum Absenden von AWTEvents), die Ihnen bei der Verwaltung von all diesem hilft, weshalb Sie sich nicht um verkettete Listen und so weiter sorgen m.ssen. Die Klasse PropertyChangeSupport kommt Ihnen zu Hilfe: public class PropertyChangeSupport extends Object implements Serializable { public PropertyChangeSupport(Object sourceBean); // constructor // Bilde Instanzen der Methoden: public synchronized void addPropertyChangeListener (PropertyChangeListener listener); public synchronized void removePropertyChangeListener(PropertyChangeListener listener); public void firePropertyChange(String propertyName, Object oldValue, Object newValue); } Wie Sie sehen kGnnen implementiert PropertyChangeSupport bereits multicast addPropertyChangeListener() und removePropertyChangeListener(), wodurch Sie sich darum nicht mehr zu k.mmern brauchen. Als Sahneh$ubchen stellt es auch ein firePropertyChange() zur Verf.gung, wodurch Ihre Setter-Methode so angepasst werden kann, dass diese das Absenden von Eigenschafts$nderungs-Events durch ein einziges Statement unterst.tzt. Da PropertyChangeSupport eine Klasse und keine Schnittstelle ist, gibt es zwei Wege, auf denen Sie diese verwenden kGnnen: 590
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
Sie kGnnen Ihre JavaBeans als Unterklasse davon bilden, Ihre JavaBeans kGnnen eine Instanz von PropertyChangeSupport enthalten und das Eigenschafts$nderungsmanagement dahin delegieren. Die erste Option ist sehr restriktiv und kann in vielen F$llen unmGglich sein (Java erlaubt keine mehrfache Implementierungsvererbung, nur mehrfache Schnittstellenvererbung), weshalb hier ein Beispiel der zweiten Option gezeigt wird: import java.beans.*;
Listing 18.2
public class TestBean {
TestBean.java
// Eine Instanz von PropertyChangeSupport, um ihr die // ganze schwere Arbeit zu :berlassen protected PropertyChangeSupport butler = new PropertyChangeSupport(this); // Die Innereien der Eigenschaft 'glitzyProperty' protected int starValue; //---------------// Constructor //---------------public TestBean() { starValue = 99; // Der Eigenschaft einen Dummy-Startwert geben } //-------------------------------------------------// set()/get() f:r die Eigenschaft 'glitzyProperty' //-------------------------------------------------public void setGlitzyProperty(int newValue) { int oldValue = starValue; starValue = newValue; butler.firePropertyChange("glitzyProperty", new Integer(oldValue), new Integer(newValue)); } public int getGlitzyProperty() { return starValue; } //-------------------------------------------------// Die (De)Registrationsmethoden sind trivial: // Lassen Sie einfach die Klasse // PropertyChangeSupport // die Arbeit machen. //-------------------------------------------------591
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
public void addPropertyChangeListener (PropertyChangeListener listener) { butler.addPropertyChangeListener(listener); } public void removePropertyChangeListener( PropertyChangeListener listener) { butler.removePropertyChangeListener(listener); butler.removePropertyChangeListener(listener); } } // Ende der Klasse TestBean TestBean exportiert eine einzelne gebundene (integer-)Eigenschaft, genannt glitzyProperty. Beachten Sie, dass die Registrierung von Listener f.r Eigenschafts$nderungen auf Pro-JavaBean-Basis und nicht auf Pro-Property-Basis stattfindet. Es gibt deshalb keinen Weg, festzustellen, ob eine bestimmte Eigenschaft gebunden oder schlicht ist. Da das TestBean nur eine einzige Eigenschaft enth$lt und dieser der Außenwelt signalisiert, dass sie Eigenschafts$nderungs-Events absenden kann, folgt daraus, dass glitzyProperty gebunden sein muss. Wenn eine weitere Eigenschaft hinzugef.gt w.rde, h$tte der Client keine MGglichkeit, zu wissen, welche der beiden gebunden ist oder ob sogar beide gebunden sind. Wenn dies ein Problem f.r Ihre JavaBeans ist, dann finden Sie sp$ter in diesem Kapitel unter „Registrieren auf Basis von Eigenschaften“ eine LGsung.
Hinweis
Beachten Sie die Reihenfolge der Aktionen in der Setter-Methode: (1) Die Eigenschaft wird zuerst geupdatet und (2) dann werden die Benachrichtigungs-Events abgeschickt. Diese Reihenfolge ist ein notwendiger Teil einer gebundenen Eigenschaft, die dem Standard der JavaBeans entspricht. Ein weiterer Aspekt von TestBean ist, dass es keine grafische JavaBean ist: es gibt keinerlei sichtbare Repr$sentation. JavaBean-kompatible Umgebungen sollten keine Probleme mit solchen JavaBeans haben. Wenn ein Anwender in der Lage sein muss, unsichtbare JavaBeans zu manipulieren, dann sollte das Werkzeug oder Programm einen Ersatz einer solchen grafischen Darstellung anbieten. Die BeanBox zum Beispiel verwendet die Darstellung des Namens als Handle. Die BeanBox kann ebenfalls alle nicht-grafischen JavaBeans .ber den Men.punkt View>Hide Invisible Beans verstecken. Obwohl unsichtbare JavaBeans im Sinne des Standards vGllig legal sind, werden Sie normalerweise nur solche JavaBeans erstellen, die eine minimale grafische Darstellung haben. Dies wird durch das Erweitern von java.awt.Canvas und die Darstellung von irgendetwas auf dem Canvas erreicht oder durch die Erweiterung von java.awt.Panel und das Hinzuf.gen irgendwelcher AWT- oder eigenen Komponenten zum Panel. Da Sie jetzt verstehen, warum TestBean in dieser Form geschrieben ist, wird jetzt die korrekte Funktion des Event Change-Mechanismus getestet:
592
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
import java.beans.*; import java.io.*; public class TestBeanPropertyChangeDemo implements PropertyChangeListener { protected TestBean ourBean;
Listing 18.3 TestBeanProperty ChangeDemo.java
//--------------------------------------------------// main() Einstiegspunkt //--------------------------------------------------public static void main (String[] args) { new TestBeanPropertyChangeDemo(); } //--------------------------------------------------// TestBeanPropertyChangeDemo Constructor //--------------------------------------------------public TestBeanPropertyChangeDemo() { ourBean = (TestBean) newBean("TestBean"); if ( ourBean == null ) { System.out.println("Could not instantiate" + " TestBean."); System.exit(10); } // Registriere uns als interessiert in `nderungen // der Eigenschaften von Button ourBean.addPropertyChangeListener(this); // Modifiziere eine unserer Bean-Eigenschaften ourBean.setGlitzyProperty(1234); // quit System.exit(0); } //--------------------------------------------------// propertyChange() ist die Methode, die wir // aus der Schnittstelle PropertyChangeListener // implementieren m:ssen. Hier hIren wir von // sich [ndernden OurButton-Eigenschaften. //--------------------------------------------------public void propertyChange(PropertyChangeEvent pcEvent) { String changedProperty; Object oldPropertyValue, newPropertyValue; changedProperty = pcEvent.getPropertyName(); oldPropertyValue = pcEvent.getOldValue(); newPropertyValue = pcEvent.getNewValue();
593
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
System.out.println("TestBean property '"+ changedProperty +"'"); System.out.println(".. changed from '"+ oldPropertyValue +"'"); System.out.println(".. to '"+ newPropertyValue +"'"); } //--------------------------------------------------// Utility-Methode zum Instanzieren von Beans. //--------------------------------------------------public static Object newBean(String beanName) { Object aBean; try { aBean = Beans.instantiate(null, beanName); } catch (ClassNotFoundException noSuchBean) { System.out.println("newBean() failed: "+ noSuchBean); return null; } catch (IOException beanIOerror) { System.out.println("newBean() I/O error while" + " loading bean: " + beanIOerror); return null; } return aBean; } } // Ende der Klasse TestBeanPropertyChangeDemo Wenn Sie dieses Programm von der Konsole aus starten (es Gffnet keine GUI-Komponenten, da TestBean eine nicht-grafische JavaBean ist), kGnnen Sie sehen, dass die gebundenen Eigenschaften korrekt die Außenwelt dar.ber informieren, wie sie sich ver$ndern: C:\MasteringBeans\ch04>javac TestBean.java C:\MasteringBeans\ch04>javac TestBeanPropertyChangeDemo.java C:\ MasteringBeans\ch04>java TestBeanPropertyChangeDe mo TestBean property 'glitzyProperty' .. changed from '99' .. to '1234' Beachten Sie, wie der etwas schwerf$llige Prozess der Instanzbildung (der normalerweise Beans.instantiate() in einem try-catch-Statement einbettet) in eine einzelne Methode newBean() umgewandelt wurde. Diese Methode ist tats$chlich generisch genug, um dem Utilities-Paket hinzugef.gt zu werden. F.r zuk.nftige Wiederverwendung sollten Sie die Methode newBean() in einer neuen Utilities-Klasse genannt BeansKit speichern, die wiederum in einem Unterpaket von utilities.beans gespeichert ist. 594
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
Das Utilities-Paket der JavaBeans muss installiert werden, bevor Sie diese Beispiele ausf.hren kGnnen. Hinweis
Testen gebundener Eigenschaften in der BDK-BeanBox Statt ein ganzes Programm zum Testen der gebundenen Eigenschaften von in der Entwicklung befindlichen JavaBeans selbst zu schreiben, kGnnen Sie auch die BDKBeanBox verwenden, um die Funktionsf$higkeit zu testen. Um JavaBeans in der BeanBox zu testen, sollten Sie Ihre zu testenden JavaBeans in eine .jar-Datei verpacken. Verwenden Sie dazu folgende Prozedur: Erzeugen Sie eine Datei mit dem Namen TestBean.mf mit dem folgenden Inhalt: Name: TestBean.class Java-Bean: True Groß- und Kleinschreibung ist hier von Bedeutung, darum achten Sie auf diese Details. Als N$chstes erzeugen Sie die eigentliche .jar-Datei, indem Sie jar auf folgende Art aufrufen: jar cvfm TestBean.jar TestBean.mf TestBean.class Dieser Aufruf von jar erzeugt eine Datei TestBean.jar unter Verwendung von Test Bean.mf als Vorlage und speichert die Datei TestBean.class als einzige Datei in der neu erzeugten .jar-Datei: adding: manifest adding: TestBean.class (in=1002) (out=546) (deflated 45%) Alles was nun noch getan werden muss, ist das Kopieren oder Verschieben der erstellten .jar-Datei in das jars-Verzeichnis des BDK-Verzeichnisbaums. Auf einer DOS-basierten Maschine kGnnte dies mit folgendem Kommando geschehen: copy TestBean.jar \Bdk\jars Dies setzt voraus, dass sich Ihr Arbeitsverzeichnis auf derselben Partition wie Ihr BDK befindet und dass Sie das BDK in das Wurzelverzeichnis dieser Partition installiert haben. Wenn Sie jetzt die BeanBox starten, sollten Sie TestBean im ToolBox-Fenster aufgef.hrt sehen. Die folgende Abbildung 18.2 stellt die Ausgangssituation dar. Ihr n$chster Schritt besteht darin, TestBean auszuw$hlen und eine Instanz davon irgendwo im Designpanel der BeanBox zu platzieren. Um den Event der Eigenschafts$nderung zu testen, m.ssen Sie die JavaBean mit einer BDK ChangeReporter Demonstration Bean verbinden. W$hlen Sie dieses deshalb aus der ToolBox und platzieren Sie es neben dem TestBean. Wenn Sie TestBean 595
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
ausw$hlen, werden Sie sehen, dass das PropertySheet-Fenster die Eigenschaften zusammen mit den aktuellen Werten darstellt. Die .bern$chste Darstellung (Abbildung 18.3) zeigt diese Situation.
Abb. 18.2 Abbildung 18.1: Die BeanBox zeigt TestBean im ToolBox-Fenster
Nun m.ssen Sie ChangeReporter mit TestBean verbinden. Der einzige Zweck von ChangeReporter besteht darin, PropertyChangeEvents in einem Textfeld auszugeben. Die Verbindung wird erstellt, indem der Quelle mitgeteilt wird, mit dem Ziel Verbindung aufzunehmen, nicht umgekehrt (was vielleicht eher der aktiven Rolle des Listeners entspr$che). W$hlen Sie also TestBean und dann den Men.punkt Edit>Events>PropertyChange. Wenden Sie den Men.eintrag PropertyChange an. Abbildung 18.4 zeigt die Navigation durch die Men.s. Die BeanBox l$sst Sie nun das Ziel des Listeners f.r den PropertyChangeEvent von TestBean angeben. Ein Gummiband erlaubt es Ihnen, eine verbindende Linie zwischen den beiden JavaBeans zu ziehen. Abbildung 18.5 zeigt, wo Sie den Endpunkt der Linie platzieren sollten. Klicken Sie (linke Maustaste), um die Linie einzufrieren und die Verbindung zu definieren.
596
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
Abb. 18.3 Das Panel der BeanBox mit dem ausgew7hlten TestBean und einem ChangeReporter, bereit, verbunden zu werden
Die BeanBox Gffnet jetzt ein EventTargetDialog-Fenster, das alle mGglichen Methoden im Ziel-Listener auflistet, die mit dem generierten Typ von Event kompatibel sind. Im Fall der Demonstration Bean ChangeReporter enth$lt es nur eine Methode, die an PropertyChangeEvents interessiert ist: reportChange(). W$hlen Sie diese Methode aus und best$tigen Sie mit OK (siehe Abbildung 18.6).
Abb. 18.4 Ausw7hlen des propertyChange-Events von TestBean
597
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
Abb. 18.5 Spezifizieren der Verbindung zwischen Event Source und Event Listener
Zu diesem Zeitpunkt ist die Verbindung vollst$ndig und alle Eigenschafts$nderungen in TestBean werden von ChangeReporter in dessen Textfeld ausgegeben. Wenn Sie jetzt TestBean erneut ausw$hlen und die Eigenschaft glitzyProperty im PropertySheet-Fenster ver$ndern, werden Sie sehen, dass die JavaBean diese Snderung erfolgreich per Event an die interessierten Listener weitermeldet (Sie kGnnen weitere ChangeReporter hinzuf.gen, wenn Sie sich davon .berzeugen wollen, ob das Multicasting funktioniert). Abbildung 18.7 ist der Beweis daf.r, dass TestBean korrekt funktioniert.
598
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
Abb. 18.6 Der Ziel-Listener kann mehrere kompatible Methoden enthalten, die dazu f7hig sind, bestimmte Events zu empfangen. Das EventTargetDialog-Fenster l7sst Sie die richtige Methode w7hlen.
Registrieren auf Basis von Eigenschaften Bisher haben Sie in diesem Buch gelesen, dass immer dann, wenn sich ein Listener f.r Eigenschafts$nderungs-Events registriert, dieser Mitteilungen .ber Snderungen aller ge$nderten Events gebundener Eigenschaften durch die eine propertyChange()-Methode erh$lt. In den meisten F$llen sind Listener auch nur an einem kleinen Teil der gebundenen Eigenschaften interessiert. Ein Listener kann zum Beispiel an den dimensionsbezogenen Eigenschaften interessiert sein, w$hrend ein anderer nur an den Eigenschaften zur Fehlersuche interessiert ist. Mit dem bislang vorgestellten Schema werden alle Listener benachrichtigt, sobald sich irgendeine gebundene Eigenschaft $ndert, auch wenn sich viele Listener f.r diese spezielle Ver$nderung gar nicht interessieren und diese Benachrichtigung deshalb ignorieren, nachdem einige Prozessorzyklen verschwendet wurden, um dies herauszufinden.
599
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
Abb. 18.7 Die Eigenschaft glitzyProperty 1bertr7gt korrekt die Eigenschafts7nderungs-Events an alle interessierten Listener
Um diese (teuren) Snderungs-Events zu reduzieren, die niemanden interessieren, kGnnen JavaBeans Listener-Managementmethoden auf einer Pro-Eigenschaft-Basis anbieten. Das Template der Methode ist: void addListener(PropertyChangeListener p); void removeListener(PropertyChangeListe ner p); oder void addListener(VetoableChangeListener p); void removeListener(VetoableChangeListe ner p); (Die letzten beiden Methoden sind f.r constrained properties gedacht, die im n$chsten Abschnitt n$her betrachtet werden.)
600
Beachten Sie, dass diese Methoden dasselbe ChangeListener Argument als Eingabeparameter benGtigen (PropertyChangeListener respektive VetoableChangeListener). Mit anderen Worten, die Benachrichtigung wird noch immer ausschließlich .ber eine einzelne Change()-Methode aufseiten des Listeners ausgef.hrt. Das Anbieten von Methoden der (De-)Registrierung auf Pro-EigenschaftBasis ist deshalb eine Optimierung der Performance, um so die Anzahl unnGtiger
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
Events zu reduzieren, die an Listener .bertragen werden, die sich daf.r nicht interessieren. Das bedeutet aber nicht, dass Listener eine Pro-Eigenschaft Event receiveMethode haben kGnnen. Alle Events treffen nach wie vor in der zentralen Sammelstelle ein, auch bekannt als propertyChange()-Methode, und m.ssen dort bei deren Eintreffen ausgesondert werden. Wenn Snderungen von Eigenschaften multicast sind, kGnnen ein oder mehrere Listener ein starkes Interesse an dem neuen Wert der Eigenschaft haben. Ein bestimmter Listener kGnnte nicht dazu in der Lage sein, diesen neuen Wert der Eigenschaft in seiner BlackBox zu reflektieren, weil – sofern der Listener betroffen ist – der Wert ung.ltig ist oder sich außerhalb des erlaubten Bereichs befindet. Um mit solchen Situationen umgehen zu kGnnen, unterst.tzen die Eigenschaften von JavaBeans ein letztes Feature, das Gegenstand des n$chsten Abschnitts ist.
Beschr5nkte Eigenschaften Beschr$nkte Eigenschaften (constrained properties) sind die am weitesten fortgeschrittenen und – ungl.cklicherweise – komplexesten Eigenschaftstypen. Sie kGnnen das Annehmen bestimmter Werte durch Auswerfen einer java.beans.PropertyVetoException verweigern. Diese Exception hat ihren Ursprung normalerweise nicht innerhalb der JavaBeans selbst, sondern in einem Listener, der dieser Snderung widerspricht. Deshalb sind beschr$nkte Eigenschaften eine um einiges komplexere Form der gebundenen Eigenschaften, weil beschr$nkte Eigenschaften ebenfalls Listeners f.r Eigenschafts$nderungen unterst.tzen, mit der Ausnahme, dass diese der widersprechenden Variante angehGren. Das Methoden-Template beschr$nkter Eigenschaften besteht aus vier Methoden: public <propertyType> get<propertyName>( ); public void set<propertyName>( <propertyType> formalParamName) throws PropertyVetoException; public void addVetoableChangeListener( VetoableChangeListener x); public void removeVetoableChangeListener( VetoableChangeListener x); Die ersten beiden Methoden sind Standardzugriffsmethoden, mit der Ausnahme, dass der Setter eine zus$tzliche throws-Anweisung hat, damit diese die setAnweisung verweigern kann. Die folgenden beiden Methoden sind Listener-Managementmethoden. Sie sind grunds$tzlich identisch mit den add/removeProperty-ChangeListener()-Methoden einfacher (nicht widersprechbarer) gebundener Eigenschaften. Durch Erkennen des Templates der obigen Setter-Methode in einer Definition der Klasse von JavaBeans kGnnen Sie darauf schließen, dass diese JavaBean ein Veto auswerfen kann. Aber wie wirft ein Listener ein Veto aus? Die Antwort liegt in der relevanten Schnittstelle des Listeners VetoableChangeListener:
601
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
public interface VetoableChangeListener extends EventListener { public void vetoableChange(PropertyChangeEvent evt) throwsPropertyVetoException; } Jeder Listener, der daran interessiert ist, etwas .ber beschr$nkte Eigenschaften zu erfahren, muss die Methode vetoableChange() implementieren. Dies ist die Stelle, an der ein Listener durch Auswerfen einer PropertyVetoException sein Veto aussprechen kann.
Hinweis
Beachten Sie, dass das tats$chlich .bertragene Event-Objekt noch immer vom Typ PropertyExchangeEvent ist. Es gibt kein VetoableChangeEvent, um zwei Typen von Events zu unterscheiden. Nur die Listener-Methoden propertyChange() und vetoableChange() machen diese Unterscheidung. Da das Auswerfen von Vetos ein zentrales Element in der Implementierung beschr$nkter Eigenschaften ist, sollten diese ausgeworfenen Vetos n$her untersucht werden: classPropertyVetoException. public class PropertyVetoException extends Exception { // constructor public PropertyVetoException(String mess, PropertyChangeEvent evt); // instance methods public PropertyChangeEvent getPropertyChangeEvent(); } Um einer widersprechbaren Snderung einer Eigenschaft zu widersprechen, muss eine neue Instanz von PropertyVetoException erzeugt werden. Der Konstruktor der Klasse benGtigt eine Nachricht, die den Grund hinter dem Veto erkl$rt, und eine Instanz von dem PropertyChangeEvent, der daf.r gesorgt hat, dass Ihre Methode dieser Snderung widersprechen will. Da Ihnen Letzteres als Argument von vetoableChange() .bergeben wird, bedeutet dies, dass Sie dieses Argument einfach an den Konstruktor der Exception .bergeben kGnnen. Als Beispiel f.r mehrere Listener, die auf eine widersprechbare Ver$nderung einer Eigenschaft zugreifen und der Ver$nderung widersprechen, stellen Sie sich folgendes Szenario vor: Ein kleines aber sehr wichtiges Land mGchte seinen politischen Status $ndern, indem es seine bisherige Neutralit$t aufgibt und einer Supermacht zuwendet. Bei dem n$chsten hypothetischen Treffen der Vereinten Nationen (UN) stimmen die Mitglieder .ber diese neue, sehr komplizierte Situation ab. Das folgende Programm PrimitiveGames simuliert dieses Szenario, indem es sich auf den Mechanismus der beschr$nkten Eigenschaften verl$sst. Die erste Komponente des Programms ist die Klasse Country, die als JavaBean mit den folgenden beiden exportierten Eigenschaften implementiert wird:
602
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
countryName countryStatus CountryName ist eine einfache gebundene Eigenschaft, w$hrend countryStatus die beschr$nkte Eigenschaft ist, um die herum die ganze Simulation aufgebaut ist. Hier die Implementierung der Klasse: import java.beans.*; public class Country { // Instanzen der Property- und VetoableChangeSupport, // um die harte Arbeit des Listener-Managements abzuge// ben. protected PropertyChangeSupport changesButler; protected VetoableChangeSupport vetosButler;
Listing 18.4 Country.java
// Die Innereien der widersprechbaren Eigenschaft // 'countryStatus' protected String countryStatus; // Die Innereien der Eigenschaft 'countryName' protected String countryName; //--------------------------------------------------// Country Constructors //--------------------------------------------------public Country() { // Alle beans brauchen vorgegebene Konstruktoren! this("Atlantis", "hidden"); } public Country(String name, String status) { this.countryName = name; this.countryStatus = status; changesButler = new PropertyChangeSupport(this); vetosButler = new VetoableChangeSupport(this); } //--------------------------------------------------// set()/get() f:r 'CountryName'-Eigenschaft //--------------------------------------------------public void setCountryName( String newValue) { String oldName = countryName; countryName = newValue; changesButler.firePropertyChange("countryName", oldName, newValue); } 603
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
public String getCountryName() { return countryName; } //--------------------------------------------------// set()/get() f:r widersprechbare 'CountryStatus'// Eigenschaft //--------------------------------------------------public void setCountryStatus(String newValue) throws PropertyVetoException { // Zuerst nachsehen, ob es Listener-Objekte gibt. // Beachten Sie, dass die Eigenschaft sich bisher // noch nicht ver[ndert hat. vetosButler.fireVetoableChange("countryStatus", countryStatus, newValue); // Wenn ein Listener der `nderung widersprochen // hat, wird diese Zeile niemals ausgef:hrt: // Die Veto Exception h[tte die Methode abgebrochen // und w[re zum Caller von setCountryStatus() // zur:ckgekehrt. // Wenn kein Listener widerspricht, wird der Wert // der Eigenschaft ge[ndert. countryStatus = newValue; } public String getCountryStatus() { return countryStatus; } //--------------------------------------------------// Die (De)Registrationsmethoden sind trivial: // Lassen Sie einfach die Klasse // PropertyChangeSupport // die Arbeit machen. //--------------------------------------------------public void addPropertyChangeListener ( PropertyChangeListener l) { changesButler.addPropertyChangeListener(l); } public void removePropertyChangeListener ( PropertyChangeL istener l) { changesButler.removePropertyChangeListener(l); }
604
public void addVetoableChangeListener ( VetoableChange Listener l) {
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
vetosButler.addVetoableChangeListener(l); } public void removeVetoableChangeListener ( VetoableChangeListener l) { vetosButler.removeVetoableChangeListener(l); } } // Ende der Klasse Country
Das Herz der Klasse (im Kontext der Diskussion beschr$nkter Eigenschaften) ist setCountryStatus(), die Setter-Methode der Eigenschaft countryStatus. Jeder Setter einer beschr$nkten Eigenschaft benachrichtigt zuerst alle registrierten Listener .ber die anstehende Snderung (w$hrend die eigentliche Snderung noch gar nicht durchgef.hrt wurde). Wenn kein Listener der Snderung widerspricht, kann nur die interne Eigenschaft ge$ndert werden.
Vort5uschung falscher Tatsachen Die algorithmische Sequenz des Tnderns, nachdem die Listener von der bevorstehenden Tnderung benachrichtigt wurden, bedeutet, dass die JavaBeans die Listener anlgen, wenn sie denen mitteilen, dass eine Tnderung bereits durchgefhrt wurde. Die Verz"gerung aufseiten der JavaBeans zwischen dem Belgen der Listener ber eine durchgefhrte Tnderung der Eigenschaft und der tatschlichen Tnderung der Eigenschaft bedeutet, dass sich Listener vollkommen auf den neuen Wert verlassen sollten, der von dem Objekt PropertyChangeEvent getragen wird und niemals auf die „ordentliche“ Getter-Methode, weil der Getter nicht dazu in der Lage sein kann, den tatschlichen Wert zu reflektieren. Getter-Methoden widersprechbarer Eigenschaften sollten nicht innerhalb der Methoden der Event-Benachrichtigung verwendet werden, die sich mit diesen Eigenschaften beschftigen. Eine weitere Quelle rgerlicher Bugs wre fr einen Listener die Verwendung des alten Werts aus einem PropertyChangeEvent. Wie Sie sehen werden, wenn die Simulation PrimitiveGames ausgefhrt wird, kann der Parameter old Value auf legale Art Werte annehmen, denen ein Listener widersprochen htte, wenn diese als neue Werte prsentiert worden wren. Deshalb verwenden Sie nicht die alten Werte, sondern beschrnken Sie Ihren Code darauf, ausschließlich die neuen Werte zu betrachten.
605
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
Wie bei der Snderung einfacher Eigenschaften wurde auch hier eine JavaBeansAPI-Hilfsklasse verwendet, VetoableChangeSupport: public class VetoableChangeSupport extends Object implementsSerializable { public VetoableChangeSupport(Object sourceBean); public synchronized void addVetoableChangeListener (VetoableChangeListener listener); public synchronized void removeVetoableChangeListener (VetoableChangeListener listener); public void fireVetoableChange(String propertyName,Object oldValue, Object newValue) throws PropertyVetoException; } Diese Unterst.tzungsklasse, wie auch PropertyChangeSupport, managt die Details der unteren Ebenen wie das Registrieren der Listener und das Iterieren durch eine Liste von Listenern, wenn eine widersprechbare Snderung einer Eigenschaft abgeschickt werden muss. Allerdings macht in diesem Fall die Methode VetoableChangeSupport.fireVetoableChange() eine Menge mehr als die verwandte Methode PropertyChangeSupport.firePropertyChange(), die f.r nicht widersprechbare Snderungen benutzt wird. W$hrend letztere Methode einfach alle Listener durchgeht und diesen jeweils einen PropertyChangeEvent zuschickt, muss die Veto-bewusste Variante Folgendes machen, in Pseudo-Code: vetoed = false while (listeners) { send listener a PropertyChangeEvent (old, new) if listener vetoed change vetoed = true break out of while loop } if (vetoed) { reset iterator while (listeners) { send listener a PropertyChangeEvent (new, old) } } Auf gut Deutsch bedeutet dies, dass die Methode genau so beginnt wie das nicht beschr$nkte Gegenst.ck, aber aus seiner Event-sendenden Schleife ausbricht, sobald ein Listener der Snderung widerspricht. Dann muss die Methode wieder alle Listener durchgehen, um alle dar.ber zu informieren, dass die Eigenschaft auf den urspr.nglichen Wert zur.ckgesetzt wurde. Da Sie jetzt verstehen, wie die beschr$nkte Eigenschaft countryStatus in die JavaBean Country implementiert ist, soll nun die Klasse SuperPower n$her untersucht werden, wo auf die Snderungen der Eigenschaften geachtet wird und bedingtes Widersprechen auftritt. 606
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
import java.beans.*; import java.util.*; public class SuperPower extends Country implements VetoableChangeListener { protected static Vector bigGuys = new Vector(); protected static final int SALT_LIMIT = 2000000; // S.A.L.T // Einige zus[tzliche Attribute, die ein Land // besonders machen protected int numNuclearWarHeads;
Listing 18.5 SuperPower.java
//--------------------------------------------------// SuperPower Constructors //--------------------------------------------------public SuperPower() { // Alle Beans brauchen vorgegebene Konstruktoren! this("Unnamed superpower", 0); } public SuperPower(String name, int destructivePower) { super(name, "Independent"); this.numNuclearWarHeads = destructivePower; // F:ge neue Supermacht zur Liste der // Superm[chte hinzu bigGuys.addElement(this); } //--------------------------------------------------// set()/get() f:r Eigenschaft 'ArsenalSize' //--------------------------------------------------public void setArsenalSize(int size) { numNuclearWarHeads = size; } public int getArsenalSize() { // Wenn das tats[chliche Arsenal kleiner ist, als // viele glauben, passe dies nach oben an. if ( numNuclearWarHeads < 500000 ) { return (int) (numNuclearWarHeads * 2.5); } // Wenn das tats[chliche Arsenal grIsser ist, als // per Vertrag erlaubt, passe dies nach unten an. if ( numNuclearWarHeads > SALT_LIMIT ) { return (int) (SALT_LIMIT * 1.1); } return numNuclearWarHeads; } //---------------------------------------------------
607
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
// Die Methode vetoableChange() ist [hnlich der // Methode propertyChange(), mit der Ausnahme, dass // der Listener der `nderung widersprechen kann. // Dies wird durch Auswerfen einer PropertyVeto// Exception realisiert. //--------------------------------------------------public void vetoableChange( PropertyChangeEvent countryChanged) throws PropertyVetoException { String howChanged=""; // Extrahiere Details der Eigenschafts[nderung howChanged = (String) countryChanged.getNewValue(); System.out.print("UN member "+ countryName + "assesses the situation.. '"); System.out.println(countryChanged.getOldValue() + "' -> '"+howChanged+"'"); // Wenn ein sensitives Land seinen unabh[ngigen // Status [ndern will, dann passen alle Super// m[chte auf: if ( ! howChanged.equals("Independent") ) {
608
// Wenn das Land eine Allianz mit einer anderen // Supermacht bilden will und die Supermacht // eine grIssere Schlagkraft hat als das Land // selbst, dann widerspricht die kleinere // Supermacht dem Wechsel. int allyPosition = howChanged.indexOf("ally of"); if ( allyPosition != -1 ) { for (int i=0; i < bigGuys.size(); i++) { String allyName = howChanged.substring (ally Position+8); SuperPower ally = (SuperPower) (bigGuys.elementAt(i)); if ( ! ally.getCountryName(). equals(allyName) ) { continue; } if ( ally.getArsenalSize() > numNuclearWarHeads ) { System.out.println(countryName + "vetos change!"); String reason = countryName + " will notallow Kuwait to become " + howChanged; throw new PropertyVetoException(reason, countryChanged); }
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
} // endfor } // endif schwieriges Land will Farbe wechseln } // endif Geopolitischer Trigger } } // Ende der Klasse SuperPower Die Klasse SuperPower soll nun n$her betrachtet werden. SuperPower deklariert sich selbst als Unterklasse von Country, so dass die Eigenschaften countyName und countryStatus geerbt werden. Zus$tzlich deklariert SuperPower, dass sie ein VetoableChangeListener ist, was bedeutet, dass die Methode vetoableChange(PropertyChangeEvent x) throwsPropertyVetoException implementiert wird. SuperPower verwendet einen static (das bedeutet von allen Instanzen der Klasse gemeinsam genutzt) java.util.Vector, um die Referenzen zu jedem SuperPower-Objekt zu speichern, das es erzeugt. Das einzige zus$tzliche Instanzfeld, das hinzugef.gt wird, ist NumNuclearWarHeads, ein int, das die Feuerkraft von SuperPower enth$lt. Zu der Feuerkraft wird eine Klassenkonstante assoziiert, SALT_LIMIT, die eine Obergrenze f.r den Vorrat nuklearer SprengkGpfe darstellt. Der Konstruktor SuperPower ist unkompliziert: Wird der Name eines Landes und die jeweilige Feuerkraft .bergeben, werden diese Details aufgezeichnet und eine Referenz zu dem neu erzeugten Objekt in dessen bigGuys-Datenbank (dem statischen Vektor) gespeichert. Als N$chstes f.gt SuperPower eine eigene Schreib-/Leseeigenschaft, arsenalSize, hinzu, f.r die auch die entsprechenden Zugriffsmethoden bereitgestellt werden. Die Methode getArsenalSize() ist ein wenig verschlagen und es soll Ihnen selbst .berlassen bleiben, diese zu analysieren. Abschließend gibt es die Methode vetoableChange(), das Herz der Klasse SuperPower. Hier stimmt SuperPower als ein VetoableChangeListener dar.ber ab, ob eine Country-Bean ihre beschr$nkte Eigenschaft countryStatus $ndern darf. Der Snderungs-Event ist in einem normalen PropertyChangeEvent-Objekt eingekapselt, weshalb ein Aufruf von getNewValue() dazu f.hrt, dass der neue Wert der Eigenschaft countryStatus .bermittelt wird. Der Rest der Logik l$sst sich reduzieren auf das Herausfinden, ob der neue B.ndnispartner des ehemals unabh$ngigen Landes ein grGßeres Waffenarsenal hat und, wenn ja, dann wird dem Wechsel widersprochen (dies ist exakt dieselbe Art verr.ckter „Logik“, die von unserer Rasse angewandt wird, seit wir die GrGße des Kn.ppels unseres Gegners beurteilen kGnnen). Sobald die Klassen Country und SuperPower definiert sind, wird noch das eigentliche Programm benGtigt, das die Szenarien der Vereinten Nationen erstellt und die Superm$chte abstimmen l$sst:
609
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
Listing 18.6
import java.beans.*; import utilities.beans.*;
PrimitiveGames.java
610
public class PrimitiveGames extends Object { //--------------------------------------------------// main()-Einstiegspunkt //--------------------------------------------------public static void main (String[] args) { new PrimitiveGames(); } //--------------------------------------------------// PrimitiveGames Constructor //--------------------------------------------------public PrimitiveGames() { Country valuable; SuperPower a,b,c; // Erzeuge ein kleines Land und drei Superm[chte valuable = (Country) BeansKit.newBean("Country"); a = (SuperPower) BeansKit.newBean("SuperPower"); b = (SuperPower) BeansKit.newBean("SuperPower"); c = (SuperPower) BeansKit.newBean("SuperPower"); // Gib jedem Land seine Attribute valuable.setCountryName("Kuwait"); try { valuable.setCountryStatus("Independent"); } catch (PropertyVetoException impossibleAtThisStage) { System.out.println("Caught impossible PropertyVetoException!"); System.exit(10); } a.setCountryName("Britain"); a.setArsenalSize(600000); b.setCountryName("France"); b.setArsenalSize(3000000); c.setCountryName("China"); c.setArsenalSize(12000000); // Alle Superm[chte beobachten das kleine Land // ganz genau country valuable.addVetoableChangeListener(a); valuable.addVetoableChangeListener(b); valuable.addVetoableChangeListener(c); System.out.println(""); // und nun der Moment der Wahrheit... // Kuwait will ein Verb:ndeter von China werden try { System.out.println("Attempting to have Kuwait" + " become an ally of China\n"); valuable.setCountryStatus("ally of China"); } catch (PropertyVetoException notAcceptable) {
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
System.out.println(""); System.out.println("Kuwait not allowed to " + change status because: "); System.out.println(notAcceptable); } } } // Ende der Klasse PrimitiveGames Der Kern des Programms ist das erfolgreiche Registrieren von SuperPower a, b und c als Listener beliebiger widersprechbarer Eigenschafts$nderungen, die von valuable versucht werden, zusammen mit dem Versuch von valuable, countryStatus in „ally of China“ zu $ndern. Wird das Programm von der Konsole gestartet, sehen Sie in etwa folgende Ausgabe: C:\MasteringBeans\ch04>javac PrimitiveGames.java C:\MasteringBeans\ch04>java PrimitiveGames Attempting to have Kuwait become an ally of China UN member Britain assesses the situation.. 'Independe nt' ->'ally of China' Britain vetos change! UN member Britain assesses the situation.. 'ally of C hina' ->'Independent' UN member France assesses the situation.. 'ally of Ch ina' ->'Independent' UN member China assesses the situation.. 'ally of Chi na' ->'Independent' Kuwait not allowed to change status because: java.beans.PropertyVetoException: Britain will not allowKuwait to become ally of China Diese Ausgabe kann wie folgt erkl$rt werden: Britain war das erste Land, das .ber die Ver$nderung informiert wurde und sofort widersprach (weil China mehr Feuerkraft hat). VetoableChangeSupport.fireVetoableChange() hat dann nicht mehr alle anderen registrierten Listener informiert, weil bereits ein Listener der Snderung widersprochen hat. Es wurden dann alle Listener dar.ber informiert, dass die eingeleiteten Snderungen der Eigenschaft wieder r.ckg$ngig gemacht werden und der urspr.ngliche Wert wiederhergestellt wird. Hier ist deutlich zu erkennen, dass ein Listener .ber einen Wertewechsel alt-nachneu informiert werden kann, wobei der alte Wert niemals akzeptiert worden w$re, ohne dass der Snderung widersprochen worden w$re. Dies ist der Grund, warum Ihr Code die alten Werte eines PropertyChangeEvents nicht betrachten – oder benutzen – soll. Beachten Sie, dass die Reihenfolge, in der die Listener informiert werden, nicht spezifiziert ist. Die Ausgabe des Programms h$tte deshalb auch so aussehen kGnnen:
611
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
UN member China assesses the situation.. 'Independent' ->'ally of China' UN member France assesses the situation.. 'Independen t' ->'ally of China' UN member Britain assesses the situation.. 'Independe nt' ->'ally of China' Britain vetos change! UN member Britain assesses the situation.. 'ally of C hina' ->'Independent' UN member France assesses the situation.. 'ally of Ch ina' ->'Independent' UN member China assesses the situation.. 'ally of Chi na' ->'Independent' Wenn der widersprechende Listener als Letztes informiert wird, wie in diesem Beispiel, dann wird eine Menge Arbeit durch die vorhergehenden Listener verschwendet, die die Snderung bereits in ihren Code und die Datenstrukturen propagiert haben, nur um einige Augenblicke sp$ter angewiesen zu werden, diese Snderungen wieder zur.ckzunehmen. Eine Technik, diesen Effekt zu verringern, besteht darin, eine kurze VerzGgerung in alle Aktionen einzubauen, die von einer widersprechbaren (vetoable) Snderung ausgehen, um abzuwarten, ob nicht kurz darauf diese Snderungen wieder zur.ckgenommen werden sollen. Ist dies der Fall, kGnnen beide Events als sich gegenseitig aufhebend betrachtet werden und es muss keine Snderung durchgef.hrt werden. Sie sollten jedoch mit einer solchen ProblemlGsung sehr vorsichtig sein, weil diese Technik nur dann funktionieren kann, wenn weder die Aktion noch die Zur.cknahme irgendwelche Nebeneffekte auslGsen. Falls doch, m.ssen die Aktionen individuell ausgef.hrt werden, damit diese Nebeneffekte (vorausgesetzt, es handelt sich um vom Design erw.nschte Nebeneffekte) eintreten kGnnen. Ein weiteres wichtiges Problem bei dieser verzGgernden Technik besteht darin, dass die Performance der Interaktivit$t leidet. Wie in allen anderen F$llen auch m.ssen Sie wieder einen mGglichst optimalen Mittelweg zwischen den mGglichen Wegen finden.
Hinweis
Als die Ingenieure von JavaSoft .ber die verschwenderischen Schritte des Ausf.hrens und Zur.cknehmens in Bezug auf Widerspr.che (Vetos) befragt wurden, teilten diese mit, dass widersprechbare Eigenschaften eine extrem spezialisierte Form der Eigenschaften sind und diese deshalb nur mit Bedacht verwendet werden sollten. Bei der Mehrzahl der JavaBeans werden Sie wahrscheinlich nie auch nur in Betracht ziehen, eine Unterst.tzung f.r widersprechbare Eigenschaften hinzuzuf.gen.
Testen der beschr5nkten Eigenschaften in der BDK-BeanBox Das BDK enth$lt eine Demonstration JavaBean, Voter, die jeder widersprechbaren Eigenschaft entweder widersprechen oder zustimmen kann, abh$ngig vom Zustand der booleschen Eigenschaft vetoAll. Der Zustand der wiedersprechbaren Eigenschaft countryStatus von Country kann deshalb auch in der BeanBox getestet werden. 612
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
Um Country zur BeanBox hinzuzuf.gen, m.ssen Sie so verfahren, wie im Abschnitt „Testen gebundener Eigenschaften in der BDK-BeanBox“ beschrieben: Erstellen Sie eine Datei Country.mf mit folgendem Inhalt: Name: Country.class Java-Bean: True Erzeugen Sie die .jar-Datei: jar cvfm Country.jar Country.mf Country.class Kopieren Sie Country.jar in das Verzeichnis BeanBox jar: copy Country.jar \Bdk\jars Befolgen Sie dann folgende Schritte: 1
Nehmen Sie die JavaBean Country aus der ToolBox und platzieren Sie sie auf der Designoberfl$che.
2
Nehmen Sie die Demonstration Bean Voter und platzieren Sie sie neben Country.
3
Verbinden Sie Voter und Country:
4
W$hlen Sie Country.
5
W$hlen Sie vetoableChange aus dem Men. Events.
6
Verbinden Sie die beiden mit dem Gummiband.
7
W$hlen Sie die Methode vetoableChange im EventTargetDialog.
Wenn Sie jetzt Country w$hlen und versuchen, die Eigenschaft countryStatus zu ver$ndern, wird ein Veto Error-Dialog geGffnet, was bedeutet, dass der Snderung widersprochen wurde. Abbildung 18.8 zeigt diese Situation. Wenn Sie den Dialog schließen, werden Sie sehen, dass die Eigenschaft nicht ver$ndert wurde. Sie kGnnen die Eigenschaft vetoAll von Voter von true auf false $ndern, woraufhin Voter alle widersprechbaren Snderungen zulassen wird.
613
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
Abb. 18.8 Die BDK Demonstration Bean Voter widerspricht dem Versuch, die Eigenschaft countryStatus von Country zu ver7ndern (obwohl der Grund daf1r eher simpel ist)
4berprfen von Eigenschaftswerten durch beschr5nkte Eigenschaften Das Design des Systems der beschr$nkten Eigenschaften ist deutlich auf die Listener konzentriert: Es existiert, um alle registrierten Listener einer Eigenschaft zur Akzeptanz eines neuen Wertes zu befragen. Dieses System kann aber auch f.r einen etwas anderen Zweck benutzt werden: JavaBeans kGnnen der Snderung einer eigenen Eigenschaft widersprechen. Bisher haben beschreibbare Eigenschaften alle g.ltigen Werte innerhalb der Dom$ne des eigenen Typs akzeptiert. Eine Integer-Eigenschaft zum Beispiel wird alle mGglichen Java-int-Werte zwischen minus 4 Milliarden und plus 4 Milliarden akzeptieren. Dies gilt f.r alle anderen Typen entsprechend, woraus die Notwendigkeit entstehen kann, diesen Bereich etwas zu begrenzen, den Eigenschaften akzeptieren. Nehmen Sie zum Beispiel eine JavaBean, die eine Eigenschaft Alter exportiert. Ein Alter kann immer nur positive Werte annehmen und da es in Java keine vorzeichenlosen Integerwerte gibt, w.rde die Verwendung des Typs int dazu f.hren, dass Clients – wenn der Code keine entsprechenden Tests enth$lt – den internen Zustand korrumpieren kGnnten, indem sie einen negativen Wert setzen. Dem kGn-
614
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Kategorien der JavaBean-Eigenschaften
nen Sie entgegenwirken, indem Sie den Mechanismus der beschr$nkten Eigenschaften verwenden und so Eigenschaften der JavaBean „selbstbeschr$nkt“ machen, indem der Snderung widersprochen wird. Eine elegant wirkende LGsung w$re es, eine JavaBean einen VetoableChangeListener f.r ihre eigenen widersprechbaren Eigenschaften sein zu lassen. Die JavaBean kGnnte die benGtigte vetoableChange()-Methode implementieren, mit der sie die Snderungen der Eigenschaften validiert und sich selbst als ihr eigener Listener registriert. Diese „clevere“ LGsung hat einen schwer wiegenden Fehler: Sie w.rde bedeuten, dass jeder wirkliche externe Listener mindestens einmal, im ung.nstigen Fall zweimal (bei Widerspruch der Snderungen und Wiederherstellung des urspr.nglichen Zustands) benachrichtigt werden m.sste. Dies liegt daran, dass die JavaBean notwendigerweise als Erstes .ber die Snderung informiert w.rde, der sie selbst widersprochen hat. Dies kann sich im ung.nstigen Fall zu einer enormen Belastung der CPU aufsummieren, die verschwendet w$re, weil kein einziger externer Listener f.r die Snderung wirklich befragt werden muss. (Eine Benachrichtigung .ber einen Event kann eine Lawine von weiteren Events auslGsen, die durch das System propagiert werden, und jeder Event kann einige sehr teure Aktionen bedeuten, die vom Empf$nger ausgef.hrt werden.) Stattdessen sollte eine JavaBean einfach ein altmodisches if-Statement an den Anfang einer Setter-Methode einer Eigenschaft hinzuf.gen und dem Wert sofort widersprechen (also eine Exception auswerfen), wenn dieser nicht akzeptabel ist. Nur der Aufrufer wird die Effekte des Widerspruchs bemerken und alle registrierten Listener bleiben von der gescheiterten Snderung des Wertes verschont. Sich auf einige einfache if- 200) { PropertyChangeEvent pcEvt = new PropertyChangeEvent(this, "age", age, newAge); throw new PropertyVetoException( "age has to be positive and less than 200 years",pcEvt); } vetoChangeSuppObj.fireVetoableChange("age", age, newAge); // Wenn wir hier ankommen, sind keine Vetos aufge// treten, also [ndere die Eigenschaft age = newAge; }
615
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
Wenn Sie Schwierigkeiten hatten, diesem letzten Abschnitt .ber beschr$nkte Eigenschaften zu folgen, werden Sie sich daran erinnern wollen, dass es eine gute Daumenregel ist, Ihre Designs so einfach wie mGglich zu halten (was bedeutet, nach MGglichkeit gar keine widersprechbaren Eigenschaften zu verwenden). Dies wird umso deutlicher, wenn Sie die Komplexit$t von Javas omnipr$sentem Multithreading ber.cksichtigen. Die Probleme, die mit Eigenschaften und Multithreading zusammenh$ngen, werden im n$chsten Abschnitt erkl$rt.
Eigenschaften und Multithreading Java ist eine sehr dynamische Sprache und enth$lt einfach zu verwendende Multithreading-Sprachkonstrukte und -Unterst.tzungsklassen. Viele Java-Programme verlassen sich auf Multithreading, um so die Applikation intern zu parallelisieren, die Netzwerkanbindung zu verbessern oder das Antwortverhalten zum Anwender zu beschleunigen. Die meisten Java-Laufzeitumgebungen verwenden Multithreading, um die Garabage Collection von Java zu implementieren. Zus$tzlich verl$sst sich das AWT f.r seine Funktionalit$t ebenfalls auf separate Threads. Kurz gefasst: Selbst die einfachsten Java-Programme werden in einer aktiven MultithreadingUmgebung geboren. JavaBeans werden deshalb in solchen dynamischen Umgebungen mit mehreren Threads eingesetzt und hier liegt die klassische Gefahr so genannter race conditions. Solche race conditions sind vom Timing abh$ngige Szenarios des Programmablaufs, die zur Korruption des Status (Programmdaten) f.hren kGnnen (im n$chsten Abschnitt werden Sie ein solches Szenario im Detail kennen lernen). Jede JavaBean muss bereits im Design solche race conditions ber.cksichtigen, damit sie die gleichzeitige Benutzung durch mehrere Client-Threads aush$lt.
Probleme des Multithreadings mit einfachen Eigenschaften Implementierungen von JavaBeans m.ssen davon ausgehen, dass mehrere Threads gleichzeitig auf eine Instanz zugreifen und/oder diese ver$ndern. Als Beispiel einer nicht korrekt implementierten JavaBean (soweit es das Multithreading betrifft) soll das folgende BrokenProperties zusammen mit dem assoziierten MTProperties-Testprogramm dienen: Listing 18.7 BrokenProperties.java
import java.awt.Point; // Demonstration Bean, die nicht vor der Verwendung // durch multiple Threads gesch:tzt ist. public class BrokenProperties extends Point { //--------------------------------------------------// set()/get() f:r Eigenschaft 'Spot' //--------------------------------------------------public void setSpot(Point point) { // 'spot' Setter
616
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eigenschaften und Multithreading
this.x = point.x; this.y = point.y; } public Point getSpot() { // 'spot' Getter return this; } } // Ende von Bean/Klasse BrokenProperties
import java.awt.Point; import utilities.*; import utilities.beans.*;
Listing 18.8 MTProperties.java
public class MTProperties extends Thread { protected BrokenProperties myBean; // Die Ziel-Bean, die getroffen werden soll... protected int myID; // Jeder Thread tr[gt eine ID //--------------------------------------------------// main() Einstiegspunkt //--------------------------------------------------public static void main (String[] args) { BrokenProperties bean; Thread thread; bean = (BrokenProperties) BeansKit.newBean( "Broken Properties"); for (int i=0; i < 20; i++) { // starte 10 Threads, um das Bean zu testen thread = new MTProperties(bean, i); // Threads erhalten Zugriff auf das Bean thread.start(); } } //--------------------------------------------------// MTProperties Constructor //--------------------------------------------------public MTProperties(BrokenProperties bean, int id) { // Notiere das zu adressierende Bean this.myBean = bean; // Notiere wer wir sind this.myID = id; } //--------------------------------------------------// Die Hauptschleife des Threads: // Wiederhole unendlich
617
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
// Erzeuge einen zuf[lligen neuen Punkt mit x == y // Teile Bean mit, den Punkt als neue Eigenschaft // 'spot' zu :bernehmen // Frage Bean nach aktuellem Zustand von 'spot' // Gib eine Mitteilung aus, wenn spot x ungleich // spot y ist //--------------------------------------------------public void run() { int someInt; Point point = new Point(); while ( true ) { someInt = (int) (Math.random()*100); point.x = someInt; point.y = someInt; myBean.setSpot( point ); point = myBean.getSpot(); if ( point.x != point.y ) { System.out.println("Bean corrupted ! x= " + point.x +", y= " + point.y); System.exit(10); } System.out.print( (char) ('A' + myID) ); System.out.flush(); } } } // Ende der Klasse MTProperties Die beiden Quellcodes definieren eine JavaBean, genannt BrokenProperties, und eine Klasse MTProperties, die benutzt wird, um die JavaBean in 20 Threads auszuf.hren. Beginnend mit dem main()-Einstiegspunkt von MTProperties soll der Ablauf dargestellt werden: Eine Instanz von BrokenProperties wird gebildet, gefolgt von der Erzeugung und dem Starten von 20 Threads. Die Klasse MTProperties erweitert die Klasse java.lang.Thread, weshalb die Klasse MTProperties durch Implementieren der Methode run() in einen Thread umgewandelt werden kann (vergleiche mit Schnittstelle java.lang.Runnable). Der Konstruktor der Threads nimmt zwei Argumente: das JavaBean-Objekt, mit dem kommuniziert wird und eine eindeutige Identifikation, damit die einzelnen Threads w$hrend der Laufzeit leicht unterschieden werden kGnnen. Der aktive Teil dieser Demo ist die Methode run() in der Klasse MTProperties. Hier wird eine Endlosschleife gebildet, die zuf$llige (x,y)-Punkte erzeugt, die aber einer bestimmten Charakteristik folgen: Die x-Koordinate entspricht immer der y-Koordinate. Diese zuf$lligen Punkte werden der Methode setSpot() der JavaBean .bergeben und dann sofort mit der Methode getSpot() wieder ausgelesen. Eigentlich sollten die ausgelesenen Eigenschaften identisch mit dem wenige Millisekunden zuvor erzeugten Punkt sein. Hier eine Beispielausgabe des Programms, wenn dieses von der Kommandozeile aufgerufen wird: 618
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eigenschaften und Multithreading
C:\MasteringBeans\ch04>javac MTProperties.java C:\MasteringBeans\ch04>java MTProperties ABBBBBBBBBBBBBBBBBBDJJJJJJJJJJJJJJJJJJJJEGHHHHHHHHHHH HHHHHHHSSSSSSSSSSSSSS IICCBBBBBBBBBBBBBBBBBKBDLJMBOPLQNRTPHHHHHHHHFFFFFFFFF FFFSSSSSSSSSSSSSSSSSS FFFFFFFFFFFFFFFAAAAAACCCCCCCKKKKKKKKKKKKKKKKKMMMMMMMM MMMMMMMMMMMMMMMMMMMDD JEOQQQQQQQQQQQQQQQRRRRRRRRRRRRRRRRRBBBBBBBBBBBBBBBTTT TTTTTTTTTTTTTLPPPPPPP PPPPGGHHHFFFFFFFFIIIIIIIIIIIIIISSSSSSSSSSSSSSSSSSSSAC CCCCCCCCCCCCCCCCCCKMD QQQQQQNNNNNNNNNNNNNNNNRRRRRTRRHHHHHHHHHFFFFFFFFFFFFFF FFFFIIIIIIIIIIIIIIIII MMMJEEEEEEEEEEDDDDEEEEEEEEEOOOOOOOOOOOOOOOOOOOOOOOOOO OQNNNNNNNNBTLPLRGFFFF FFFFFFFFFIIAAAAAAAAAAAAAAAAASSSSSSSSSSSSSSSSSSKKKKKKK KKKKKKKKKCCCCCCCMMJAA AACBean corrupted ! x = 67, y = 13 OOOOOOOOOOOOOOOOOOO Die Ausgabe zeigt die 20 Threads, die parallel ablaufen (soweit dies f.r einen menschlichen Beobachter feststellbar ist). Jeder Thread verwendet die ihm bei der Erstellung zugewiesene ID, um einen der Buchstaben zwischen ‚A‘ und ‚T‘ (die ersten 20 Buchstaben des Alphabets) auszugeben. Sobald ein Thread feststellt, dass die ausgelesene Eigenschaft nicht mit dem programmierten Charakteristikum x = y .bereinstimmt, gibt der Thread die Mitteilung „Bean corrupted“ aus und stoppt das Experiment. Was Sie sehen kGnnen, ist der den Status korrumpierende Seiteneffekt einer race condition innerhalb des setSpot()-Codes. Hier noch einmal die Methode: public void setSpot(Point point) { // 'spot' setter this.x = point.x; this.y = point.y; } Was kann an einem so einfachen St.ck Code falsch funktionieren? Stellen Sie sich vor, ein Thread A ruft setSpot()mit einem Argument f.r den Punkt (67,67) auf. Wenn nun die Zeit stark verlangsamt wird, kGnnten Sie sehen, dass die JVM jedes einzelne Statement ausf.hrt. Thread A f.hrt das Kopieren der x Koordinate (this.x = point.x;) aus ... und dann wird Thread A plGtzlich vom Betriebssystem eingefroren und Thread C wird eine gewisse Zeit lang ausgef.hrt. Im seinem vorherigen Ablaufzustand hatte Thread C gerade seinen eigenen Punkt (13,13) erzeugt, setSpot() aufgerufen und wurde dann eingefroren, um Platz f.r Thread M zu schaffen, nachdem die x-Koordinate auf 13 gesetzt wurde. Die wiederaufgenommene Ausf.hrung von Thread C setzt die programmierte Logik fort, setzt die y-Koordinate auf 13 und .berpr.ft dann, ob der Punkt der Koordinate (13,13) entspricht – aber hier wird fest-
619
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
gestellt, dass dies auf mysteriGse Art nicht mehr der Fall ist, sondern eine ung.ltige Koordinate (67,13) eingetragen ist. Dies entspricht zur einen H$lfte dem Status von Thread A, der dabei war, den Punkt (67,67) zu setzen, und zur anderen H$lfte dem Thread C, der den Punkt (13,13) setzen wollte. Das Resultat ist, dass BrokenProperties mit einem intern inkonsistenten Zustand endet: einer besch$digten Eigenschaft (engl. broken property). Wann immer eine nicht-atomare Datenstruktur (eine Struktur, die aus mehr als einem Teil besteht) von mehr als einem Thread gleichzeitig ver$ndert werden kann, m.ssen Sie diese Struktur sch.tzen. In Java wird dies mit dem Schl.sselwort synchronized erreicht.
Hinweis
Anders als alle anderen Java-Typen, garantiert Java bei long und double nicht, dass diese als atomar behandelt werden! Dies liegt daran, dass long und double 64 Bits benGtigen, was die doppelte Wortl$nge der meisten modernen CPU-Architekturen ist (32 Bits). Das Laden oder Speichern einzelner MaschinenwGrter sind intrinsisch atomare Operationen, aber das Bewegen von 64-Bit-Elementen benGtigt zwei solche Bewegungen und diese werden von Java aus den .blichen Gr.nden nicht gesch.tzt: Performance. (Einige CPUs erlauben dem Systembus, dass dieser automatisch gesichert ist, w$hrend so genannte multiword transfers ausgef.hrt werden, aber dies gilt nicht f.r alle CPUs und w$re außerdem unglaublich teuer in der Anwendung auf alle long- und double-Manipulationen!) Selbst wenn eine Eigenschaft nur aus einem einzigen long oder einem einzigen double besteht, sollten Sie deshalb immer alle Maßnahmen ergreifen, um diese long- und double-Werte davor zu sch.tzen, plGtzlich korrumpiert zu werden. Das Schl.sselwort synchronized kennzeichnet einen Block des Codes als einen atomaren Schritt. Der Code kann nicht „geteilt“ werden, wie es der Fall w$re, wenn ein anderer Thread den Code unterbricht, um dann eventuell denselben Block auszuf.hren (daher der Ausdruck reentrant Code; jeder Java-Code ist reentrant). Die LGsung f.r BrokenProperties ist trivial: Ersetzen Sie einfach die Methode setSpot() mit folgendem Code: public void setSpot(Point point) { // 'spot' setter synchronized (this) { this.x = point.x; this.y = point.y; } } oder alternativ: public synchronized void setSpot(Point point) { // 'spot' setter this.x = point.x; this.y = point.y; }
620
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eigenschaften und Multithreading
Beide Ersetzungen sind $quivalent, obwohl h$ufig die erste Methode bevorzugt wird, weil diese deutlicher zeigt, was die exakte Funktion des Schl.sselwortes synchronized ist: Ein sychronisierter Block ist immer an ein Objekt gebunden, das gesichert wird. Mit gesichert ist in diesem Falle gemeint, dass die JVM zuerst versucht, das Objekt zu sichern (den exklusiven Zugriff darauf zu erhalten) oder wartet, bis das Objekt entsichert wird, wenn es bereits gesichert ist. Der Prozess des Sicherns garantiert, dass ein Objekt nur von einem Thread zur Zeit gesichert (oder besessen) werden kann. Die Syntax synchronized (this) gibt eindeutig den internen Mechanismus wieder: Das Argument innerhalb der Klammern ist das Objekt, das gesichert werden soll (das gegenw$rtige Objekt), bevor der Codeblock ausgef.hrt wird. Die alternative Syntax, in der das Schl.sselwort synchronized als Modifikator in der Signatur der Methode verwendet wird, ist einfach eine Abk.rzung der ersten Variante. Wenn statische (static) Methoden als synchronized gekennzeichnet werden, gibt es f.r diese Methoden kein gegenw$rtiges Objekt, das gesichert werden kGnnte. Nur Instanzen von Methoden werden mit einem gegenw$rtigen Objekt assoziiert (die Objektinstanz this). Wenn also Methoden von Klassen durch synchronized gesichert werden, wird stattdessen das Objekt java.lang.Class gesichert. Dies hat weitreichende Auswirkungen auf die Performance, da eine Sammlung von Instanzen einer Klasse sich ein einzelnes assoziiertes Class-Objekt teilen. Sobald dieses Class-Objekt gesichert wird, wird verhindert, dass irgendein anderes Objekt der Klasse (egal ob 3, 50 oder 1000!) diese statische Methode aufruft. Das im Hinterkopf sollten Sie es sich gut .berlegen, ob Sie die Synchronisation auf statische Methoden anwenden wollen.
Hinweis
In der Praxis sollten Sie sich immer an die explizite synchronized-Form erinnern, da diese es Ihnen erlaubt, den kleinsten mGglichen Block innerhalb einer Methode zu „atomisieren“. Die abgek.rzte Form „atomisiert“ die ganze Methode und dies ist aus Gr.nden der Performance nicht immer das, was Sie erreichen wollen: W$hrend ein anderer Thread einen atomaren Blockcode ausf.hrt, kann kein anderer Thread auf diesen Teil zugreifen. Wenn ein Objekt gesichert wird, wird der gesamte synchronisierte Code der Klasse des Objekts atomar. Sollte Ihr Code also mehr als eine Datenstruktur enthalten, die atomar behandelt werden muss, w$hrend diese Datenstrukturen ansonsten unabhGngig voneinander sind, kann ein anderer Flaschenhals in der Performance auftreten. Clients, die synchronisierte Methoden aufrufen, die eine interne Datenstruktur manipulieren, blockieren alle anderen Methoden, die mit anderen atomaren Datenstrukturen der Klasse arbeiten. Selbstverst$ndlich sollten solche Situationen vermieden werden, indem die Klasse in kleinere Klassen aufgeteilt wird, die sich mit nur jeweils einer atomar zu behandelnden Datenstruktur zur Zeit befassen.
Hinweis
Die JVM behandelt dies, indem Warteschlangen aus Threads gebildet werden, die darauf warten, dass ein Objekt entsichert wird. W$hrend dies aus der Perspektive des Schutzes der Konsistenz zusammengesetzter Datenstrukturen eine sehr begr.ßenswerte Sache ist, kann dies zu Staus bei den Threads f.hren, wenn ein Teil des Codes, der „less-than-efficient“ ist, als synchronized gekennzeichnet ist. 621
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
Achten Sie deshalb immer genau darauf, welchen Code Sie synchronisieren: Dies sollte immer nur das absolut notwendige Minimum sein. Stellen Sie sich zum Beispiel vor, dass die urspr.ngliche setSpot()-Methode wie folgt ausgesehen h$tte: public void setSpot(Point point) { // 'spot' setter log.println("setSpot() called on " + this.toString() ); this.x = point.x; this.y = point.y; } Obwohl das Statement println logisch zur Methode setSpot() gehGrt, ist es nicht Teil des Statements, das atomar gruppiert werden muss. Deshalb w$re in diesem Fall die korrekte Anwendung des Schl.sselwortes synchronized wie folgt: public void setSpot(Point point) { // 'spot' setter log.println("setSpot() called on " + this.toString() ); synchronized (this) { this.x = point.x; this.y = point.y; } } Und nicht der „bequeme“ Weg, wie hier gezeigt: public synchronized void setSpot(Point point) { // 'spot' setter log.println("setSpot() called on " + this.toString() ); this.x = point.x; this.y = point.y; } Der erste Ansatz wird alle anderen Threads f.r das absolut notwendige Minimum an Zeit ausschließen, w$hrend der zweite Ansatz alle Threads dazu zwingt, auf etwas zu warten, das vGllig irrelevant ist: das println-Statement. Wenn Sie sich unter dem Druck der modernen Produktzyklen f.r den bequemen Weg entschlossen haben, und alle Methoden in G$nze synchronisieren, werden Ihre Anwender eine $hnlich „bequeme“ (mit geringer Performance) JavaBean erhalten.
622
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eigenschaften und Multithreading
Listener von Eigenschaften und Multithreading Multithreading f.hrt das ganze Spektrum mGglicher Probleme ein, die Ihnen das Leben als Entwickler von JavaBeans schwer machen. Hier ein weiteres Szenario, das Sie beachten sollten: Race conditions (schon wieder) kGnnen zu unerwartetem Verhalten f.hren, das aus inkorrekten Erwartungen resultiert, die mit der Reihenfolge von Aufrufen von add- und remove und dem tats$chlichen Timing der eingehenden Events zusammenh$ngen. Stellen Sie sich eine Reihe von Threads und eine einzelne JavaBean mit einer gebundenen Eigenschaft vor. Wenn diese Threads kontinuierlich die eine Eigenschaft registrieren, deregistrieren und modifizieren (die Gr.nde hierf.r sind im Augenblick nebens$chlich), kann es passieren, dass ein Thread einen g.ltigen Event einer Eigenschafts$nderung erh$lt, obwohl er sich gerade deregistriert hat (und deshalb nicht erwartet, Events zu empfangen). Die folgende JavaBean UnexpectedEvents und das Testprogramm MTListeners demonstrieren dieses Ph$nomen: import java.beans.*;
Listing 18.9
public class UnexpectedEvents { protected PropertyChangeSupport butler; // butler does all the work //--------------------------------------------------// UnexpectedEvents Constructor //--------------------------------------------------public UnexpectedEvents() { butler = new PropertyChangeSupport(this); }
UnexpectedEvents.java
//--------------------------------------------------// set()/get() f:r 'Property' Dummy Eigenschaft //--------------------------------------------------public void setProperty( int value) { System.out.println("Bean changing property." + " Firing PC event."); butler.firePropertyChange("property", new Integer(0), new Integer(1)); } public int getProperty() { return 1; } //--------------------------------------------------// Listener Registrierungsmethoden //--------------------------------------------------public void addPropertyChangeListener( PropertyChange Listener l) { butler.addPropertyChangeListener(l); }
623
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
public void removePropertyChangeListener( PropertyChangeListener l) { butler.removePropertyChangeListener(l); } } // Ende von Bean/Klasse UnexpectedEvents
Listing 18.10 MTListeners.java
import java.beans.*; import utilities.*; import utilities.beans.*; public class MTListeners extends Thread implements PropertyChangeListener { protected UnexpectedEvents myBean; // Das zu testende Bean ... protected int myID; // Jeder Thread hat eine eigene ID protected boolean readyForEvents = false; // Sind wir schon registriert? //--------------------------------------------------// main()-Einstiegspunkt //--------------------------------------------------public static void main (String[] args) { UnexpectedEvents bean; Thread thread; bean = (UnexpectedEvents) BeansKit.newBean( "UnexpectedEvents"); for (int i=0; i < 5; i++) { // Starte N Threads um Bean zu testen thread = new MTListeners(bean, i); // Threads erhalten Zugriff auf Bean thread.start(); } } //--------------------------------------------------// MTListeners Constructor //--------------------------------------------------public MTListeners(UnexpectedEvents bean, int id) { this.myBean = bean; // notiere das zu // adressierende Bean this.myID = id; // notiere, wer wir sind } //--------------------------------------------------// Die Thread-Hauptschleife: // Wiederhole endlos
624
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eigenschaften und Multithreading
// Registriere als Listener, setze flag, um Events // zu akzeptieren // Deregistriere als Listener, setze flag, um Events // abzulehnen // `ndere Bean-Eigenschaft // // Falls wir jemals einen Event erhalten, w[hrend // wir eigentlich keinen erhalten sollten, // teile dies mit und halte an. //--------------------------------------------------public void run() { while ( true ) { // Registriere bei Bean, das bedeutet, wir // kInnten von jetzt an Events erwarten. synchronized (this) { System.out.println(myID +" registering."); myBean.addPropertyChangeListener(this); readyForEvents = true; } // Lasse andere Threads f:r einige Zeit laufen randomSleep(); // Deregistriere uns jetzt, das bedeutet, wir // sollten eigentlich keine weiteren Events mehr // von der Bean bekommen. synchronized (this) { System.out.println(myID +" un-registering."); myBean.removePropertyChangeListener(this); readyForEvents = false; } // Lasse andere Threads f:r einige Zeit laufen randomSleep(); // dann modifiziere die Eigenschaft der Bean: System.out.println(myID + " about to change bean p roperty.."); myBean.setProperty( (int) (Math.random()*50)); } } //--------------------------------------------------// Die Implementierung der Schnittstelle // PropertyChangeListener //--------------------------------------------------public void propertyChange( PropertyChangeEvent pcEvent) { if ( readyForEvents == false ) { System.out.println("I ("+ myID + ") got an event while I did not" + " expect to receive one!!"); System.exit(0);
625
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
} System.out.println(myID + " heard of property change."); } //--------------------------------------------------// Hilfsfunktion, die einen Thread f:r eine // zuf[llige Zeitspanne (kleiner 1 Sekunde) // einschl[fert //--------------------------------------------------protected void randomSleep() { MiscKit.delay( (int) (Math.random()*1000)); } } // Ende der Klasse MTListeners Wie bei der schon vorher gezeigten Multithreading-Demonstrationsklasse MTProperties ist das Herz der Klasse die MTListener-Methode run(), in der sich die Logik des Threads verbirgt. Was diese Methode macht, ist ziemlich banal: Sie registriert und deregistriert fortlaufend ihre Threads als Listener f.r die Quelle des Events der Eigenschafts$nderung, w$hrend gleichzeitig die Bean-Eigenschaft ver$ndert wird und eine Menge zuf$lliger Pausen eingelegt werden. Wenn Sie MTListener von der Konsole ausf.hren, kGnnte die Ausgabe zum Beispiel so aussehen: C:\MasteringBeans\ch04>javac MTListeners.java C:\MasteringBeans\ch04>java MTListeners 0 registering. 2 registering. 1 registering. 3 registering. 4 registering. 4 un-registering. 4 about to change bean property.. Bean changing property. Firing PC event. 0 heard of property change. 2 heard of property change. 1 heard of property change. 3 heard of property change. 1 un-registering. 4 registering. 0 un-registering. 3 un-registering. 2 un-registering. 2 about to change bean property.. Bean changing property. Firing PC event. 4 heard of property change. 2 registering. 1 about to change bean property.. Bean changing property. Firing PC event. 4 heard of property change. 626
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
Eigenschaften und Multithreading
2 heard of property change. 1 registering. 0 about to change bean property.. Bean changing property. Firing PC event. 4 heard of property change. 1 un-registering. 2 heard of property change. I (1) got an event while I did not expect to receive one!! Das Programm beginnt damit, dass Klone von Threads erzeugt werden, damit die Ausgabe mit einer Reihe von Threads beginnt, die mitteilen, dass diese sich als Listener bei den JavaBeans registrieren. Beachten Sie, dass die Ausgabe bereits durcheinander gew.rfelt ist, obwohl die Threads in einer strikten 0-1-2-3 ... Reihenfolge gestartet werden. Dies liegt an den vom darunter liegenden Betriebssystem in diesem Augenblick getroffenen Entscheidungen .ber die Zeitplanung der Tasks oder Prozesse (woraus folgt, dass bei Ihnen die Ausgabe des Programms mit an Sicherheit grenzender Wahrscheinlichkeit anders aussehen wird) und ist auch genau der Grund f.r die h$ufig unvorhersagbare Reihenfolge prGziser Events in Multithreading-Systemen. (Selbstverst$ndlich sind diese Systeme in ihrer Gesamtheit deterministisch, wie auch vGllig sequenzielle Systeme, aber dies liegt auch nur an dem Vertrauen auf bestimmte kritische Synchronisationspunkte.) Die n$chste Ausgabe ist Thread 4, der das Gl.ck hatte, lange genug laufen zu kGnnen, um sich in nur einer einzigen Zeitscheibe zu registrieren, zu deregistrieren und die Eigenschaft zu $ndern. Die Snderung der Eigenschaft lGst das Aussenden eines PropertyChangeEvents an alle augenblicklich registrierten Listener aus (Threads 0,1,2 und 3). Sie sehen dies in den aufeinander folgenden Zeilen „x heard of property change.“ W$hrend die Zeit vergeht zeigen die Threads ihren frei umherlaufenden Charakter, indem sie zunehmend mit Pausen ineinander verschachtelt (interleaved) ablaufen (siehe Ausgabe des Programms). Sehen Sie sich nun wieder die Zeile „0 about to change bean property ...“ an. An dieser Stelle sind die Threads 1,2 und 4 registriert und kurz darauf hGrt Thread 4 schon von der Snderung, aber Thread 1 deregistriert sich bereits, bevor er etwas von der Snderung gehGrt hat. Als N$chstes erf$hrt Thread 2 von der Snderung und Peng! ... Thread 1, der in diesem Augenblick gar kein Interesse mehr an den Snderungen hat, wird von der Snderung der Eigenschaft informiert, die Thread 0 einige Schritte zuvor ausgelGst hatte. Thread 1 war nicht als Listener f.r diesen Event registriert und wurde trotzdem ein Event, den dieser Thread in diesem Augenblick gar nicht erwartet hatte. Wenn dies ein Bug in der Methode PropertyChangeSupport.firePropertyChange() w$re, w.rde hier nicht in solcher Breite und Tiefe darauf eingegangen. Das Problem ist in diesem Fall, dass dies ein g.ltiger Nebeneffekt der Freiheit der Implementierung ist, die den implementierenden der Event-auslGsenden Methoden einger$umt wurde. W$hrend durch die Liste der Listener iteriert wird, kann (nicht: muss!) eine Event-absendende Methode die Liste sperren, um so Snderungen zu verhindern, w$hrend
627
H:/Sybex/Java_2_Buch/DasBuchNeu.3d from 4.1.2001 Page size: 168,00 x 240,00 mm
JavaBean-Eigenschaften
die Listener benachrichtigt werden. Es ist klar, dass im Fall eines PropertyChangeSupport.firePropertyChange() die Liste gesperrt wird (mittels clone() auf den Vektor, der alle Listener enth$lt). Dies erkl$rt, warum Thread 1 sich selbst als Listener entfernen konnte und trotzdem Augenblicke sp$ter den Event erhielt.
Hinweis
628
Es ist klar, dass Multithreading die Implementierung von JavaBeans sehr stark belasten kann. Deshalb ist es von kritischer Bedeutung, dass Sie Ihre JavaBeans immer ausreichenden Belastungstests aussetzen. Verwenden Sie dazu aggressive, weit hergeholte und bewusst falsch verwendete Szenarios. Die Realit$t ist meistens noch sehr viel kreativer, wenn es darum geht, unwahrscheinliche Szenarios zu finden. Wenn Sie versucht haben, die Ausgaben der Testprogramme zu reduzieren, haben Sie wegen der Unvorhersagbarkeit der Zeitplanung des Multithreadings gesehen, wie unmGglich dies ist. Die beiden Testprogramme kGnnen f.r einige Minuten laufen, ohne dass der „Fehler“ auftritt. Dieser Grund alleine sollte Sie davon .berzeugen, dass Sie Ihre JavaBeans immer umfangreichen Tests in vollst$ndig multithreadeten Umgebungen unterziehen. Lassen Sie den Simulationen ausreichend Zeit, um solche Probleme zu entdecken.
H:/Sybex/Java_2_Buch/IndexDB.3d from 3.1.2001 Page size: 168,00 x 240,00 mm
Index
! + (Verkettungsoperator) 141 . (Punktoperator) 14, 32, 123 .class-Dateien 4, 479 .java-Dateien 3 // (Kommentarzeichen) 32 2-D-API 495 Bzier-Kurven 524 Clipping 513 Fllungen 508 Fraktale 528 Kreise und Ellipsen 501 Kurventypen 499 Linienattribute 504 Polygone 521 selbstdefinierte Kurven 519 Transformationen 515 Zeichenoperationen 504 2-D-Grafik 496 = = (Gleichheitsoperator) 44
A , HTML 229 abgeleitete Klasse 12 Ableiten, Konzept 12 abnorme Zust6nde 236 absolute Werte 250 abstrakte Klassen 206-207, 499 Action, Ereignis 366 action (Methode) 40 ActionEvent 563 Ereignis 394 ActionListener (Schnittstelle) 40 implementieren 41, 74 actionPerformed (Methode der Schnittstelle ActionListener) 42 berschreiben 42 actionPerformed (Methode) 74 ActionPerformed(), Methode 351 activeCount(), Methode 420 activeGroupCount(), Methode 420, 422 add-Methoden 322 addActionListener (Methode der Klasse Button) 39, 42 addActionListener (Methode der Klasse TextField) 24 addAdjustmentListener (Methode der Klasse Scrollbar) 133, 137
Adder-Applet 68 erstellen 74 Listing 78 Adder2-Applet, Listing 84 addImpl (Methode der Klasse ScrollPane) 155 addItemListener (Methode der Klasse Checkbox) 89, 92, 105 addMenu(), Methode 378 addNotify (Methode der Klasse Button) 39 addNotify (Methode der Klasse Checkbox) 89, 155 addNotify (Methode der Klasse Label) 70 addNotify (Methode der Klasse Scrollbar) 133 addNotify (Methode der Klasse ScrollPane) 155 addNotify (Methode der Klasse TextArea) 61 addNotify (Methode der Klasse TextField) 25 addTab() berladen 380 Methode 380-381 addTabbedPane(), Methode 379 Adjustment-Ereignisse 137, 335, 350 AdjustmentEvent (Klasse) 138,335 adjustmentValueChanged (Methode der Klasse AdjustmentEvent) 138 adjustmentValueChanged (Methode der Klasse Scrollbar) 148 ADK 167 AffineTransform (Klasse) 515 Methoden 515 Aktionsereignisse, von Schaltfl6chen 39 append (Methode der Klasse TextArea) 61 appendText (Methode der Klasse TextArea) 61 Applet 161, 163, 185-186 als Listener-Objekt 42 ausfhren 7 Definition 2 Gr(ße *ndern 18 initialisieren 28 kompilieren 4 Layoutkontrolle 68 neu zeichnen 13 nummerische Eingaben 68
Platzieren von Steuerelementen 68 Applet (Klasse) 11 Applet Viewer 7