Programmieren in Java Prof. Jürgen Sauer Programmieren in Java Skriptum zur Vorlesung im WS 2003/2004 1 Programmieren in Java Inhaltsverzeichnis 0. ÜBERSICHT ..................................................................................................................................................... 7 0.1 ZIEL DER VORLESUNG „PROGRAMMIEREN“ ................................................................................................... 7 0.2 JAVA-MASCHINE UND PROGRAMMIERSYSTEM ............................................................................................... 7 0.3 DER ERSTE VERSUCH ..................................................................................................................................... 9 0.3.1 Das erste Programm .............................................................................................................................. 9 0.3.2 Bestandteile eines Programms............................................................................................................. 15 1. EINFÜHRUNG IN DIE JAVA-PROGRAMMIERUNG ............................................................................ 16 1.1 ÜBERSICHT ZUR ENTWICKLUNG DER PROGRAMMIERSPRACHE JAVA ........................................................... 16 1.2 WAS IST JAVA? ........................................................................................................................................... 17 1.3 EINSTIEG IN DIE JAVA-PROGRAMMIERUNG .................................................................................................. 20 1.3.1 Die Software für die Java-Programmierung........................................................................................ 20 1.3.2 Applets und Anwendungen ................................................................................................................... 22 1.3.3 Darstellung von Programmabläufen.................................................. .................. .......................38 1.4 DIE OBJEKTORIENTIERUNG VON JAVA ......................................................................................................... 41 1.4.1 Grundlegende Konzepte....................................................................................................................... 41 1.4.2 Klassen des Pakets java.lang ............................................................................................................... 71 1.4.3 Ausnahmen und Ausnahmenbehandlung ........................................................................................... 102 1.4.4 Ein-, Ausgaben................................................................................................................................... 109 2. HAUPTBESTANDTEILE DER SPRACHE .............................................................................................. 123 2.1 TOKEN ....................................................................................................................................................... 123 2.1.1 Schlüsselworte.................................................................................................................................... 124 2.1.2 Bezeichner und Namenskonventionen................................................................................................ 124 2.1.3 Literale............................................................................................................................................... 125 2.1.4 Trennzeichen ...................................................................................................................................... 127 2.1.5 Operatoren......................................................................................................................................... 128 2.1.6 Kommentare, eingebettete Dokumentation ........................................................................................ 133 2.2 TYPEN ........................................................................................................................................................ 135 2.2.1 Primitive Datentypen ......................................................................................................................... 135 2.2.2 Operationen mit primitiven Datentypen............................................................................................. 136 2.2.3 Datenfelder (Arrays) .......................................................................................................................... 139 2.3 AUSDRÜCKE ............................................................................................................................................... 151 2.3.1 Arithmetische Ausdrücke.................................................................................................................... 151 2.3.2 Bewertung von Ausdrücken................................................................................................................ 151 2.3.3 Typkonvertierungen ........................................................................................................................... 152 2.3.4 Vergleichsoperatoren......................................................................................................................... 155 2.3.5 Logische Ausdrücke ........................................................................................................................... 155 2.4 ANWEISUNGEN ........................................................................................................................................... 156 2.4.1 Blöcke und Anweisungen ................................................................................................................... 156 2.4.2 Leere Anweisungen ............................................................................................................................ 156 2.4.3 Benannte Anweisungen ...................................................................................................................... 156 2.4.4 Deklarationen .................................................................................................................................... 156 2.4.5 Ausdrucksanweisungen ...................................................................................................................... 156 2.4.6 Auswahlanweisungen ......................................................................................................................... 157 2.4.7 Iterationsanweisungen ....................................................................................................................... 160 2.4.8 Sprung-Anweisungen ......................................................................................................................... 162 2.4.9 Synchronisationsanweisungen ........................................................................................................... 166 2.4.10 Schutzanweisungen .......................................................................................................................... 166 2.4.11 Unerreichbare Anweisungen............................................................................................................ 166 2.5 KLASSEN .................................................................................................................................................... 167 2.6 METHODEN ................................................................................................................................................ 168 2.6.1 Die Deklaration ................................................................................................................................. 168 2.6.2 Die Zugriffsspezifizierung .................................................................................................................. 168 2.6.3 Die Methodenmodifizierer ................................................................................................................. 168 2.6.4 Rückgabewerte von Methoden ........................................................................................................... 169 2 Programmieren in Java 2.6.5 Methodenname und Parameterliste ................................................................................................... 169 2.6.6 Rekursion ........................................................................................................................................... 169 3. GRAFISCHE BENUTZEROBERFLÄCHEN UND APPLETS............................................................... 171 3.1 EREIGNISSE IN GRAFISCHEN BENUTZEROBERFLÄCHEN .............................................................................. 171 3.1.1 Gestaltung von GUI mit Hilfe der AWT-Klassen ............................................................................... 171 3.1.2 Ereignisbehandlung unter grafischen Benutzeroberflächen.............................................................. 175 3.1.3 Anwendung lokaler Klassen für die Ereignisbehandlung .................................................................. 178 3.1.4 Externe und interne Darstellung von numerischen Werten ............................................................... 182 3.1.5 Low-Level-Events............................................................................................................................... 183 3.1.6 Der Weg eines Ereignisses................................................................................................................. 189 3.2 KOMMUNIKATION ANWENDER – PROGRAMM ÜBER DIALOGE BZW. MENÜS .............................................. 191 3.2.1 Dialoge............................................................................................................................................... 191 3.2.2 Menüs................................................................................................................................................. 198 3.2.3 Popup-Menüs ..................................................................................................................................... 200 3.3 GRUNDLAGEN DER APPLET-ERSTELLUNG .................................................................................................. 202 3.3.1 HTML-Grundlagen ............................................................................................................................ 202 3.3.2 Die interne Arbeitsweise eines Applets .............................................................................................. 204 3.3.3 „Multithreading“-fähige Applets....................................................................................................... 210 3.3.4 Animation in Applets.......................................................................................................................... 213 3.3.5 Das Laden und Anzeigen von Bildern................................................................................................ 218 3.3.6 Die Ausgabe von Sound ..................................................................................................................... 219 3.3.7 Die Klasse JApplet ............................................................................................................................. 220 3.3.8 Das Interface AppletContext .............................................................................................................. 221 3.4 GRAFISCHE BENUTZEROBERFLÄCHE MIT OBSERVABLE-OBSERVER ........................................................... 222 3.5 SWING ........................................................................................................................................................ 225 3.6 JAVA BEANS............................................................................................................................................... 226 3.6.1 Wiederverwendbare Softwarekomponenten....................................................................................... 226 3.6.2 Komponenten für Java: Beans ........................................................................................................... 227 3.6.3 Enterprise Java Beans ....................................................................................................................... 228 4. GRAFIK UND BILDVERARBEITUNG.................................................................................................... 229 4.1 ALLGEMEINE ZEICHENVORGÄNGE ............................................................................................................. 230 4.1.1 Punkte, Linien, Kreise, Bögen, Polygone........................................................................................... 230 4.1.2 Farbangaben...................................................................................................................................... 236 4.1.3 Textausgabe über den Zeichen-Modus .............................................................................................. 237 4.1.4 Grössenangaben ................................................................................................................................ 240 4.1.5 Clipping-Operationen ........................................................................................................................ 241 4.2 BILDVERARBEITUNG .................................................................................................................................. 241 4.2.1 Klassen zur Bildverarbeitung............................................................................................................. 242 4.2.2 Bildproduzenten und Bildkonsumenten.............................................................................................. 245 4.2.3 Bildfilter ............................................................................................................................................. 246 4.2.4 Kopieren von Speicher in ein Bild bzw. von Bildern in einen Speicher............................................. 250 4.3 2D JAVA..................................................................................................................................................... 253 4.3.1 Das Zeichnen unter Java 2D-API ...................................................................................................... 254 4.3.2 Eigenschaften geometrischer Objekte................................................................................................ 261 4.3.3 Koordinatensysteme und Transformation .......................................................................................... 264 4.3.4 Java2D-Pipeline ................................................................................................................................ 257 4.3.5 Farbmanagement unter Java2D ........................................................................................................ 258 4.3.6 Text und Fonts unter Java2D.............................................................................................................264 4.3.7 2D Bildverarbeitung...........................................................................................................................266 4.3.8 Animation unter Java 2D....................................................................................................................267 5. AWT............................................................................................................................................................... 280 5.1 BESTANDTEILE DES AWT .......................................................................................................................... 280 5.2 DIE AWT-KOMPONENTEN ......................................................................................................................... 280 5.2.1 Schaltflächen (Buttons) ...................................................................................................................... 281 5.2.2 Labels................................................................................................................................................. 284 5.2.3 Kontrollkästchen und Optionsfelder .................................................................................................. 284 5.2.4 Auswahlmenüs.................................................................................................................................... 288 3 Programmieren in Java 5.2.5 Listenfelder ........................................................................................................................................ 289 5.2.6 Textbereiche und Textfelder............................................................................................................... 290 5.2.7 Schieber und Bildlaufleisten .............................................................................................................. 293 5.2.8 ScrollPane.......................................................................................................................................... 296 5.2.9 Zeichenbereiche ................................................................................................................................. 297 5.3 CONTAINER ................................................................................................................................................ 300 5.3.1 Panels................................................................................................................................................. 300 5.3.2 Frames ............................................................................................................................................... 300 5.3.3 Menüs................................................................................................................................................. 301 5.3.4 Dialoge............................................................................................................................................... 305 5.4 DIE LAYOUT-MANAGER ............................................................................................................................ 307 5.4.1 Layout-Regeln .................................................................................................................................... 307 5.4.2 Die einzelnen Layout-Manager.......................................................................................................... 308 5.5 DIE EVENT-MODELLE 1.0 UND 1.1............................................................................................................. 316 5.5.1 Der AWT-Handler 1.0........................................................................................................................ 316 5.5.2 Das Event-Handling in Java 1.1 bzw. Java 1.2 ................................................................................. 318 5.6 BENUTZERSCHNITTSTELLEN MIT SWING .................................................................................................... 324 5.6.1 Die Komponentenstruktur...................................................................................................................313 5.6.2 Fenster................................................................................................................................................318 5.6.3 Menüs.................................................................................................................................................321 5.6.4 Die Container-Klassen JToolBar, JSpliPane, JScrollPane, JTabbedPane........................................328 5.6.5 Swing-Komponenten..........................................................................................................................330 6. UTILITIES .................................................................................................................................................... 390 6.1 KOLLEKTIONEN (COLLECTIONS) ................................................................................................................ 390 6.1.1 Durchwandern von Daten mit Iteratoren........................................................................................... 390 6.1.2 Die Klasse Vector............................................................................................................................... 391 6.1.3 Die Klasse Stack................................................................................................................................. 393 6.1.4 Die Klasse Bitset für Bitmengen ........................................................................................................ 396 6.1.5 Die Klasse Hashtable und assoziative Speicher ................................................................................ 398 6.1.6 Die abstrakte Klasse Dictionary ........................................................................................................ 402 2.2.7 Die Klasse Properties ........................................................................................................................ 403 6.2 COLLECTION API ....................................................................................................................................... 404 6.2.1 Die Schnittstellen Collection, Iterator, Comparator.......................................................................... 404 6.2.2 Die Behälterklassen und Schnittstellen des Typs List ........................................................................ 406 6.2.3 Behälterklassen des Typs Set ............................................................................................................. 410 6.2.4 Behälterklassen des Typs Map ........................................................................................................... 413 6.2.5 Algorithmen........................................................................................................................................ 415 6.3 DIE KLASSE STRINGTOKENIZER................................................................................................................. 419 6.4 DIE KLASSE RANDOM ................................................................................................................................ 420 6.5 DATUMSBERECHNUNGEN ........................................................................................................................... 421 6.5.1 Die Klassen Date, Calendar, GregorianCalendar ............................................................................ 421 6.5.2 Formatieren der Datumsangaben...................................................................................................... 425 6.6 FORMATIEREN MIT FORMAT-OBJEKTEN ..................................................................................................... 427 7. EIN- UND AUSGABE .................................................................................................................................. 428 7.1 DIE ABSTRAKTEN KLASSEN INPUTSTREAM UND OUTPUTSTREAM ............................................................. 430 7.1.1 InputStream........................................................................................................................................ 430 7.1.2 OutputStream ..................................................................................................................................... 432 7.2 GEFILTERTE STRÖME ................................................................................................................................. 433 7.3 DIE KLASSE FILE........................................................................................................................................ 436 7.4 DIE RANDOMACCESSFILE-KLASSE ............................................................................................................ 438 7.5 DIE KLASSE STREAMTOKENIZER ............................................................................................................... 440 7.6 KLASSEN FÜR SPEZIELLE NÜTZLICHE STRÖME ........................................................................................... 442 7.7 JAVA 1.1 IO-STRÖME ................................................................................................................................. 443 7.7.1 Grundlagen ........................................................................................................................................ 443 7.7.2 Die abstrakte Klassen Reader und ihre Ableitungen ......................................................................... 444 7.7.3 Die abstrakte Klasse Writer und ihre Ableitungen ............................................................................ 446 7.7.4 Demonstrationsprogramm zur Ein-/ Ausgabe ab Java Version 1.1 .................................................. 449 4 Programmieren in Java 8. SERIALISIERUNG...................................................................................................................................... 451 8.1 GRUNDLAGEN ............................................................................................................................................ 451 8.1.1 Das Interface Serializable.................................................................................................................. 451 8.1.2 Die Klasse ObjectOutputStream ........................................................................................................ 452 8.1.3 Die Klasse ObjectInputStream........................................................................................................... 454 8.2 TIEFE OBJEKTKOPIEN ................................................................................................................................. 456 9. NETZWERKPROGRAMMIERUNG ........................................................................................................ 457 9.1 ADRESSEN, RESSOURCEN UND URLS ......................................................................................................... 457 9.1.1 Die Klasse InetAddress ...................................................................................................................... 457 9.1.2 Die Klasse URL.................................................................................................................................. 458 9.1.3 Die Klasse URLConnection und abgeleitete Klassen ........................................................................ 462 9.2 KOMMUNIKATION IN NETZWERKEN ........................................................................................................... 463 5 Programmieren in Java Literaturverzeichnis Alexander Newman u.a.: JAVA, Referenz und Anwendungen, 1996, Haar bei München : Que Ralph Steyer: JAVA 1.2, Kompendium, 1998, Markt & Technik, Haar bei München Laura Lemay u. Roger Cadenhead; JAVA 2 in 21 Tagen, 1999, Markt & Technik, Haar bei München Guide Krüger: Go To Java 2, 1999, Addison-Wesley, München ... David Flanagan: Java in a Nutshell, 1996, O’Reilly&Associates Inc, Bonn ... Florian Hawlitzek: Java 2, 2000, Addison-Wesley, München ... Christian Ullenboom: Java ist auch eine Insel, GalileoPress, Bonn 2002 6 Programmieren in Java 0. Übersicht 0.1 Ziel der Vorlesung „Programmieren“ „Vermittlung grundlegender Kenntnisse, die zum Lösen von Problemen mit einer Rechenanlage (Computer, Rechner) nötig sind.“ Die Rechenanlage ist „irgendeine“ (vom „Jumbo“ bis zum Personal Computer) und muß - eine Eingabemöglichkeit für Ziffern, Buchstaben und Sonderzeichen besitzen (Tastatur) - programmierbar sein - eine Ausgabemöglichkeit haben (Bildschirm) Zum Erwerb grundlegender Kenntnisse für das Lösen von Problemen auf einem Universalrechner ist - zu zeigen, wie man von der Problemstellung zum Lösungsverfahren (Algorithmus) kommt. Alle Probleme, für die ein Lösungweg genügend präzise formuliert werden kann, sind über den Rechner lösbar. - zu üben: Der Umgang mit den Algorithmen. Dazu wird die Programmiersprache Java benutzt, in der die Algorithmen formuliert werden. Der in Java formulierte Lösungsweg (Algorithmus) ist das JavaProgramm (Anwendung, Applet), der Rechner auf dem das Programm ausführbar ist, ist die JavaMaschine. Java ist eine Sprache, die die Arbeitsanweisungen eines Programmierers einfach und anschaulich darstellen kann. Java wurde ab 1991 bei Sun Microsystems entwickelt. Verantwortlich für Java ist JavaSoft, eine Tochterfirma von Sun Microsystems. Java und JavaSoft1 halten permanent die aktuellsten Informationen im Internet bereit. 0.2 Java-Maschine und Programmiersystem Die Java-Maschine2 nutzt die Möglichkeiten des Universalrechners. Die Eingabemöglichkeit dient zum Einschreiben von Daten und Programmen in den Speicher der Zentraleinheit (central processing unit, CPU). Die Zentraleinheit verarbeitet die eingegebenen Daten nach den Anweisungen des eingelesenen Programms, das zur Ausführung in binäre Form gebracht wurde. Das Steuerwerk überwacht die Ausführung des Programms (Entschlüsseluung der binär dargestellten Anweisungen). Das eigentliche Abarbeiten der Programmanweisungen (arithmetische, logische Operatoren) in der gegebenen Reihenfolge übernimmt das Rechenwerk. Daten, Programme, die im Hauptspeicher keinen Platz finden, sind auf externen Speichermedien ausgelagert (Magnetband, Magnetplatte, Floppy Disc). 1 http://java.sun.com Die virtuelle Maschine von Java (Java VM) ist wirklich eine Maschine, d.h. Hardware, die bestimmte Operationscodes (Maschinenanweisungen) interpretieren kann. Es wurden bzw. werdem Chips entwickelt, die diese Maschine in Hardware implementieren. Im Regelfall geschieht aber eine Emulation durch das Laufzeitsystem auf der jeweiligen Zielplattform. 2 7 Programmieren in Java Eingabeeinheit Hauptspeicher Externer Speicher Steuerwerk Ausgabeeinheit Rechenwerk Zentraleinheit Abb. 0.2-1: Prinzipieller Aufbau eines Universalrechners Der Universalrechner ist vielfältig anwendbar. Welche Anwendungsmöglichkeit (z.B. Java-Maschine) gewünscht wird, gibt der Benutzer durch Systemkommandos bekannt. Diese Kommandos werden von Systemprogrammen entschlüsselt und anschließend interpretiert. Eines dieser Kommandos verwandelt, falls die dazu nötigen Programme vorhanden sind, den Universalrechner in eine Java-Maschine. Für die Java-Maschine sind danach die Java-Programme des Benutzers die Quelle aller Erkenntnisse. Quellprogramme werden über die Tastatur eingegeben und auf externen Speicher abgelegt. Zur Eingabe, Korrektur und Änderung stellt das System ein Programm mit dem Namen „Editor" zur Verfügung. Das Speichern der eingegebenen bzw. geänderten Programme auf Externspeicher übernimmt das Systemprogramm „Dateiverwaltung“. Java-Programm Daten Eingabe (Tastatur) Externer Speicher System, Systemprogramme Java-Maschine Ausgabe (Bildschirm) 8 Externer Speicher Java-Compiler Java-Quellprogramme Java-Laufzeitsystem Java-BytecodeDateien Programmieren in Java 0.3 Der erste Versuch 0.3.1 Das erste Programm Quelltext: import java.lang.*; /* ErstesProgramm ist eine Applikation, die den einfachen Gebrauch von Zeichenketten aufzeigt */ public class ErstesProgramm extends Object { // Beginn der Ausfuehrung vom Programm public static void main(String args[]) { // Ausgabe auf das Standard-Ausgabegeraet System.out.println( "Das erste Programm der Vorlesung Programmieren in Java."); } } Das Programm umfaßt zwei Teile: eine Klassendefinition und einen Programmabschnittt, der durch die Methode main() bestimmt ist. Jede Java-Anwendung besteht aus mehreren Klassen. Die Klasse, die die Ausgangsbasis für Java-Programme ist, muß die main()-Methode benutzen: public static void main(String args[]) { ... }. Es bedeuten: public ... static ... main() ... Die Methode ist für andere Klassen und Objekte verfügbar Es handelt sich um eine Klassenmethode Die Funktion main() hat einen Parameter vom Typ Zeichenkette (String). Dieser Parameter dient zur Aufnahme von Befehlszeilenargumente. Argumente, die an Java-Programme übergeben werden, werden zu Zeichenketten konvertiert. In JavaProgrammen ist main(), wie in C/C++-Programmen, die erste Routine des Programms, die ausgeführt wird. Durch die „import“-Anweisung können Entwickler Klassen verwenden, die in anderen Dateien definiert sind. Compiler bzw. Interpreter greifen auf die Dateien zu. Über „import“ wird bestimmt, wo diese Dateien liegen. Java importiert immer das Paket „java.lang“, denn hier ist die Klasse Object enthalten, von der alle JavaKlassen abgeleitet sind. Das Paket „java.lang“, die unmittelbare Ableitung von der Klasse Object werden in Java per default bereitgestellt. Die diesbezüglichen Angaben im Programm können entfallen, der Quelltext kann deshalb auch folgende Gestalt annehmen: /* ErstesProgramm ist eine Applikation, die den einfachen Gebrauch von Zeichenketten aufzeigt */ public class ErstesProgramm { // Beginn der Ausfuehrung vom Programm public static void main(String args[]) { // Ausgabe auf das Standard-Ausgabegeraet System.out.println( "Das erste Programm der Vorlesung Programmieren in Java."); } } 9 Programmieren in Java Token Die Ausdrucksformen der Sprache Java werden Token genannt. In derartige für die Sprache Java sinnvolle Einheiten muß sich der Quelltext eines Java-Programms zerlegen lassen. Es gibt in Java fünf Arten von Token: Bezeichner oder Identifizierer, Schlüsselworte, Literale, Operatoren, Trennzeichen. Bezeichner, Identifizierer: Darunter versteht man die Benennungen (Namen) für Klassen, Objekte, Variable, Methoden. Namen sind zusammengesetzt aus UnicodeZeichen. Java benutzt den 16-Bit-Unicode-Zeichensatz, dessen erste 256 Zeichen dem normalen ASCII-Zeichensatz entsprechen (Byte 1 ist immer auf 0 gesetzt). Im vorliegenden Programm sind z.B. Bezeichner: ErstesProgramm, main, System. Java unterscheidet Groß-/ Kleinschreibung. Namen bestehen in der Regel aus alphabetischen Zeichen und Dezimalziffern. An der ersten Stelle darf keine Zahl stehen. Schlüsselwörter: Das sind Wörter, die ein wesentlicher Teil der JavaSprachdefinition sind, z.B.: public, class, void, String. Literale: mit einem Literal können Variablen und Konstanten bestimmte Werte zugewiesen werden. Literale können annehmen: numerische Werte (z.B. 13), boolesche Werte (true bzw. false), Zeichen (z.B. ‘A‘) und Zeichenketten (z.B. "Das erste Programm der Vorlesung Programmieren in Java."). Operatoren: Das sind Zeichen bzw. Zeichenkombinationen zur Angabe einer auszuführenden Operation mit einer oder mehreren Variablen oder Konstanten (Operanden), z.B. + - / <<< >>>. Variable sind Arbeitsspeicherstellen, an denen Informationen gespeichert werden können. Zur Deklaration erhält eine Variable Namen (Bezeichner) und Typ zugeordnet. Der Typ kennzeichnet die Art der Information, die die Variable aufnehmen kann, z.B.: int i; // zur Aufnahme ganzer Zahlen String s; // zur Aufnahme von Zeichenketten In Java unterscheidet man: elementare (primitive Typen) und benutzerdefinierte Typen (einschl. der vom System bereitgestellten Klassen: String und Array). Es gibt acht primitive Typen zum Speichern von ganzen Zahlen (byte, short, int, long), Gleitpunktzahlen (float, double), Zeichen (char) und booleschen Werten (boolean). Sobald die Variable deklariert wurde, kann ihr über den Zuweisungsoperator („=“) ein Wert zugewiesen werden, z.B. innerhalb der main()-Methode public static void main(String args[]) { String s; // Deklaration der Variablen s zur Aufnahme von Zeichenketten // Zuweisung s = "Das erste Programm der Vorlesung Programmieren in Java." // Aufruf der Methode println() der Klasse System, die sich im Paket // java.lang befindet System.out.println(s); } 10 Programmieren in Java Vorrang 1 2 3 4 5 6 7 8 9 10 11 12 13 Operator ++ Operandentyp Arithmetisch -- Arithmetisch +, - Arithmetisch ~ Integral ! boolean (type) *, /, % irgendein arithmetisch +, + << >> Arithmetisch String Integral Integral >>> Integral <, <= Arithmetisch >, >= Arithmetisch instanceof == != Objekt, Typ Primitiver Typ Primitiver Typ == Objekt != Objekt & & ^ ^ Integral boolean Integral boolean | | && || ?= Integral boolean boolean boolean boolean, irgendein, irgendein Variable, irgendein R Variable, irgendein R = *=, /=, +=, -= <<=, >>=, >>>=, &=, ^=, |= Abb. 0.3-1: Java-Operatoren 11 Assoziation Operation R Pre- oder Post-Inkrement (unär) R Pre- oder PostDekrement (unär) R Unäres Plus, unäres Minus R Bitweises Komplement (unär) R Logisches Komplement (unär) R cast L Multiplikation, Division, Rest L Addition, Subtraktion L Verkettung L Links-Shift L Rechts-Shift mit Vorzeichen L Rechts-Shift mit NullenNachziehen L Kleiner als, kleiner als oder gleich L Größer als, größer als oder gleich L Objekt?, Instanz? L Gleich (identische Werte) L Ungleich (verschiedene Werte) L Gleich (Referenz auf das gleiche Objekt) L Ungleich (Referenz auf verschiedene Objekte) L Bitweises Und L Logisches Und L Bitweises Oder L Logisches Oder (exklusiv) L Bitweises Oder L Logisches Oder (inklusiv) L Konditionelles Und L Konditionelles Oder R Konditioneller (ternärer) Operator Zuweisung Zuweisung mit Operation Programmieren in Java System.out.println(s); ist eine Anweisung. Anweisungen stehen für eine einzelne Aktion, die in einem Java-Programm ausgeführt wird. Jede Anweisung wird mit einem Strichpunkt „;“ abgeschlossen. Einige Anweisungen erzeugen einen Wert, wie das bspw. beim Addieren zweier Zahlen der Fall ist. Derartige Anweisungen werden als Ausdrücke bezeichnet. Ein Ausdruck ist eine Anweisung, die als Ergebnis einen (Rückgabe-) Wert produziert. Dieser Wert kann zur späteren Verwendung im Programm gespeichert, direkt in einer anderen Anweisung verwendet oder überhaupt nicht beachtet werden. Ausdrücke können Konstanten, Variablen, Operatoren beinhalten. Ausdrücke sind vielfach mit unären und binären Operatoren verknüpft. Trennzeichen: Das sind Symbole und Zusammenfassungen von Quellcode: ( ) { } [ ] ;. ,. „(“: wird sowohl zum Öffnen einer Parameterliste für eine Methode als auch zur Festlegung der Priorität für Operationen in einem Ausdruck benutzt. „)“: wird sowohl zum Schließen einer Parameterliste für eine Methode als auch zur Festlegung der Priorität für Operationen in einem Ausdruck benutzt. Bsp.: public static void main(String args[]) „{“: wird zu Beginn eines Blocks mit Anweisungen oder einer Initialisierungsliste gesetzt. „}“:wird an das Ende eines Blocks mit Anweisungen oder einer Initialisierungsliste gesetzt. Bsp.: Methoden werden in Java durch einen Anweisungsblock bestimmt. Ein Anweisungsblock besteht in der Regel aus einer Reihe von Anweisungen, die von geschweiften Klammern umschlossen sind. public static void main(String args[]) { String s; s = "Das erste Programm der Vorlesung Programmieren in Java."; System.out.println(s); } „[“: steht für eine Ausdruck, der als Index für ein Datenfeld (Array) steht. „]“: folgt einem Ausdruck, der als Index für ein Datenfeld steht. Bsp.: String args[] = {"Juergen", "Hubert", "Josef", "Liesel", "Christian"}; definiert einen Array mit 5 Komponenten, die alle den gleichen Typ besitzen. Datenfelder (Arrays) dienen zum Speichern von Elementen, die alle denselben Typ aufweisen. Jedem Element wird innerhalb des Datenfelds ein eigener Speicherplatz zugewiesen. Dieser Speicherplatz ist für den leichten Zugriff durchnumeriert. [0] [1] [2] [3] [4] "Juergen" "Hubert" "Josef" "Liesel" "Christian" Auf den Wert einer Komponenten kann über den Array-Namen (z.B. args), gefolgt von einem Index in eckigen Klammern (z.B. [0]) zugegriffen werden. Mit args[0] = "Roland"; kann dem ersten Element des Array args ein neuer Wert zugewiesen werden. „;“: dient zum Beenden einer Anweisung. „,“: wird häufig als Begrenzer (z.B. in einer Parameterliste) benutzt. „.“: wird als Dezimalpunkt als auch zum Trennen solcher Dinge wie Paketnamen von Klassennamen oder Variablennamen benutzt (z.B. System.out.println(s);). 12 Programmieren in Java Standardmäßig haben Klassen Zugriff auf die Klassen im Paket „java.lang“. Zur Bezugnahme auf eine Klasse, die sich nicht in java.lang befindet, sind alle Pakete, in denen sich die Klasse befindet, anzugeben, z.B. java.awt.Color // bezieht sich auf die Klasse Color im Paket awt, // das sich im Paket java befindet. Leerraumzeichen: Sie können in beliebiger Anzahl und an jedem Ort zwischen Token (die eine Funktion besitzen) zur übersichtlichen Gestaltung des Quellcodes plaziert werden. Solche Zeichen sind bspw.: Space, Tab, Zeilenende, Formularvorschub Kommentar: Er wird vom Compiler ignoriert. Man unterscheidet den Kommentar bis zum Zeilenende „//“ und den eingebetteten Kommentar „/* ... */“, z.B.: /* ErstesProgramm ist eine Applikation, die den einfachen Gebrauch von Zeichenketten aufzeigt */ Übersetzung und Ausführung. Befindet sich der Quelltext zum Programm in einer Datei mit dem Namen ErstesProgramm.java, dann kann dieses Programm durch Aufruf des JavaCompilers javac in ablauffähigen Bytecode übersetzt werden. Das geschieht über das folgende Systemkommando: javac ErstesProgramm.java Den Byte-Code, den der Java-Compiler in der Datei ErstesProgramm.class hinterlegt hat, kann über das Kommando java ErstesProgramm zur Ausführung gebracht werden. 13 Programmieren in Java Konsole: Arbeitsspeicher: javac ErstesProgramm.java Externspeicher ErstesProgramm.java ErstesProgramm.class java ErstesProgramm ErstesProgramm.class Java (-Interpreter) "Das erste Programm der Vorlesung Programmieren in Java" Abb. 0.3-2: Ablaufplan ErstesProgramm 14 Programmieren in Java 0.3.2 Bestandteile eines Programms Wesentliche Programmelemente in Java sind: - Anweisungen Anweisungen3 gehören zu den elementaren ausführbaren Programmelementen. Eine Anweisung kann eine Deklaration enthalten, einen Ausdruck4 auswerten oder den Programmablauf5 (Auswahl-, Iterations-, Sprung-Anweisungen und return-, throw-Anweisung) steuern. - Blöcke Ein Block6 ist eine Zusammenstellung von Anweisungen, die nacheinander ausgeführt werden. Ein Block kann eigene Variable definieren, die nur innerhalb des Blocks sichtbar sind. Sie werden beim Aufruf des Blocks angelegt und beim Verlassen des Blocks zerstört. Innerhalb eines Blocks sind nur die lokalen Variablen des Blocks und die lokalen Variablen des umgebenden Blocks bzw. der umgebenden Methode sichtbar. Nach außen stellt sich der Block als eine einzige Anweisung dar. - Methoden Methoden7 unterscheiden sich von Blöcken folgendermaßen: -- Sie haben einen Namen und können von verschiedenen Stellen des Programms aufgerufen werden. -- Sie sind parametrisierbar -- Sie können einen Rückgabewert besitzen. Methoden werden in Java immer lokal zu einer Klasse definiert. - Klassen Sie8 enthalten Variablen zur Beschreibung des Zustands von Objekten und Methoden zur Beschreibung des Verhaltens von Objekten. - Schnittstellen Eine Schnittstelle (Interface) ist eine Sammlung von Methoden, die einen Namen besitzen, aber nicht implementiert sind. Ein Klasse kann beliebig viele Schnittstellen implementieren. Dadurch wird die Klasse zur Implementierung der Methode gezwungen, deren Namen von der Schnittstelle definiert wurden. Falls zwei unterschiedliche Klassen, dieselbe Schnittstelle implementieren, können beide auf Aufrufe der Methode, die in der Schnittstelle definiert sind, reagieren. Allerdings kann die Reaktion auf diese Methodenaufrufe bei einzelnen Klassen total unterschiedlich sein. - Pakete Ein Paket ist eine Sammlung von Klassen. Jede Klasse in Java gehört zu einem Paket. Pakete ermöglichen, daß Sammlungen von Klassen bei Bedarf verfügbar sind. Die Klassenbibliotheken befinden sich in einem Paket mit dem Namen „java“. Dieses Paket beinhaltet Pakete, die spezielle Bestandteile der Sprache Java, z.B. Dateieingabe und Datenausgabe, Multimedia, etc. definieren. Standardmäßig haben Klassen der Anwender nur Zugrifff auf Klassen im Paket „java.lang“ (Standard-Feature). Klassen irgendeines anderen Pakets müssen importiert werden. - Applikationen (Anwendungen) Anwendungen (Applikationen) bilden die eigenständigen Programme. Sie benötigen zur Ausführung keinen Browser, sondern nur den Java-Interpreter und die .class-Dateien der verwendeten Klassen. - Applets Applets sind ebenfalls lauffähige Java-Programme. Sie werden aus einer HTML-Seite aufgerufen und benötigen zur Ausführung einen Web-Browser (oder ein Werkzeug wie den Appletviewer). Applets müssen von der Klasse Applet abgeleitet und nach den Regeln dieser Klasse aufgebaut sein. Zum Starten des Programms erzeugt der Browser eine Instanz der abgeleiteten Klasse und ruft eine Reihe vordefinierter Callback-Methoden9 auf. 3 vgl. 2.4 vgl. 2.4.5 5 vgl. 2.4.6 6 vgl. 2.4.1 7 vgl. 2.6 8 vgl. 2.5 9 CallBack-Methoden sind von der abgeleiteteten Klasse zur Verfügung gestellte Methoden, die vom Browser bzw. Appletviewer aufgerufen werden 4 15 Programmieren in Java 1. Einführung in die Java-Programmierung 1.1 Übersicht zur Entwicklung der Programmiersprache Java Java ist in den Entwicklungslaboren der amerikanischen Firma Sun Microsystems10 entstanden. Man entschied sich bei Sun zur Realisierung eines im Jahre 1990 begonnenen Projekts11 für eine neue Programmiersprache, da bisher entwickelte Programme mit vorliegenden Programmiersprachen zu große Schwächen zeigten. Der erste Versuch war nur bedingt erfolgreich. Lediglich der damals im Internet verbreitete Mosaic-Browser12 wurde zu einer Zielplattform der neuen Programmiersprache13, die Ende 1994 für das Internet umgearbeitet wurde und über das Netz frei und umsonst verteilt wurde. 1995 wurde die neue Programmiersprache mit dem Namen Java14 der InternetÖffentlichkeit in Kombination mit einem Browser, HotJava, präsentiert. HotJava war die erste komplexe und vollständig in Java geschriebene Anwendung, der erste Java-fähige Browser und damit die Präsentationsform für die ersten Java-Applets. Außerdem war dieser Browser eine wesentliche Ergänzung des ersten JavaEntwicklungstools von Sun – das Java Develelopment Kit (JDK 1.0). Ein kommerzielles Produkt, der Java Workshop15, wurde kurz nach der Präsentation von JDK 1.0 bereitgestellt. Natürlich gab es im JDK noch diverse Kinderkrankheiten. Im zweiten Quartal 1997 folgte deshalb nach einigen Zwischenversionen die Version 1.1 des JDK. Parallel zur 10 Sun ist eine der führenden Hersteller von Workstations Entwicklung eines vollkommen neuen, plattformunabhängigen Betriebssystems für den „Consumerbereich der allgemeinen Elektronik (Telefone, Videorecorder, Waschmaschinen, Kaffemaschinen; eigentlich alle elektrischen Maschinen, die Daten benötigen) 12 der erste WWW-Browser mit einer grafischen Benutzeroberfläche. WWW steht für World Wide Web und ist inzwischen die wichtigste Stütze im Internet. Das WWW ist im wesentlichen durch sog. Hypertexte aufgebaut, die mit der Sprache HTML entwickelt wurden und werden. Ein Hypertext ist im wesentlichen ein ASCII-Text, der durch maskierte Wörter (sog. Hyperlinks) zu weiteren Seiten führt. Hypertext ist eigentlich nur ein Text mit Verweisen auf andere Texte. Der Verweis auf den weiterführenden Text kann aktiviert werden (z.B. durch Mausklick), und es wird zu dem gewünschten Text verzweigt. Das Hypertext Transfer Protocol (HTTP) dient zur Übertragung von Informationen aus dem WWW. HTTP ist ein objektorientiertes Protokoll (TCP/IP-Programm) zur einfachen Übertragung von Hypertext-Dokumenten zwischen Client und Server. Client-Programme, die HTTP benutzen, werden (in der Regel) als Web-Browser, Server-Programme als Web-Server bezeichnet. Der Browser schickt an den Server die Aufforderung eine bestimmte HTML-Seite zu übertragen. Falls er in dieser Seite weitere Verweise (z.B. auf Bilder) entdeckt, schickt er weitere Übertragungswünsche hinterher. Das Besorgen der gewünschten Dokumente erfolgt über ein einheitliches Adressierungsschema, dem Uniform Resource Locater (URL), durch den Internet-Standort und die Art der zu übertragenden Information identifiziert werden. 13 Dem WWW mit dem bis zu diesem Zeitpunkt realisierten Stand der HTML fehlten: dreidimensionale Darstellung der Objekte, eine bewegte Animation und eine Möglichkeit zur vernünftigen Interaktion mit dem Anwender. Deshalb waren hier die Multimedia- und Interaktionseigenschaften der neuen Programmiersprache besonders erfolgreich. 14 verantwortlich für Java ist die Firma JavaSoft – eine Tochterfirma von Sun Microsystems. Sun bzw. JavaSoft halten im Internet permanent die aktuellste Information von Java bereit. Einige der Informationen findet man bereits auf der Einstiegseite von Sun (http://java.sun.com), andere Informationen bekommt man von der Neuigkeitenseite (http://java.sun.com/nav/new/index.html) 15 mit Test- und Debug-Möglichkeiten, einem Referenzcompiler, einer integrierten Entwicklungsumgebung mit Editor, Browser, Project-, Portfolio- und Build-Manager, Debugger, Project-Tester und Online-Hilfe. Der Workshop geht mit der Version 2.0 inzwischen in eine neue Phase zur Unterstützung des neuen Java. 11 16 Programmieren in Java 1.1.x-Version gab es auch einen neuen Hot-Java-Browser zur Unterstützung der neuen 1.1-API-Funktionen. Java 1.2 ist die neueste Version. In Verbindung mit dem JDK 1.2 wurde der Begriff Java 216 eingeführt. Inzwischen gibt es schon die Version 1.3, die nur wenige neue Programmierschnittstellen bietet, dafür aber eine komfortablere und schnellere Laufzeitumgebung. Seit Version 1.2 ist Java in verschiedenen Ausgaben (Editionen) erhältlich. Sun hat drei verschiedene Editionen mit unterschiedlicher Ausrichtung definiert: 1. Standard-Edition für den Desktop Client oder PC 2. Enterprise-Edition für Application Server 3. Micro-Edition für Kleingeräte. Am weitesten entwickelt ist die Standard-Edition. Neben dem JDK gibt es zahlreiche kommerzielle Entwicklungstools für JavaProgrammierer, z. B.: Symantec Visual Café, Borland JBuilder, SuperCade, National Intelligence Roaster, SunSoft Java Workshop. 1.2 Was ist Java? Java ist17 eine einfache, objektorientierte, dezentrale, interpretierte, stabil laufende, sichere, architekturneutrale, portierbare und dynamische Sprache, die Hochleistungsgeschwindigkeitsanwendungen und Multithreading unterstützt. - Java ist einfach, obwohl es sehr nahe an der ziemlich komplizierten C/C++-Syntax entworfen wurde. Die komplexen Teile von C/C++ wurden jedoch aus Java ausgeschlossen. In Java gibt es keine Zeiger (Pointer) und auch keine Zeiger-Arithmetik. Strings und Arrays sind echte Objekte. Die Speicherverwaltung erfolgt weitgehend automatisch. Die Ausdrücke in Java18 entsprechen aber weitgehend denen von C/C++. Java besitzt eine „if“Anweisung, eine „while“-, „do“- und „for“-Schleife und eine „switch“-Anweisung. Es gibt die von C bekannten „break“- und „continue“-Anweisungen (in normaler und mit einem „Label“ versehenen Form)19. - Java ist klein. Eine der ursprünglichen Ziele von Java war die Erleichterung der Software-Entwicklung für kleine Rechner. - Java ist objektorientiert. Es gehört zu einer Familie von Sprachen, die Daten als Objekte definieren und Methoden zur Bearbeitung dieser Objekte verwenden. Das objektorientierte Konzept von Java hat viel von C++ geerbt, aber auch Konzepte anderer objektorientierter Sprachen wurden übernommen. Wie die meisten objektorientierten Sprachen umfaßt Java eine umfangreiche Klassenbibliothek, die grundlegende Datentypen, Systemein- und Systemausgabe und andere Hilfsmittel (utilities) bietet. Die grundlegenden Klassen sind Teil des JDK, das darüber hinaus noch Klassen besitzt, die Funktionen im Zusammenhang mit in Netzwerken üblichen Internet-Protokollen und Benutzeroberflächen unterstützen. Da diese Klassenbibliotheken in Java geschrieben sind, sind sie, wie alle Java-Anwendungen, auf alle Plattformen portierbar. - Java ist dezentral und erfüllt eine wesentliche Eigenschaft von Client/Server-Anwendungen. Die Fähigkeit der Verteilung von Informationen für die Berechnung der Daten. „Dezentral“ beschreibt die Beziehung von Systemobjekten: Es ist gleichgültig, ob die Objekte sich auf lokalen oder entfernten 16 Die exakte Bezeichnung für das JDK 1.2 ist Java 2 JDK v1.2. Nähere Information enthält die Webseite: http://java.sun.com/products/jdk/1.2/java2.html 17 Offizielle Definition von Sun Microsystems 18 vgl. 2.3 19 vgl. 2.4. 17 Programmieren in Java Systemen befinden. Objekte können mit Java-Programmen über URLs20 vom gesamten Web genauso wie auf dem lokalen Rechner geöffnet und bearbeitet werden. Wichtige Teile der Anwendung bzw. der Daten können lokal vorhanden sein, andere werden bei Bedarf geladen. - Java ist interpretiert. Ein gewisser Teil des Java-Codes (ca. 20 %) werden vom Container, dem Browser interpretiert. Der Java-Quellcode wird mit dem Java-Compiler in Bytecode (architekturneutrales Object-Code-Format) kompiliert. Bytecode ist nicht lauffähig, bis er von der Java-Laufzeitumgebung21 interpretiert wird. Java-Compiler (Pentium) Java-Interpreter (Pentium) Java-Code Java-Compiler (SPARC) Java-Interpreter (SPARC) Abb. 1.2-1: - Java ist stabil, d.h. zuverlässig. Die Stabilität einer Programmiersprache zeigt sich darin, daß während der Kompilierungsphase der gößte Teil der Datenüberprüfung ausgeführt werden kann. Java ist stark typisiert. Damit können Fehler früh gefunden werden. Ein weiteres Stabilitätskriterium von Java-Programmen ist die eingebaute Begrenzung der Zugriffsmöglichkeiten auf den Hinzu kommt auch noch die anschließende Speicherbereich des Rechners22. Sicherheitsüberprüfung durch den „Linker“. Der Linker ist ein Teil der Laufzeitumgebung, das die im System eingehenden Daten überprüft. 20 Rechner sind über IP-Nummern bzw. über Alias-Namen (Domain-Name-System, DNS) im Internet eindeutig adressiert. Innerhalb des Internet müssen aber auch alle Daten und Programme über unverwechselbare InternetAdressen bestimmt sein. Der Name dieser Internet-Adressen für konkrete Adressanfragen an Dokumente im Internet lautet URL und steht für Uniform Resource Locator („einheitliches Adressierungsschema für Objekte“ im Internet). „Einheitlich“ deshalb, weil mit einer URL sowohl die verschiedenen Dienste (WWW, FTP, Gopher usw.) als auch Rechner oder Dokumente beschrieben werden können. Der Begriff „Objekt“ steht bspw. für Datei, Text, Videos, Sounds usw., also für ziemlich alles, was sich im Netz befindet. Die exakte Schreibweise einer URL ist je nach Dienstprotokoll etwas unterschiedlich, sieht jedoch in der Regel so aus: Dienstprotokoll://host.domain:port/pfad/datei Dienstprotokoll ist bspw.: http, ftp, gopher, news, mailto, wais. Danach folgen fast immer Doppelpunkt und zwei Slashes (Ausnahme: mailto). Mit hosts.domain wird ein Rechner im Internet adressiert. Dabei kann die IP-Nummer des Rechner angegeben werden (unüblich). Häufiger nimmt man dafür den DNS-Namen (in der Form host.{localDomain}.SecondLevelDomain.TopLevelDomain. Auf einem Internet-Rechner kann unter einem Namen eine ganze Reihe von verschiedenen Diensten parallel betrieben werden (z.B. FTP-Server, HTTP-Server). Zum Erreichen des gewünschten Dienstes auf dem ausgewählten Rechner benötigt man den sog. Port. Ports sind numerische Werte (zwischen 0 und 1023). In der Regel hat jeder Internet-Dienst einen Default-Wert, der immer verwendet wird, wenn kein Port explizit angegeben ist. Der Port für einen HTTP-Server ist immer „80“, ein FTP-Server hat immer den Port „21“. Das genaue Objekt verbirgt sich hinter der Angabe /pfad/datei. 21 Normalerweise durch einen Java-Browser 22 Ungeprüfte Zugriffe auf Speicherbereiche des Rechners ermöglicht C/C++ (Zeigerarithmetik, implizite Deklaration) 18 Programmieren in Java - Java gilt als sicher. In dieser Hinsicht muß sich Java allerdings noch bewähren23. - Java ist auf verschieden Systemen mit unterschiedlichen Prozessoren und Betriebssystemarchitekturen lauffähig (architekturneutral). Der komplette Java-Bytecode kann auf jedem Prozessor ausgeführt werden, der einen javafähigen Browser (bspw. die virtuelle Maschine von Java24) unterstützt. Plattformunabhängiger Binärcode wird allerdings nicht erzeugt, sondern Java-Bytecode wird während der Laufzeit in systemeigenen Maschinencode übertragen (interpretiert). - Java unterstützt „Multithreading“. „Multthreading bedeutet: Mehrere Aufgaben oder Prozesse können gleichzeitig ausgeführt werden. Nicht die quasi gleichzeitige Ausführung mehrerer Programme (Multitasking), sondern die gleichzeitige, parallele Ausführung von einzelnen Programmschritten (oder zusammenhängenden Prozessen) ist „Multithreading“. Das kann bedeuten: Innerhalb eines Programms können mehrere Dinge gleichzeitig geschehen, mehrere Faden / Threads eines Programms können gleichzeitig verfolgt und abgearbeitet werden. 23 Verschiedene Sicherheitslücken wurden aufgedeckt und wurden inzwischen beseitigt. Die virtuelle Maschine wird auch als Java Interpreter oder Java Environment (Java-Laufzeitumgebung) bezeichnet 24 19 Programmieren in Java 1.3 Einstieg in die Java-Programmierung 1.3.1 Die Software für die Java-Programmierung Das JAVA Development Kit (JDK) enthält die Entwicklungsumgebung zum Schreiben von Java Programmen. Das JDK ist für Sun-SPARC-Systeme mit Solaris 2.2 (oder höher) sowie für Windows NT und Windows 95 über das Internet25 erhältlich. Nach der Installation hat das Java-Verzeichnis26 folgenden Inhalt27: Abb. 1.3-1: Inhalt des Java-Verzeichnisses Innerhalb des Java-Verzeichnisses befinden sich Unterverzeichnisse28 mit folgendem Inhalt: Verzeichnis \bin \lib \include \demo \src Inhalt In diesem Verzeichnis befinden sich die JDK-Programme In diesem Verzeichnis befinden sich defaultmäßig die Standard-Java-Klassen des JDK29 In diesem Verzeichnis befinden sich diverse Header-Dateien für die gemeinsame Verwendung von Java und C/C++ Das Demo-Verzeichnis einthält Beispielprogramme Falls „Java-Source“ im InstallShield ausgewählt wurde, wird dieses Verzeichnis mit angelegt. Darin befinden sich die entkomprimierten Java-Dateien, die sonst nur im gepackten Zustand (Datei SRC.ZIP) vorhanden sind. Es ist sinnvoll, die in der JDK-Umgebung verfügbaren Werkzeuge von allen Verzeichnissen aus zugänglich zu machen. Für Windows-NT und Windows-9525 Das jeweils aktuelle JDK (aber auch ältere Versionen) können von den Sun-Microsystem-Webseiten bzw. der JavaSoft-Homepage geladen werden (http://www.sun.com bzw. http://www.javasoft.com/ ). Genutzt werden kann auch die JavaSoft-WWW-Download-Seite für das jeweilige JDK (http://java.sun.com.products/jdk/x.x , die Angabe x.x ist durch die gewünschte Versionsnummer zu ersetzen). Auch eine FTP-Download, z.B. vom Sun-FTP-Server (ftp.javasoft.com) ist möglich. 26 Erzeugt von der Entpackungsroutine des JDK 27 Das Verzeichnis „meinepr“ dient zur Aufnahme von Projekten und zählt nicht standardmäßig zum Inhalt eines Java-Verzeichnisses 28 unter Windows 29 Im wesentlichen ist das die Datei classes.zip 20 Programmieren in Java Anwender wird das Verzeichnis mit den Werkzeugen in der Pfadangabe der Datei „autoexec.bat“ eingetragen, z.B.: PATH c:\;c:\windows;c:\windows\command;c:\jdk1.1.6\bin;c:\jdk1.1.6\include; Die CLASSPATH-Umgebungsvariable30 „autoexec.bat“ so vorliegen: sollte unter Windows in der Datei set classpath=c:\jdk1.1.6\lib\classes.zip; Im JDK 1.2 wird die CLASSPATH-Umgebungsvariable nicht mehr benötigt. Ist allerdings eine CLASSPATH-Variable gesetzt, so wird sie auch verwendet. Nach der Installation des JDK 1.2 bzw. JDK 1.3 muß das Verzeichnis \jdk1.3\bin in den Suchpfad ausführbarer Dateien eingetragen werden. Das kann direkt in der autoexec.bat durch Modifikation der PATH-Anweisung, mit Hilfe einer Batch-Datei oder direkt erledigt werden, z.B.: set PATH=d:\jdk1.3\bin;%PATH% 30 Umgebungsvariable sind Einstellungen, mit denen die HotJava- und Java-Interpreter-Umgebungen des Systems spezifiziert werden. Sie werden z.B. unter Windows im allg. auf Befehlszeilenebene oder in der „autoexec.bat“ mit der Anweisung „SET [Umgebungsvariable]= ....“ gesetzt. CLASSPATH ist die wichtigste der Umgebungsvariablen von Java. Mit dieser Umgebungsvariablen wird bestimmt, woher die Systemklassen importiert werden. 21 Programmieren in Java 1.3.2 Applets und Anwendungen Java Programme werden in zwei Hauptanwendungs-Gruppen gegliedert: Applets und Anwendungen. Applets sind Java-Programme, die über das WWW heruntergeladen und von einem Web-Browser auf dem Rechner des Anwenders ausgeführt werden. Applets können nur auf einem javafähigen Browser ausgeführt werden bzw. mit einem Tool des JDK, dem Appletviewer, gesichtet werden. Java-Anwendungen sind allgemeine, in der Java-Sprache geschriebene Programme. Zum Ausführen von Java-Anwendungen ist kein Browser nötig. 1.3.2.1 Entwicklung von Java-Anwendungen 1. Aufgabe: Erstelle eine Anwendung, die den Text „Willkommen in der JavaWelt“ ausgibt. Lösungsschritte: 1) Erstelle die folgende Datei mit dem Namen „Willkommen.java“ mit Hilfe eines Dateiaufbereiters (Editor): class Willkommen { public static void main(String args[]) { System.out.println("Willkommen in der Java-Welt!"); } } Das Programm umfaßt zwei Teile: eine Klassendefinition und ein Programmabschnitt, der unter main() angegeben ist. 2) Speichern der Datei mit dem Namen „Willkommen.java“ unter einem beliebigen Verzeichnis31. 3) Aufruf des Java-Übersetzers über die folgende Befehlszeileneingabe: javac [optionen] dateiname. optionen .... bestimmen das Verhalten vom Compiler dateiname ... Name der Datei mit dem Java-Quellcode. "javac" fordert, daß der Quelltext in Dateien steht, deren Dateiname die Extension ".java" besitzt. „javac“ kompiliert den Quellcode in Java-Bytecode und speichert seine Ausgabe in einer Datei mit dem Namen „dateiname.class“. Standardmäßig werden .classDateien im gleichen Verzeichnis wie die .java-Quelldatei erzeugt32. Im vorliegenden Fall ist der Aufruf: javac Willkommen.java. 4) Ausführen der Bytecode-Datei „Willkommen.class“ mit dem Java-Interpreter java: „java Willkommen“. Der im JDK enthaltene Interpreter heißt „java“. Falls alles richtig gelaufen ist, erscheint in der letzten (Ausgabe-) Zeile: Willkommen in der Java-Welt!. 31 32 Vgl. pr13210 Mit der Option "-d" im javac-Aufruf kann die .class-Datei an einem anderen Ort gespeichert werden. 22 Programmieren in Java 2. Aufgabe: Erstelle eine Anwendung, die den Text „Herzlich Willkommen Juergen Hubert Josef Liesel“ ausgibt. Die angebenen Namen sollen auf Befehlszeilenebene als Parameter eingeben werden. Lösungsschritte: 1) Erstellen einer Quelle (Datei), die folgenden Quellcode33 enthält: class WillkommensGruss { public static void main(String args[]) { System.out.print("Herzlich Willkommen "); System.out.print(args[0]); } } 2) Speichern der Datei, Übersetzen und Aufruf des Programm mit dem Parameter Juergen führt zu: „Herzlich Willkommen Juergen“. Argumente auf Befehlszeilenebene werden in den Zeichenketten-Array „String args[]“ aufgenommen. „args“ ist der Name des Zeichenketten-Arrays, das die Argumentenliste enthält und dem Programm immer zur Verfügung steht. Hier wurde nur das erste Argument der Liste (args[0])ausgegeben. Sollen alle Argumente, die auf der Befehlszeilenebene eingegeben wurden, ausgegeben werden, dann ist die Liste komponentenweise mit einer Zählschleife abzuarbeiten. Die Zählschleife (forSchleife) sieht in Java so aus: for (Initialisierung; Test; Inkrement) { // Anweisungen } Initialisierung ist ein Ausdruck, der den Beginn der Zählschleife einleitet, z.B. Initialisierung eines Schleifenindex (z.B. int i = 0;). Die Variablen, die in diesem Teil der Schleife deklariert werden, sind lokal (in Bezug auf die Schleife). Das bedeutet: Sie gehören zur Schleife und existieren nicht mehr nach der vollständigen Ausführung der Schleife. Man kann in diesem Bereich mehr als eine Variable initialisieren (durch Angabe mehrerer durch Kommas getrennte Ausdrücke, z.B. int i = 0, int j = 10). Test ist ein Ausdruck, der nach jeder Iteration der Schleife ausgeführt wird. Der Test muß ein boolescher Ausdruck oder eine Funktion sein, die einen booleschen Wert zurückgibt (true oder false). Ergibt der Test true, wird die Schleife ausgeführt. Sobald er false ergibt, wird die Schleifenausführung angehalten. Inkrement ist ein beliebiger Audruck oder Funktionsaufruf. Üblicherweise wird er verwendet, um den Wert des Schleifenindex näher an den Endwert zu bringen und damit für Beendigung der Schleife zu sorgen. Wie im Initialisierungsbereich kann im Inkrement-Bereich mehr als ein Ausdruck untergebracht sein, falls die einzelnen Ausdrücke mit Kommas voneinander getrennt sind. Auf den Wert eines Elements in einem Array wird über den Namen des Array, gefolgt von einem Index in eckigen Klammern zugegriffen (z.B. args[i]). Array-Indizes beginnen mit 0. Alle Array-Indizes werden geprüft, um sicher zu stellen, daß sie sich innerhalb der Grenzen des Array befinden, wie sie bei der Erzeugung des Arrays festgelegt wurden. Die Länge des Array kann im Programm mit der Instanzvariablen length getestet werden (z.B. args.length). 33 Vgl. pr13210 23 Programmieren in Java 3) Erweitern der Quellcode-Datei um eine Zählschleife, die die Argumentenliste abarbeitet: class WillkommensGruss { public static void main(String args[]) { int i; // Lokale Variable System.out.print("Herzlich Willkommen "); for (i=0; i < args.length;i++) { System.out.print(args[i] + " "); } } } „args.length“ bestimmt die Länge der Argumentenliste. 4) Speichern der Datei, Übersetzen und Aufruf des Programm mit Parametern führt zu: „Herzlich Willkommen Juergen Liesel Vera Christian Hubert“. 3. Aufgabe: Die auf Befehlszeilenebene eingegebenen Namen sollen sortiert werden. So soll die Eingabe der Befehlszeile „java WillkommensGruss Juergen Hubert Josef Liesel Christian“ zu folgender Ausgabe führen: „Herzlich Willkommen Christian Hubert Josef Juergen Liesel“. Lösungsschritte: 1) Gesucht ist ein Algorithmus, der die über die Befehlszeile eingegebenen Namen sortiert. Ein einfacher Sortieralgorithmus ist unter dem Namen „Bubble-Sort“ bekannt. Man vergleicht dabei zunächst den ersten unter args[0] abgelegten Namen gegen alle weiteren im Datenfeld args abgelegten Namen. args [0] [1] [2] [3] [4] "Juergen" "Hubert" "Josef" "Liesel" "Christian" Nach 4 Vergleichen hat das Datenfeld args folgende Gestalt angenommen: args [0] [1] [2] [3] [4] "Christian" "Juergen" "Josef" "Liesel" "Hubert" Die erste Position (args[0]) ist im Datenfeld damit schon richtig eingeordnet. Danach muß jetzt der Name an der 2. Position ([1]) mit den übrigen Namen des Datenfelds verglichen werden. Nach 3 Vergleichen zeigt sich folgendes Bild: args [0] [1] [2] [3] [4] "Christian" "Hubert" "Juergen" "Liesel" "Josef" 24 Programmieren in Java „Christian“ und „Hubert“ sind jetzt richtig eingeordnet. Nun muß der Name an der dritten Position mit allen noch verbliebenen Namen auf Position 4 und 5 noch verglichen werden. Das Resultat der Vergleiche zeigt: args [0] [1] [2] [3] [4] "Christian" "Hubert" "Josef" "Liesel" "Juergen" Der nächste Durchgang führt dann zum sortierten Datenfeld: args [0] [1] [2] [3] [4] "Christian" "Hubert" "Josef" "Juergen" "Liesel" 2) Der soeben beschriebene Sortieralgorithmus muß in Java-Programmcode abgebildet werden. Es ist leicht erkennbar, daß hier zwei verschachtelte forSchleifen die Sortierung erreichen können. for (i = 0; i < args.length; i++) { for (j = i + 1; j < args.length; j++) { if (args[i].compareTo(args[j]) > 0) { // Tauschen String temp = args[i]; // lokale Variable args[i] = args[j]; args[j] = temp; } } } Zeichenketten werden über die Methode „compareTo“ der Klasse String verglichen. Generell stehen die Vergleichsoperatoren (== != < <= > >=) nur für das Vergleichen von Zahlen zur Verfügung. Die Vergleichsbedingung wird durch das Schlüsselwort if erzeugt: if (Ausdruck) { // Anweisung(en) } else { // Anweisung(en) } Eine if-Bedingung verwendet einen boolschen Ausdruck für die Entscheidung, ob eine Anweisung ausgeführt werden soll. Die Anweisung wird ausgeführt, wenn der Ausdruck den Wert true zurückliefert. Falls gewünscht wird, daß eine bestimmte Anweisung bzw. Anweisungen ausgeführt werden, wenn der boolsche Ausdruck false zurückliefert, ist dieser Anweisungsblock durch das Schlüsselwort else einzuleiten. 25 Programmieren in Java 3) Das vollständige Programm umfaßt folgenden Quelltext: class WillkommensGruss { public static void main(String args[]) { int i, j; // Lokale Variable System.out.print("Herzlich Willkommen "); for (i = 0; i < args.length; i++) { for (j = i + 1; j < args.length; j++) { if (args[i].compareTo(args[j]) > 0) { String temp = args[i]; args[i] = args[j]; args[j] = temp; } } } for (i = 0; i < args.length; i++) { System.out.print(args[i] + " "); } System.out.println(); } } 4) Speichern der Datei, Übersetzen und Aufruf des Programms mit Parametern führt zu: „Herzlich Willkommen Christian Hubert Josef Juergen Liesel“. 4. Aufgabe: Die Argumentenliste args soll auf folgende Weise angezeigt werden: args[0] args[1] args[2] args[3] args[4] = = = = = "Christian" "Hubert" "Josef" "Juergen" "Liesel" Lösungsschritte: 1) System.out.println() bzw. System.out.print() erwarten ein einziges Argument innerhalb der Klammern. Sollen, wie hier gewünscht, mehrere Variablen, vom Typ String oder Zeichenkettenliterale, Argument für println() sein, dann können diese Elemente mit dem Verkettungsoperator „+“ zu einem einzigen String oder Zeichenkettenliteral verknüpft werden. Der Umgang mit dem Verkettungsoperator ist in Java einfach, da er alle Variablentypen und Objektwerte wie Strings behandelt. Sobald ein Teil einer Verkettung ein String oder ein StringLiteral ist, werden alle Operatoren wie Strings behandelt, z.B.: System.out.println("1 + 2 = " + 3);. 2) Die Ausgabe kann über die folgende Anweisungen so erfolgen for (i = 0; i < args.length; i++) { System.out.println("args[" + i + "] } System.out.println(); = \"" + args[i] + "\""); Die Zeichen für doppelte Anführungszeichen(") und Backslash(\) müssen durch die Verwendung von sog. Escape-Sequenzen (\" und \\) dargestellt werden. Die beiden Anführungszeichen, die das Literal umschliessen, müssen in derselben Zeile des Quellcodes stehen. Eine neue Zeile kann innerhalb eines Literals durch die Escape-Sequenz \n erreicht werden. 26 Programmieren in Java 5. Aufgabe: Zwei Gleitpunktzahlen sollen über die Befehlszeile eingelesen werden. Anschließend sollen mit diesen beiden Zahlen alle zugelassenen, binären arithmetischen Operationen für Gleitpunktzahlen (des primitiven Datentyps float) und unäre Inkrement- bzw. Dekrement-Operationen ausgeführt werden. Lösungsschritte: 1) Die Übergabe der beiden Zahlen über die Befehlsargumentenliste erfordert die Konvertierung des Typs String in einen Typ der Klasse Float. Das konvertierte Datum kann anschließend einer Variablen des primitiven Typs float zugewiesen werden. float x = Float.valueOf(args[0]).floatValue(); float y = Float.valueOf(args[1]).floatValue(); 2) Für ganze Zahlen und Gleitpunktzahlen sind Additions- (+), Subtraktions- (-), Multiplikations- (*) und Divisionsopertoren (/) definiert. Es gibt außerdem für unäre Arithmetik noch Inkrement- und Dekrementoperatoren zur Manipulation des Werts von Variablen. 3) Der Quelltext zur Programmlösung ist dann: public class FloatDemo { public static void main(String args[]) { // Konvertieren float x = Float.valueOf(args[0]).floatValue(); float y = Float.valueOf(args[1]).floatValue(); // Ausgabe der ueber die Befehlszeile eingegebenen Zahlen System.out.println("x = " + x); System.out.println("y = " + y); // Binaere Arithmetik mit den Operatoren + - * / float z; z = x + y; System.out.println("z = x + y = " + z); z = x - y; System.out.println("z = x - y = " + z); z = x * y; System.out.println("z = x * y = " + z); z = x / y; System.out.println("z = x / y = " + z); // Unaere Arithmetik mit Inkrement- / Dekrementoperator x++; System.out.println("Nach x++: x = " + x); y--; System.out.println("Nach y--: y = " + y); z = x++; System.out.println("Nach z = x++: z = " + z + ", x = " + x); z = ++x; System.out.println("Nach z = ++x: z = " + z + ", x = " + x); System.out.println("x = " + x); System.out.println("y = " + y); z = ++x + y--; System.out.println("nach z = ++x + y--: z = " + z + ", x = " + x + " y = " + y); System.out.println("x = " + x); System.out.println("y = " + y); z = x + y * ++y; System.out.println("nach z = x + y * ++y: z = " + z + ", x = " + x + " y = " + y); 27 Programmieren in Java z = (float) (1.0f / 0.0f); System.out.println("z = (float) (1.0f / 0.0f) = " + z); } } 4) Der Aufruf java FloatDemo 17 4 führt dann zu folgenden Ausgabe: x = 17.0 y = 4.0 z = x + y = 21.0 z = x - y = 13.0 z = x * y = 68.0 z = x / y = 4.25 Nach x++: x = 18.0 Nach y--: y = 3.0 Nach z = x++: z = 18.0, x = 19.0 Nach z = ++x: x = 20.0, x = 20.0 x = 20.0 y = 3.0 Nach z = ++x + y--: z = 24.0, x = 21.0 y = 2.0 x = 21.0 y = 2.0 Nach z = x + y * ++y: z = 27.0, x = 21.0 y = 3.0 z = (float) (1.0f / 0.0f) = Infinity Das zuletzt angegebenen Resultat zeigt die Zuordnung "Infinity" falls eine Zahl ungleich Null durch Null geteilt wird. 1.0f und 0.0f sind Gleitpunkt-Literale. Da "Infinity" als sehr groß interpretiert (Typ double) wird, muß hier in den Typ float konvertiert werden. Die Anwendung zeigt außerdem die Anwendungsweise der Inkrement- und Dekrementoperatoren in Präfix- und Postfix-Schreibweise. Steht der Operator vor der Variablen (z.B. ++x), dann wird er angewendet, bevor der Wert der Variablen in eine Anweisung eingesetzt wird. Im umgekehrten Fall (z.B. x++) wird die Variable zuerst in einer Anweisung benutzt und dann erhöht. 6. Aufgabe: In der 5. Aufgabe führt der Aufruf java FloatDemo zu einem Fehler. Dieser Fehler soll aufgefangen werden. Lösungsschritte: 1) Java verfügt zur Behandlung von Fehlern eine spezielle Ausnahmenbehandlungsroutine (exception handling) den „try-catch“-Block. Im „try“-Block wird der normale Ablauf behandelt. Im „catch“-Block erfolgt die Behandlung der Ausnahmen. Die Verzweigung in den „catch“-Block erfolgt beim Auftreten einer Ausnahme automatisch, der „try“-Block bleibt dabei unberührt. 2) Der Quelltext nimmt nach dem Einfügen eines „try-catch“-Blocks folgende Gestalt an: public class FloatDemo { public static void main(String args[]) { try { // Konvertieren ................ } catch(Exception e) { System.out.println("Fehler bei der Eingabe: java FloatDemo a b"); System.out.println(e.getMessage()); } 28 Programmieren in Java } } 1.3.2.2 Entwicklung von Java-Applets Die Entwicklung eines Applets unterscheidet sich von einer Anwendung, weil JavaApplets in einer Web-Seite mit anderen Seitenelementen zusammen ausgeführt werden. Zur Ausführung eines Applets ist es nötig, das Applet in einem HTMLDokument34 einzubetten. In diesem HTML-Dokument werden dem javafähigen Browser die Informationen mitgeteilt, die er zur Ausführung des Applets benötigt. Der Browser lädt die Klassendatei und führt das Applet automatisch aus. Aus der Sicht des Programmierers ist das Applet eine Klasse, die von der AppletKlasse abgeleitet wird. 1. Aufgabe: Erstelle ein Applet, das in einem Fenster den Text „Herzlich Willkommen in der Java-Welt“ ausgibt Lösungsschritte: 1) Erstelle die folgende Datei mit dem Namen „WillkommenApplet.java“ mit Hilfe eines Dateiaufbereiters (Editor): /* Das erste Java-Applet */ import java.awt.Graphics; public class WillkommenApplet extends java.applet.Applet { public void paint (Graphics g) { g.drawString("Herzlich willkommen in der Java Welt!",5,25); } } Durch die „import“-Anweisung können Entwickler Klassen verwenden, die in anderen Dateien definiert sind. Compiler bzw. Interpreter greifen auf die classDateien zu. Über „import“ wird bestimmt, wo diese Dateien liegen. Java importiert immer das Paket „java.lang“, denn hier ist die Klasse Object enthalten, von der alle Java-Klassen abgeleitet sind. Die Graphics-Klasse enthält Methoden zum Zeichnen von Textzeichen und Zeichenketten. Mit der „drawstring“-Methode der Graphics-Klasse können Textzeichen auf den Bildschirm gemalt werden. Die folgende Abbildung zeigt die Modellierung35 des vorliegenden Quellcodes: WillkommenApplet paint() g.drawString (“Herzlich Willkommen in der Java-Welt!“,5,25) Abb. 1.3-2: Klassendiagramm zu WillkommenApplet 34 eine entsprechende Referenz innerhalb einer HTML-Seite mit einem speziellen Tag, dem <APPLET>-Tag erledigt die Einbettung in den Browser 35 Die Modellierung erfolgt nach den Regeln der Unified Modelling Language (UML). Die UML ist eine grafische, standardisierte Sprache zum Spezifizieren, Konstruieren, Visualisieren und Dokumentieren. 29 Programmieren in Java Die Klasse WillkommenApplet läßt sich grafisch als rechteckiges Symbol darstellen. Die paint()-Methode wird ohne formale Parameter beschrieben, ihre Implementierung wird durch die beigefügte Notiz gezeigt. Die unmittelbare Superklasse wird im Quelltext direkt in der „extends“-Klausel angegeben. „extends java.applet.Applet“ bestimmt das die angegebene Applet-Klasse von der Applet-Klasse des Abstract Windowing Toolkit (AWT) abgeleitet ist. Der andere Teil der Klassendefinition enthält das Schlüsselwort „public“ mit der Bedeutung: Die Klasse ist nach dem Laden für das gesamte JavaSystem verfügbar. Applets müssen „public“ deklariert werden. Die folgende Abbildung zeigt die Beziehungen der Klasse WillkommenApplet zu ihren unmittelbaren Nachbarn: Applet WillkommenApplet paint() Graphics Abb. 1.3-3: Die unmittelbare Umgebung von WillkommenApplet Die gerichtete Linie mit der unausgefüllten Pfeilspitze von WillkommenApplet zu Applet repräsentiert eine Generalisierung, d.h.: WillkommenApplet ist eine Unterklasse von Applet. Der gestrichelte Pfeil repräsentiert eine Abhängigkeitbeziehung: WillkommenApplet verwendet Graphics. Ein eigenes, vom Benutzer erstelltes Applet überschreibt gewöhnlich Methoden, die in der Superklasse Applet definiert sind. Diese Methoden übernehmen Aufgaben zur Initialisierung des Applet vor der Ausführung (public void start()), zur Reaktion auf Mauseingaben, zum Anhalten des Applet (public void stop()) und zu Aufräumungsarbeiten (public void destroy()), wenn das Applet beendet wird. Eine dieser Methoden ist paint(), die sich um die Anzeige des Applet in einer Webseite kümmert. Die paint()-Methode besitzt ein einziges Argument, eine Instanz der Klasse Graphics. Die Klasse Graphics stellt Verhaltensweisen zur Darstellung von Schriften, Farben, zum Zeichnen von Linien36, von Ellipsen bzw. Kreisen37, von Rechtecken38 und anderen Formen zur Verfügung. In der paint()-Methode wurde hier der String "Herzlich Willkommen in der Java Welt!" bei den (x,y)-Koordinaten (5,25) ausgegeben. Der Ursprung des Koordinatensystems liegt in der linken oberen Ecke des Darstellungsbereichs. Der String wird in einer defaultmäßig festgelegten Schrift und Farbe angezeigt. 36 public void drawLine(int x1, int y1, int x2, int y2); (x1,y1) bestimmt den Anfangspunkt, (x2,y2) bestimmt den Endpunkt der Linie. 37 public void drawOval(int x, int y, int width, int height); (x,y) gibt die Koordinaten der oberen linken Ecke des die Ellipse umschreibenden Rechtecks mit der Höhe height und der Breite width am 38 public void drawRect(int x, int y, int width, int height); bestimmt die obere linke Ecke des Rechtecks, (width, height) legen Breite und Höhe des Rechtecks fest. 30 Programmieren in Java Die Untersuchung der Java-Bibliotheken zu Applet und Graphics zeigt: Die beiden Klassen sind Teil einer größeren Hierarchie. Verfolgt man die von Applet erweiterten und implementierten Klassen, dann kann man das folgende Klassendiagramm erhalten: Object Component ImageObserver Container Panel Applet WillkommenApplet Abb. 1.3-4: Vererbungshierarchie von WillkommenApplet Die Beziehung zwischen ImageObserver und Component ist eine Schnittstelle. ImageObserver wird von Component implementiert. WillkommenApplet arbeitet mit den Klassen Applet und Graphics unmittelbar zusammen. Diese beiden Klassen bilden lediglich einen kleinen Ausschnitt aus der Bibliothek mit vordefinierten Java-Klassen. Die Verwaltung dieser Klassen und Schnittstellen organisiert Java in mehreren verschiedenen Paketen. Das Wurzelpaket in der Java-Umgebung heißt java. In dieses Paket sind mehrere weitere Pakete geschachtelt, die wiederum andere Pakete, Schnittstellen und Klassen enthalten. Object existiert im Paket lang, Panel, Container, Component existieren im Paket awt, und die Klasse Applet im Paket applet. Die Schnittstelle ImageObserver existiert im Paket image, das liegt wiederum im Paket awt (qualifizierter Name: java.awt.ImageObeserver). Die Paketstruktur39 kann in einem Klassendiagramm visualisiert werden: 39 Pakete werden in der UML als Akten mit Reitern dargestellt. Die gestrichelten Pfeile repräsentieren die Abhängigkeiten zwischen den Paketen. 31 Programmieren in Java java WillkommenApplet applet awt lang Abb. 1.3-5: Paketstruktur im WillkommenApplet 2) Speichern der Datei mit dem Namen „WillkommenApplet.java“ unter einem beliebigen Verzeichnis. 3) Aufruf des Java-Übersetzers über die folgende Befehlszeileneingabe: „javac WillkommenApplet.java“. Der Compiler gibt bei erfolgreicher Übersetzung keine Rückmeldung. Zwei Dateien müssen nach erfolgreicher Übersetzung vorliegen: „WillkommenApplet.java“ und „WillkommenApplet.class“. 4) Erstellen der folgenden HTML-Datei „WillkommenApplet.html“, anschließend Speichern dieser Datei. <HTML> <HEAD> <TITLE>Seid gegruesst!</TITLE> </HEAD> <BODY> <P>Mein Java Applet sagt:<BR> <APPLET CODE="WillkommenApplet.class" WIDTH=200 HEIGHT=50> </APPLET> </BODY> </HTML> Der Bezugspunkt in HTML-Dateien auf ein Applet erfolgt mit dem <Applet>-Tag. Das Code-Attribut dient zur Angabe von dem Namen der Klasse, die das Applet enthält. Die Attribute WIDTH und HEIGHT dienen zum Bestimmen der Größe des Applets. Der Browser benutzt diese Werte zur Zuteilung des Raums, der für das Applet auf der Seite freigehalten werden muß. Hier wurde eine Box mit einer Breite von 200 und einer Höhe von 50 Pixeln definiert. WillkommenApplet ist als Applet implementiert. Es kann nie isoliert stehen, sondern ist normalerweise Teil einer Webseite. 32 Programmieren in Java WillkommenApplet.java WillkommenApplet.class WillkommenApplet.html Abb. 1.3-6: Die Komponenten von WillkommenApplet 5) Ausführung des Applet mit einem javafähigen Web-Browser bzw. dem Appletviewer, z.B. mit dem Aufruf „appletviewer WillkommenApplet.html“. Das Resultat müßte so aussehen: Abb. 1.3-7: Darstellung des Fensters mit dem Appletviewer In einem Browser würde zusätzlich der Text rund um das Applet („Mein Java Applet sagt:“) gezeigt werden. 2. Aufgabe: Verändern von Schrift und Farbe für den Text „Herzlich Willkommen in der Java-Welt“. Lösungsschritte: 1) Erweitere die Datei mit dem Namen „WillkommenApplet.java“ mit Hilfe eines Dateiaufbereites (Editor). Die erste Erweiterung soll die Schrift verändern, in der der Text ausgegeben wird. Es wird ein Objekt der Klasse java.awt.Font über folgende Anweisung erzeugt: Font f = new Font("TimesRoman",Font.BOLD,12); Objekte der Klasse Font dienen zum Bereitstellen verschiedener Schriftarten für die Methode drawString() und repräsentieren den Namen, den Stil und die Größe einer Schrift. Mit einem spezifischen Objekt der Klasse Font kann man eine Schrift aufrufen, die sich von der standardmäßig in Applets benutzten Schrift unterscheidet. Dem Font-Objekt wird hier die Schrift „TimesRoman“, fett in „12-Punkt“ Größe zugewiesen. Das neue Objekt wird anschließend der Instanzvariablen f zugewiesen und ist so der Methode paint() zugänglich: public void paint (Graphics g) { // Mitteilung: Die Schrift zur Anzeige von Text befindet sich in der // Instanzvariablen f g.setFont(f); 33 Programmieren in Java // Mitteilung: Die Farbe fuer die Ausgabe ist gelb g.setColor(Color.yellow); // Die glebe Farbe dient zum Fuellen eines Rahmens fuer den Text, der // aus einem Rechteck mit abgerundeten Ecken besteht g.fillRoundRect(0,0,225,30,10,10); // Mitteilung: die Farbe fuer Textausgaben ist // eine Instanz der Klasse Color fuer die Farbe rot g.setColor(Color.red); // Mitteilung: Text wird in festgelegter Schrift und Farbe bei den // (x,y)-Koordinaten (5,25) ausgegeben. g.drawString("Herzlich Willkommen in der Java Welt!",5,25); } Die Klassen Font und Color werden über die folgenden import-Anweisungen bereitgestellt: import java.awt.Graphics; import java.awt.Font; import java.awt.Color; Dafür kann man auch „import java.awt.*;“1 schreiben. 2) Kompiliere die Datei „WillkommenApplet.java“ in eine „.class“-Datei. 3) Ausführung des Applet, z.B. über den Aufruf appletviewer WillkommenApplet.html. 3. Aufgabe: Erweitere das Applet der 1. Aufgabe um einen Dokumentationskommentar Java kennt verschiedene Kommentarzeichen - für die interne Dokumentation: // und die Kombination /* ... */ - für die externe Dokumentation: //** ... */ Grundsätzlich steht der Dokumentationskommentar vor Klassen, (Interfaces, Konstruktoren,) Methoden, z.B.41: /* Das erste Java Applet */ import java.awt.Graphics; /** Dieses Applet demonstriert den Grundaufbau von Applets. *** @author Juergen Sauer *** @version WS 2001/2002 *** @see WillkommenApplet01 */ public class WillkommenApplet01 extends java.applet.Applet { /** Die Methode paint ist von Component geerbt *** Sie wird aufgerufen, wenn das Applet neu gezeichnet werden muss;. *** @param g Eine Referenz eines Grafikobjekts */ public void paint (Graphics g) { g.drawString("Herzlich Willkommen in der Java Welt!",5,25); } } 40 41 Diese Anweisung stellt alle Klassen des Pakets java.awt zur Verfügung pr13210 34 Programmieren in Java Das Programm, das einen derartigen Quellcode zu einer HTML-Dokumentation verarbeitet, heißt javadoc.exe und ist im Java-SDK von Sun enthalten. Aufrufsyntax: javadoc [flags] [klasse|package] Für „flags“ kann * oder folgende Schalter angegeben werden: -version -author -sourcepath -classpath -doctype -noindex -notree @version berücksichtigen @author berücksichtigen Suchpfad für Klassen Synonym für sourcepath Ausgabetyp (Standard: html) Kein Verzeichnis aller Konstruktoren und Methoden erzeugen Keine Java-Klassenhierarchie erzeugen Falls das vorliegende Beispiel mit javadoc –author –version –notree – noindex WillkommenApplet01.java aufgerufen wird, erzeugt das Programm eine Datei WillkommenApplet01.html, die Auswertungen der Java-API zeigt und den Kommentar, z. B.: 35 Programmieren in Java Abb. 1.3-8: Ausschnitt zum über javadoc erzeugten Dokument 4. Aufgabe: Überwachen des Lebenszyklus eines Applet. Ein Applet führt keine Aktionen aus eigener Initiative aus, sondern empfängt Ereignisse vom System und liefert Ergebnisse zurück. Beim Start eines Applet ruft der Webbrowser die Methode init() des Appletobjekts auf. Beim Beenden eines Applets wird die Methode destroy() aufgerufen. init() und destroy() werden im Lebenszyklus eines Applet genau einmal ausgeführt, nämlich beim Erzeugen bzw. beim Beenden eines Objekts. Zusätzlich sind zwei weitere Methoden start() und stop() vorgesehen, die an verschiedenen Stellen zur Aktivierung aufgerufen werden können. Wählt ein Anwender bei gestartetem Applet z.B. im Browser eine andere Internetseite an, so ruft der Browser die stop()-Routine auf und hält das Applet an, bis der Anwender wieder auf die Appletseite zurückkehrt. Dann ruft der Browser die start()-Routine zum ordnungsgemäßen Fortsetzen des Applet auf. import java.awt.*; import java.applet.*; public class AppletDemo extends Applet { // Instanzvariable String s; int initialisierungen = 0; int startpunkte = 0; int haltepunkte = 0; // Methoden public void init() { initialisierungen++; } public void start() { startpunkte++; } public void stop() { haltepunkte++; } public void paint(Graphics g) { // Zur Ausgabe einer geeigneten Nachricht über das Verhalten // des Applet wird der String s mit Hilfe des Opertors + aus // Teilstrings zusammengesetzt. Ganze Zahlen werden dabei in Zei// chenketten konvertiert. s = "Initialisierungen: " + initialisierungen + ", Startpunkte: " + startpunkte + ", Haltepunkte: " + haltepunkte; g.drawString(s,10,10); } } 36 Programmieren in Java Browser öffnet <<new>> Webseite :Applet init() start() paint() verlässt stop() Browser schliesst destroy() Browser Abb.: Lebenszyklus eines Applets 5. Aufgabe: Entwickle aus der vorliegenden Aufgabe eine allgemeine AppletSchablone, die als Vorlage für das Erstellen von Applets dienen kann. Das folgende Gerüst beschreibt ein Muster für alle Applets: // // // // // // Name der Klasse: Beschreibung: Import der Pakete import java.lang.*; import java.applet.*; import java.awt.*; // Top-Level-Klassen-Deklaration bzw. Definition des Applets public Klassenname extends java.applet.Applet { // Variablen-Deklarationen bzw. Definitionen // ... // Eigene Methoden // ... // Methoden, die ueberschrieben werden // public void init() { // ... } public void start() { // ... } public void stop() { // ... } public void destroy() { // ... } // Optional: die Ausgabemethode 37 Programmieren in Java public void paint(Graphics g) { // .. } } Ein Applet erbt Methoden und Variablen der Applet-Klasse. Die Applet-Klasse erbt wiederum von einer Reihe anderer Klassen. Eine Klasse, die java.applet.Applet erweitert, kann Variablen und Methoden aus java.lang.Object, java.awt.Component, java.awt.Container sowie java.awt.Panel verwenden. 1.3.3 Darstellung von Algorithmen durch Programmabläufe und Java Komposition Aus einer Anweisungsfolge42 (mit n ∈ N ) „anweisung1 anweisung2 ... anweisungn“ konstuiert man durch Klammerung mit { und } eine Anweisung, die Komposition der Anweisungen der Folge { anweisung1 anweisung2 ... anweisungn } Die Anweisungen werden nacheinander in der angegebenen Reihenfolge ausgeführt. Die Klammern umschließen einen Block, in dem Variablen neu bzw. lokal vereinbart werden können. In Programmabläufplänen werden Anweisungen in Anweisungsknoten (Symbol: Kästchen) dargestellt. Abb.: Anweisungsknoten Neben Anweisungsknoten gibt es in Prgrammablaufplänen Testknoten. Sie werden mit einem Rhombus symbolisiert. Testknoten enthalten Tests. Ist die Bedingung erfüllt bestimmt der mit „true“ bewertete Pfeil den nächsten Knoten. Anderenfalls der mit „false“ bewertete. Abb.: Testknoten Daneben gibt es noch initale und finale Knoten: Abb.: Initialer bzw. finaler Knoten 42 vgl. 2.4.1 38 Programmieren in Java Bedingte Anweisung43 Java-Notation: if (bedingung) anweisung Programmablaufplan: true false Abb.: Bedingte Anweisung Verzweigungsanweisung Java-Notation: if (bedingung) anweisung1 else anweisung2 Programmablaufplan: true false Abb.: Verzweigungsannweisung Endgeprüfte Schleife44 Java-Notation: // Vorbedingung do anweisung // Invariante while (bedingung) // Nachbedingung 43 44 vgl. 2.4.6 vgl. 2.4.7 39 Programmieren in Java Programmablaufplan: true false Abb.: Endgeprüfte Schleife Anfangsgeprüfte Schleife45 Java-Notation: // Vorbedingung while (bedingung) // Invariante anweisung // Nachbedingung Programmablaufplan: false true Abb.: Anfangsgeprüfte Schleife 45 vgl. 2.4.7 40 Programmieren in Java 1.4 Die Objektorientierung von Java 1.4.1 Grundlegende Konzepte 1.4.1.1 Zustand und Verhalten von Objekten, Klassen, Instanz- und Klassen-Variable bzw. -Methoden Die Abbildung von Zustand bzw. Verhalten in Instanzvariable bzw. Instanzmethoden Objekte sind die Schlüssel zum Verständnis der objektorientierten Technologie. Objekte sind Gegenstände des täglichen Lebens: der Schreibtisch, das Skript, die Vorlesung. All diese Objekte der realen Welt haben Zustand und Verhalten. Auch Software-Objekte haben Zustand und Verhalten. Der Zustand wird in Variablen festgehalten, das Verhalten der Objekte beschreiben Methoden. Die Variablen bilden den Kern der Objekte. Methoden schirmen den Objektkern von anderen Objekten des Programms ab (Kapselung). Software-Objekte kommunizieren und verkehren über Nachrichen (Botschaften) miteinander. Das sendende Objekt schickt dem Zielobjekt eine Aufforderung, eine bestimmte Methode auszuführen. Das Zielobjekt versteht (hoffentlich) die Aufforderung und reagiert mit der zugehörigen Methode. Die genaue formale Schreibweise solcher Botschaften in objektorientierten Sprachen ist im Detail verschieden, jedoch wird meistens folgende Form verwendet: Empfänger.Methodenname(Argument). „Argument“ ist in dem Botschaftsausdruck ein Übergabeparameter für die Methode. In der realen Welt existieren häufig Objekte der gleichen Art. Sie werden über einen Prototyp, eine Klasse, zusammengefaßt. Eine Klassendefinition in Java wird durch das Schlüsselwort „class“ eingeleitet. Anschließend folgt innerhalb von geschweiften Klammern eine beliebige Anzahl an Variablenund Methodendefinitionen. Zum Anlegen eines Objekts einer Klasse (Instanziierung46) muß eine Variable vom Typ der Klasse deklariert und mit Hilfe des new-Operators ein neu erzeugtes Objekt zugewiesen werden. Das Speicher-Management in Java erfolgt automatisch. Während das Erzeugen von Objekten immer einen expliziten Aufruf des new-Operators erfordert47, erfolgt die Rückgabe von nicht mehr benötigtem Speicher automatisch48. Das Schreiben eines Programms besteht damit aus Entwurf und Zusammenstellung von Klassen. Klassenbibliotheken (Sammlung von Klassen) stellen Lösungen für grundlegende Programmieraufgaben bereit. Zustand, Aussehen und andere Qualitäten eines Objekts (Attribute) werden durch Variable definiert. Da jede Instanz einer Klasse verschiedene Werte für ihre Variablen haben kann, spricht man von Instanzvariablen. Zusätzlich gibt es noch Klassenvariable, die die Klasse selbst und alle ihre Instanzen betreffen. Werte von Klassenvariablen werden direkt in der Klasse gespeichert. Der Zustand wird in Variablen festgehalten und zeigt den momentanen Stand der Objektstruktur an, d.h. die in den einzelnen Bestandteilen des Objekts enthaltenen Informationen und Daten. Abhängig vom Detaillierungsgrad kann die Notation für eine Variable den Namen, den Datentyp und den voreingestellten Wert zeigen: 46 Eine Instanz einer Klasse ist ein (tatsächliches) Objekt (konkrete Darstellung) Ausnahmen: String-, Array-Literale 48 Ein Garbage-Collector (niedrigpriorisierte Hintergrundprozeß) sucht in regelmäßigen Abständen nach nicht mehr referenzierten Objekten und gibt den durch sie belegten Speicher an das Laufzeitsystem zurück 47 41 Programmieren in Java Sichtbarkeit Typ Name = voreingestellter_Wert; Sichtbarkeit: öffentlich (public), geschützt (protected) oder privat (private) Typ: Datentyp Name: eine nach bestimmten Regeln49 gebildete Zeichenkette Nach der Initialisierung haben alle Variablen des Objekts zunächst Standardwerte (voreingestellte Werte). Der Zugriff auf sie erfolgt mit Hilfe der Punktnotation: Objekt.Variable. Zur Bezugnahme auf das aktuelle Objekt dient das Schlüsselwort this. Es kann an jeder beliebigen Stelle angegeben werden, an der das Objekt erscheinen kann, z.B. in einer Punktnotation zum Verweis auf Instanzvariablen des Objekts oder als Argument für eine Methode oder als Ausgabewert der aktuellen Methoden. In vielen Fällen kann das Schlüsselwort this entfallen. Das hängt davon ab, ob es Variablen mit gleichem Namen im lokalen Bereich gibt. Zur Definition des Verhaltens von Objekten dienen Methoden. Methoden sind Funktionen, die innerhalb von Klassen definiert werden und auf Klasseninstanzen angewandt werden. Methoden wirken sich aber nicht nur auf ein Objekt aus. Objekte kommunizieren auch miteinander durch Methoden. Eine Klasse oder ein Objekt kann Methoden einer anderen Klasse oder eines anderen Objekts aufrufen, um Änderungen in der Umgebung mitzuteilen oder ein Objekt aufzufordern, seinen Zustand zu ändern. Instanzmethoden (Operationen, Services) werden auf eine Instanz angewandt, Klassenmethoden beziehen sich auf eine Klasse. Klassenmethoden können nur mit Klassenvariablen arbeiten. Die Beschreibung der Operationen (Nachrichten, Methoden) erfolgt nach dem folgenden Schema: Sichbarkeit Rückgabetypausdruck Name(Parameterliste) Sichtbarkeit: öffentlich (public), geschützt (protected), privat (private)50 Rückgabetypausdruck: Jede Methode ist typisiert. Der Typ einer Methode bestimt den Typ des Rückgabewerts. Dieser kann von einem beliebigen primitiven Typ51, einem Objekttyp oder vom Typ void sein. Methoden vom Typ void haben keinen Rückgabewert und dürfen nicht in Ausdrücken verwendet werden. Hat eine Methode einen Rückgabewert, dann kann sie mit der „return“Anweisung52 einen Wert an den Aufrufer zurückgeben. Parameterliste enthält optional Argumente und hat folgende Struktur: Datentyp variablenname, ..... Die Anzahl der Parameter ist beliebig und kann Null sein. In Java wird jede selbstdefinierte Klasse mit Hilfe des Operators new instanziert. Mit Ausnahme von Zeichenketten (Strings) und Datenfeldern (Arrays), bei denen der Compiler auch Literale zur Objekterzeugung bereitstellt, gilt dies für alle vordefinierten Klassen der Java-Bibliothek. Der Aufruf einer Methode erfolgt ähnlich der Verwendung einer Instanzvariablen in „Punktnotation“. Zur Unterscheidung von einem Variablenzugriff müssen zusätzlich die Parameter in Klammern angegeben werden, selbst wenn die Parameter-Liste leer ist. 49 vgl. 2.1.2 Bezeichner und Namenskonventionen vgl. 2.6.2 51 vgl. 2.2 52 vgl. 2.6.4 50 42 Programmieren in Java Ein praktisches Beispiel 1. Erstellen der Klasse Rechentafel zum Rechnen mit ganzen Zahlen Die grundlegende Klassendefinition ist: class Rechentafel{ } Das ist eine Java-Klasse (in einfachster Form), selbstverständlich passiert hier noch nicht viel. Damit etwas Aktion stattfinden kann, müssen Zustand und Verhalten diverser Rechentafel-Objekte festgehalten werden können. Der Zustand wird in Variablen gespeichert. In Rechentafel-Objekten gibt es zwei Operanden zur Aufnahme ganzzahliger Werte für die Berechnung und eine Variable zur Aufnahme des Resultats nach der Berechnung. Operanden und Resultat sind in der Klasse Rechentafel Instanzvariable, da jedes Rechentafel-Objekt dafür verschiedene Werte haben kann. Folgende Instanzvariable (mit Datentyp int) werden in den durch „{ }“markierten Klassenkörper eingetragen: // Instanzvariable int ersterOperand = 0; int zweiterOperand = 0; Da keine spezifische Angabe zur Sichtbarkeit vorliegen, haben Objekte auf die Variablen Zugriff, die aus Klassen innerhalb des Klassenverzeichnisses gebildet werden. Rechentafel-Objekte sollten „Rechnen“ können. Dieses Verhalten bedeutet: Die vorliegenden Instanzvariablen sind durch Instanzmethoden zu ergänzen, z.B.: // Operationen int addiere() { return ersterOperand + zweiterOperand; } int subtrahiere() { return ersterOperand - zweiterOperand; } int multipliziere() { return ersterOperand * zweiterOperand; } int dividiere() { // ganzzahlige Division return ersterOperand / zweiterOperand; } Die Klasse ist damit für Rechentafel-Objekte vollständig bereitgestellt. Allerdings wird erst nach dem Hinzufügen der main()-Methode aus dieser Klasse eine JavaAnwendung1: 2. Die Klasse Rechentafeltest zum Überprüfen der Klasse Rechentafel Die Klasse Rechentafeltest ist durch die Anwendung der Klasse Rechentafel bestimmt. Sie enthält die main()-Methode. Im Mittelpunkt der Anwendung steht die Anweisung: Rechentafel einRechenobjekt = new Rechentafel(); 53 vgl. pr14101 43 Programmieren in Java Sie erzeugt eine Instanz der Klasse Rechentafel und speichert eine Referenz darauf in der Variablen „einRechenobjekt“. In jeder objektorientierten Programmiersprache lassen sich spezielle Methoden definieren, die bei der Initialisierung eines Objekts aufgerufen werden. In Java werden Konstruktoren als Methoden ohne Rückgabewert definiert, die den Namen der Klasse erhalten, zu der sie gehören. Falls eine Klasse keinen expliziten Konstruktor besitzt, wird ein parameterloser „default“-Konstruktor aufgerufen (, der hier zusammen mit dem Operator new verwendet wird). import java.lang.*; class Rechentafeltest extends Object { // Zur Ausführung mit dem Java-Interpreter wird eine main()-Methode // benoetigt public static void main(String argv[]) { // Erzeugen einer Instanz der Klasse Rechentafel Rechentafel einRechenobjekt = new Rechentafel(); // Setzen der Instanzvariablen einRechenobjekt.ersterOperand = 3; einRechenobjekt.zweiterOperand = 2; // Test mit einem Rechenobjekt der Klasse Rechentafel System.out.println("Test mit einem Rechentafel-Objekt"); // Aufruf der Methoden und Ausgabe des jeweiligen Resultats System.out.print("Addition "); System.out.println("von " + einRechenobjekt.ersterOperand + " und " + einRechenobjekt.zweiterOperand + " ist " + einRechenobjekt.addiere()); System.out.print("Subtraktion "); System.out.println("von " + einRechenobjekt.ersterOperand + " und " + einRechenobjekt.zweiterOperand + " ist " + einRechenobjekt.subtrahiere()); System.out.print("Multiplikation "); System.out.println("von " + einRechenobjekt.ersterOperand + " und " + einRechenobjekt.zweiterOperand + " ist " + einRechenobjekt.multipliziere()); System.out.print("Division "); System.out.println("von " + einRechenobjekt.ersterOperand + " und " + einRechenobjekt.zweiterOperand + " ist " + einRechenobjekt.dividiere()); } } Einfache Typen55 und Referenztypen Einfache Typen Jedes Java-Programm besteht aus einer Sammlung von Klassen. Der vollständige Code von Java wird in Klassen eingeteilt. Es gibt davon nur eine Ausnahme: Boolesche Operatoren, Zahlen und andere einfache Typen56 sind in Java erst einmal keine Objekte. Java hat für alle einfachen Typen sog. Wrapper-Klassen implementiert. Ein Wrapper-Klasse ist eine spezielle Klasse, die eine Objektschnittstelle für die in Java verschiedenen primitiven Typen darstellt. Über 55 56 vgl. 2.2.1 vgl. 2.2.1 44 Programmieren in Java Wrapper-Objekte können alle einfachen Typen wie Klassen behandelt werden. Java besitzt acht primitive Datentypen57: - vier Ganzzahltypen mit unterschiedlichen Wertebereichen - zwei Gleitpunktzahlentypen mit unterschiedlichen Wertebereichen nach IEEE Standard of Binary Floating Point Arithmetic. - einen Zeichentyp Primitiver Typ boolean char byte short int long float double void Größe 1-Bit 16-Bit 8-Bit 16-Bit 32-Bit 64-Bit 32-Bit 64-Bit - Minimum Unicode 0 -128 -215 -231 -263 IEEE 754 IEEE 754 - Maximum Unicode 216-1 +128 +215-1 +231-1 +263-1 IEEE 754 IEEE 754 - Wrapper-Klasse Boolean Character Byte Short Integer Long Float Double Void Den primitiven Typen sind „Wrapper“-Klassen zugeordnet, die ein nicht primitives Objekt auf dem „Heap“ zur Darstellung des primitiven Typs erzeugen, z.B.: char zeichen = ‘x‘; Character zeichenDarstellung = new Character(zeichen) bzw. Character zeichenDarstellung = new Character(‘x‘); Referenztypen Neben den primitiven Typen gibt es die Referenztypen. Dazu gehören: Objekte der benutzerdefinierten und aus vom System bereitgestellten Klassen, der Klassen String und Array (Datenfeld). Weiterhin gibt es die vordefinierte Konstante „null“, die eine leere Referenz bezeichnet. „String“ und „Array“ weisen einige Besonderheiten aus: - Für Strings und Arrays kennt der Compiler Literale, die einen expliziten Aufruf des Operator new überflüssig machen - Arrays sind klassenlose Objekte. Sie können ausschließlich vom Compiler erzeugt werden, besitzen aber keine explizite Klassendefinition. Sie werden dennoch vom Laufzeitsystem wie normale Objeklte behandelt. - Die Klasse String ist zwar im JDK vorhanden. Der Compiler hat aber Kenntnis über den inneren Aufbau von Strings und generiert bei Anzeigeoperationen Code, der auf Methoden der Klassen String und StringBuffer zugreift. Konstruktoren Eine „Constructor“-Methode bestimmt, wie ein Objekt initialisiert wird. Konstruktoren haben immer den gleichen Namen wie die Klasse und besitzen keine „return“Anweisung. Java erledigt beim Aufruf eines Konstruktors folgende Aufgaben: - Speicherzuweisung für das Objekt - Initialisieung der Instanzvariablen des Objekts auf ihre Anfangswerte oder einen Default-Wert (0 bei Zahlen, „null“ bei Objekten, „false“ bei booleschen Operatoren. - Aufruf der Konstruktor-Methode der Klasse 57 vgl. 2.2.1 45 Programmieren in Java Gewöhnlich stellt man „explizit“ einen „default“-Konstruktor zur Verfügung. Dieser parameterlose Konstruktor überlagert den implizit bereitgestellten „default“Konstruktor. Er wird dann bei allen parameterlosen Instanzierungen verwendet. Konstruktoren können aber auch – wie normale Dateien – Parameter übergeben bekommen, z.B.58: public Rechentafel(int ersterOperand, int zweiterOperand) { super(); // Aufruf des Default-Konstruktors der Superklasse this.ersterOperand = ersterOperand; this.zweiterOperand = zweiterOperand; } super() bestimmt einen Aufruf des „default“-Konstruktors der eindeutig bestimmten „Superklasse“. „super(...)“ darf nur als erste Anweisung eines Konstruktors auftreten. Generell bezeichnet das reservierte Wort „super“, die nach „extends“ benannte Superklasse. Häufig sind die Parameter Anfangswerte für Instanzvariablen und haben oft den gleichen Namen wie die entsprechenden Instanzvariablen. In diesen Fällen löst die Verwendung von bsp. this.ersterOperand = ersterOperand; derartige Namenskonflikte auf. Bei „this“ handelt es sich um einen Zeiger, der beim Anlegen eines Objekts automatisch generiert wird. „this“ ist eine Referenzvariable, die auf das aktuelle Objekt zeigt und zum Ansprechen der eigenen Methoden und Instanzvariablen dient. Der „this“-Zeiger ist auch explizit verfügbar und kann wie eine ganz normale Objektvariable verwendet werden. Er wird als versteckter Parameter an jede nicht statische Methode übergeben. Konstruktoren können in Java verkettet aufgerufen werden, d.h. sie können sich gegenseitig aufrufen. Der aufrufende Konstruktor wird dabei als normale Methode angesehen, die über this einen weiteren Konstruktor der aktuellen Klasse aufrufen kann, z.B.: public Rechentafel() { this(0,1); } „Freundliche“ Klassen und „freundliche“ Methoden „Rechentafel“ ist eine „freundliche“ Klasse. Der voreingestellte „Defaultstatus“ einer Klasse ist immer „freundlich“ und wird dann verwendet, wenn keine Angaben über die Sichtbarkeit (Spezifizierer, Modifizierer) am Beginn der Klassendefinition vorliegen. Die freundliche Grundeinstellung aller Klassen bedeutet: Diese Klasse kann von anderen Klassen nur innerhalb desselben Pakets benutzt werden. Das PaketKonzept von Java faßt mehrere Klassen zu einem Paket über die Anweisung „package“ zusammen. Durch die Anweisung „import“ werden einzelne Pakete dann in einem Programm verfügbar gemacht. Klassen, die ohne „package“Anweisung definiert werden, werden vom Compiler in ein Standardpaket gestellt. Die „.java“- und „.class“-Dateien dieses Pakets befinden sich im aktuellen Verzeichnis oder im darunterliegenden Verzeichnis. 58 pr14102 46 Programmieren in Java Mit dem Voranstellen von „public“ vor die Klassendeklaration wird eine Klasse als „öffentlich“ deklariert.Dies bedeutet: Alle Objekte haben Zugriff auf „public“-Klassen (nicht nur die des eigenen Pakets). Der voreingestellte Defaultstaus einer Methode ist immer freundlich und wird immer dann verwendet, wenn keine explizite Angabe zur Sichtbarkeit am Anfang der Methodendeklaration vorliegt. Die freundliche Grundeinstellung aller Methoden bedeutet: Die Methoden können sowohl innerhalb der Klasse als auch innerhalb des zugehörigen Pakets benutzt werden. Zugriffsrechte auf Klassen, Variable und Methoden Es gibt in Java insgesamt 4 Zugriffsrechte: private Ohne Schlüsselwort protected public Zugriff nur innerhalb einer Klasse Zugriff innerhalb eines Pakets Zugriff innerhalb eines Pakets oder von Subklassen in einem anderen Paket Zugriff von überall Mit Voranstellen des Schlüsselworts „public“ können alle Klassen, Variablen / Konstanten und Methoden für einen beliebigen Zugriff eingerichtet werden. Eine derartige Möglichkeit, die in etwa der Zugriffsmöglichkeit globaler Variablen in konventionellen Programmiersprachen entspricht, ist insbesondere bei komplexen Programm-Systemen gefährlich. Es ist nicht sichergestellt, daß zu jedem Zeitpunkt die Werte der Instanzvariablen von Objekten bestimmte Bedingungen erfüllen. Möglich ist bspw. die Anweisung einRechenobjekt.zweiterOperand = 0; in der Klasse „Rechentafeltest“. Mit Sicherheit führt diese Anweisung bei der Ausführung einer Division auf einen Divisionsfehler. Abhilfe verspricht hier das Prinzip der Datenkapselung (data hiding, data encapsulation). Instanzvariable werden als „private“ erklärt, d.h.: Zugriff auf diese Instanzvariable nur innerhalb der Klasse. Von außerhalb kann nur indirekt über das Aufrufen von Methoden, die als „public“ erklärt sind, auf die Instanzvariablen zugegriffen werden. Deshalb sollte auch prinzipiell für jede Instanzvariable eine entsprechende „get“- und eine „set“-Methode („hole“ bzw. „setze“) zur Verfügung gestellt werden, die jeweils „public“ erklärt werden. Bsp.: Die Klasse „Rechentafel“ nimmt dann folgenden Gestalt an: class Rechentafel extends Object { // Instanzvariable private int ersterOperand = 0; private int zweiterOperand = 0; // Konstruktoren public Rechentafel() { this(0,1); } public Rechentafel(int ersterOperand, int zweiterOperand) { super(); this.ersterOperand = ersterOperand; this.zweiterOperand = zweiterOperand; 47 Programmieren in Java } // Operationen public int holersterOperand() { return ersterOperand; } public void setzersterOperand(int ersterOperand) { this.ersterOperand = ersterOperand; } public int holzweiterOperand() { return zweiterOperand; } public void setzweiterOperand(int zweiterOperand) { this.zweiterOperand = zweiterOperand; } public int addiere() { return ersterOperand + zweiterOperand; } public int subtrahiere() { return ersterOperand - zweiterOperand; } public int multipliziere() { return ersterOperand * zweiterOperand; } public int dividiere() { // ganzzahlige Division if (zweiterOperand == 0) { System.out.println("Fehler: Division durch Null"); System.out.println("Divisor wird auf 1 gesetzt!"); zweiterOperand = 1; } return ersterOperand / zweiterOperand; } } Das Beispiel zeigt folgende Empfehlungen für die Vergabemöglichkeit von Zugriffsrechten: Klassen Instanzvariable Instanzkonstanten Instanzmethoden public private public public, falls ein Zugriff von außen erforderlich und sinnvoll ist. private, falls es sich um klaseninterne Hilfsmethoden handelt. Analoge Überlegungen gelten auch für Klassenvariable und -methoden. Klassenvariable und Klassenmethoden Die Klasse „Rechentafel"59 wurde zusätzlich erweitert um // Klassenvariable static int anzRechenobjekte = 0; 59 pr14103 48 Programmieren in Java Das reservierte Wort static macht Variable und Methoden (wie bspw. main()) zu Klassenvariablen bzw. Klassenmethoden. Klassenvariable werden in der Klasse definiert und gespeichert. Deshalb wirken sich ihre Werte auf die Klasse und all ihre Instanzen aus. Jede Instanz hat Zugang zu der Klassenvariablen, jedoch gibt es für alle Instanzen dieser Variablen nur einen Wert. Durch Änderung des Werts ändern sich die Werte aller Instanzen der betreffenden Klasse. Die folgende Erweiterung des Konstruktors der Klasse „Rechentafel“ berücksichtigt das spezielle Verhalten von Klassenvariablen: public Rechentafel(int ersterOperand, int zweiterOperand) { super(); this.ersterOperand = ersterOperand; this.zweiterOperand = zweiterOperand; nummer = ++anzRechenobjekte; } „nummer“ ist eine Instanzvariable, „anzRechenObjekte“ ist die Klassenvariable. Das reservierte Wort final macht Variablen zu Größen, denen nur einmal bei der Deklaration ein Wert zugewiesen werden kann, d.h. sie werden dadurch zu Konstanten, z.B.: public static final double PI = 3.1415926535897932846; Klassenmethoden wirken sich wie Klassenvariable auf die ganze Klasse, nicht auf einzelne Instanzen aus. Klassenmethoden sind nützlich zum Zusammenfassen allgemeiner Methoden an einer Stelle der Klasse. So umfaßt die Math-Klasse zahlreiche mathematische Funktionen in der Form von Klassenmethoden. Es gibt keine Instanzen der Klasse Math. Auch rekurvive Programme benutzen Klassenmethoden, z.B.: // Berechnung der Fakultaet public static long fakultaet(int n) { if (n < 0) { return -1; } else if (n == 0) { return 1; } else { return n * fakultaet(n-1); } } Der Aufruf einer Klassenmethode kann im Rahmen der Punktnotation aber auch direkt erfolgen, z.B.: long resultat = fakultaet(i); bzw. long resultat = Rechentafel.fakultaet(i); unter der Voraussetzung: Die Klassenmethode „fakultaet“ befindet sich in der Klasse „Rechentafel“. 49 Programmieren in Java Lokale Variable und Konstanten Lokale Variable werden innerhalb von Methodendefinitionen deklariert und können nur dort verwendet werden. Es gibt auch auf Blöcke beschränkte lokale Variablen. Die Deklaration einer lokalen Variablen gilt in Java als ausführbare Anweisung. Sie darf überall dort erfolgen, wo eine Anweisung verwendet werden darf. Die Sichtbarkeit einer lokalen Variablen erstreckt sich von der Deklaration bis zum Ende des umschließenden Blocks. Lokale Variablen existieren nur solange im Speicher, wie die Methode oder der Block existiert. Lokale Variable müssen unbedingt ein Wert zugewiesen bekommen, bevor sie benutzt werden können. Instanz- und Klassenvariable haben einen typspezifischen Defaultwert. Auf lokale Variable kann direkt und nicht über die Punktnotation zugegriffen werden. Lokale Variable können auch nicht als Konstanten60 gesetzt werden. Beim Bezug einer Variablen in einer Methodendefinition sucht Java zuerst eine Definition dieser Variablen im aktuellen Bereich, dann durchsucht es die äußeren Bereiche bis zur Definition der aktuellen Methode. Ist die gesuchte Größe keine lokale Variable, sucht Java nach einer Definition dieser Variablen als Instanzvariable in der aktuellen Klasse und zum Schluß in der Superklasse. Konstanten sind Speicherbereiche mit Werten, die sich nie ändern. In Java können solche Konstanten ausschließlich für Instanz- und Klassenvariable erstellt werden. Konstante bestehen aus einer Variablendeklaration, der das Schlüsselwort final vorangestellt ist, und ein Anfangswert zugewiesen ist, z.B.: final int LINKS = 0; Überladen von Methoden In Java ist es erlaubt, Methoden zu überladen, d.h. innerhalb einer Klasse zwei unterschiedliche Methoden mit denselben Namen zu definieren. Der Compiler unterscheidet die verschiedenen Varianten anhand Anzahl und Typisierung der Parameter. Es ist nicht erlaubt, zwei Methoden mit exakt demselben Namen und identischer Parameterliste zu definieren. Es werden auch zwei Methoden, die sich nur durch den Typ ihres Rückgabewerts unterscheiden als gleich angesehen. Der Compiler kann die Namen in allen drei Fällen unterscheiden, denn er arbeitet mit der Signatur der Methode. Darunter versteht man ihren internen Namen. Dieser setzt sich aus dem nach außen sichtbaren Namen und zusätzlich kodierter Information über Reihenfolge und die Typen der formalen Parameter zusammen. Überladen kann bedeuten, daß bei Namensgleichheit von Methoden in „Superklasse“ und abgeleiteter Klasse die Methode der abgeleitetem Klasse, die der Superklasse überdeckt. Es wird aber vorausgesetzt, daß sich die Parameter signifikant unterscheiden, sonst handelt es sich bei der Konstellation Superklasse – Subklasse um den Vorgang des Überschreibens. Soll die Originalmethode aufgerufen werden, dann wird das Schlüsselwort „super“ benutzt. Damit wird der Methodenaufruf in der Hierarchie nach oben weitergegeben. Überschreiben einer Oberklassen-Methode: Zum Überschreiben einer Methode wird eine Methode erstellt, die den gleichen Namen, Ausgabetyp und die gleiche Parameterliste wie eine Methode der Superklasse besitzt. Da Java die erste Methodendefinition ausführt, die es findet und die in Namen, Ausgabetyp und 60 Das bedeutet: das Schlüsselwort final ist bei lokalen Variablen nicht erlaubt 50 Programmieren in Java Parameterliste übereinstimmt, wird die ursprüngliche Methodendefinition dadurch verborgen. Konstruktor-Methoden können „technisch“ nicht überschrieben werden. Da sie den gleichen Namen wie die aktuelle Klasse haben, werden Konstruktoren nicht vererbt, sondern immer neu erstellt. Wird ein Konstruktor einer bestimmten Klasse aufgerufen, wird gleichzeitig auch der Konstruktor aller Superklassen aktiviert, so daß alle Teile einer Klasse initialisiert werden. Ein spezielles Überschreiben kann über super(arg1, arg2, ...) ermöglicht werden. Die Methode toString() ist eine in Java häufig verwendete Methode. „toString()“ wird als weitere Instanzmethode der Klasse Rechentafel definiert und überschreibt die Methode gleichen Namens, die in der Oberklasse Object definiert ist, z.B.61: public String toString() { return " Rechenobjekt# " + nummer + " Erster Operand: " + ersterOperand + " Zweiter Operand: " + zweiterOperand; } } 1.4.1.2 Superklassen und Subklassen, Vererbung und Klassenhierarchie Definitionen Klassen können sich auf andere Klassen beziehen. Ausgangspunkt ist eine Superklasse, von der Subklassen (Unter-) abgeleitet sein können. Jede Subklasse erbt den Zustand (über die Variablen-Deklarationen) und das Verhalten der Superklasse. Darüber hinaus können Subklassen eigene Variable und Methoden hinzufügen. Superklassen, Subklassen bilden eine mehrstufige Hierarchie. In Java sind alle Klassen Ableitungen einer einzigen obersten Klasse – der Klasse Object. Sie stellt die allgemeinste Klasse in der Hierarchie dar und legt die Verhaltensweisen und Attribute, die an alle Klassen in der Java-Klassenbibliothek vererbt werden, fest. Vererbung bedeutet, daß alle Klassen in eine strikte Hierarchie eingeordnet sind und etwas von übergeordneten Klassen erben. Was kann von übergeordneten Klassen vererbt werden? Beim Erstellen einer neuen Instanz erhält man ein Exemplar jeder Variablen, die in der aktuellen Klasse definiert ist. Zusätzlich wird ein Exemplar für jede Variable bereitgestellt, die sich in den Superklassen der aktuellen Klasse befindet. Bei der Methodenauswahl haben neue Objekte Zugang zu allen Methodennamen ihrer Klasse und deren Superklasse. Methodennamen werden dynamisch beim Aufruf einer Methode gewählt. Das bedeutet: Java prüft zuerst die Klasse des Objekts auf Definition der betreffenden Methode. Ist sie nicht in der Klasse des Objekts definiert, sucht Java in der Superklasse dieser Klasse usw. aufwärts in der Hierarchie bis die Definition der Methode gefunden wird. Ist in einer Subklasse eine Methode mit gleichem Namen, gleicher Anzahl und gleichem Typ der Argumente wie in der Superklasse definiert, dann wird die Methodendefinition, die zuerst (von unten nach oben in der Hierarchie) gefunden wird, ausgeführt. Methoden der abgeleiteten Klasse überdecken die Methoden der Superklasse (Overriding beim Überschreiben / Überdefinieren). 61 pr14103 51 Programmieren in Java Zum Ableiten einer Klasse aus einer bestehenden Klasse ist im Kopf der Klasse mit Hilfe des Schlüsselworts „extends“ ein Verweis auf die Basisklasse anzugeben. Dadurch wird bewirkt: Die abgeleitete Klasse erbt alle Eigenschaften der Basisklasse, d.h. alle Variablen und alle Methoden. Durch Hinzufügen neuer Elemente oder Überladen der vorhandenen kann die Funktionalität der abgeleiteten Klasse erweitert werden. Die Vererbung einer Klasse kann beliebig tief geschachtelt werden. Eine abgeleitete Klasse erbt dabei jeweils die Eigenschaften der unmittelbaren „Superklasse“, die ihrerseits die Eigenschaften ihrer unmittelbaren „Superklasse“ erbt. Superklassen können abstrakte Klassen mit generischem Verhalten sein. Von einer abstrakten Klasse wird nie eine direkte Instanz benötigt. Sie dient nur zu Verweiszwecken. Abstrakte Klassen dürfen keine Implementierung einer Methode enthalten und sind damit auch nicht instanziierbar. Java charakterisiert abstrakte Klassen mit dem Schlüsselwort abstract. Abstrakte Klassen werden zum Aufbau einer Klassenhierarchie verwendet, z.B. für die Zusammenfassung von zwei oder mehr Klassen. Auch eine abstrakte Methode ist zunächst einmal durch das reservierte Wort "abstract" erklärt. Eine abstrakte Methode besteht nur aus dem Methodenkopf, anstelle des Methodenrumpfs (der Methodendefinition) steht nur das "Semikolon", z.B. public abstract String toString();. Enthält eine Klasse mindestens eine abstrakte Methode, so wird automatisch die gesamte Klasse zu einer abstrakten Klasse. Abstrakte Klassen enthalten keine Konstruktoren. Sie können zwar Konstruktoren aufnehmen, allerdings führt jeder explizite Versuch zur Erzeugung eines Objekts einer abstrakten Klasse zu einer Fehlermeldung. Abstrakte Methoden stellen eine Schnittstellenbeschreibung dar, die der Programmierer einer Subklasse zu definieren hat. Ein konkrete Subklasse einer abstrakten Klasse muß alle abstrakten Methoden der Superklasse(n) implementieren. Beispiel: Eine Klassenhierarchie für "geometrische" Objekte62 Das folgende Klassendiagramm zeigt die hierarchische Struktur von Klassen, die "geometrische Objekte" beschreiben. All diesen geometrischen Objekten ist gemeinsam: Sie werden durch einen Bezugspunkt (x,y) fixiert. DieGemeinsamkeiten sind in der abstrakten Klasse "Geomobjekt" zusammengefaßt. 62 vgl. pr14131 52 Programmieren in Java GeomObjekt private double x; private double y; public double holeX(); public void setzeX(double x); public double holeY(); public void setzeY(double y); Kreis Rechteck private double r; private double breite; private double hoehe; public double holeR(); public void setzeR(double r); public double umfang(); public double flaeche(); public String toString(); public double holeBreite(); public void setzeBreite(double breite) public double holeHoehe(double hoehe); public void setzeHoehe(double hoehe); public double umfang(); public double flaeche(); public String toString(); Ausgangspunkt ist die abstrakte Klasse "GeomObjekt". Dort ist der allen Objekten zugeordnete Bezugspunkt fixiert. Subklassen (z.B. Kreis und Rechteck) erben diesen Bezugspunkt und die zum Zugriff auf ihn bereitgestellte Methoden. Außerdem führen die Subklassen die nötigen Spezialisierungen auf das jeweilige geometrische Objekt durch. import java.lang.*; public abstract class GeomObjekt extends Object { // Instanzvariable private double x, y; // Bezugspunkt geom. Objekte // Instanzmethoden public double holeX() { return x; } public void setzeX(double x) { this.x = x; } public double holeY() { return y; } public void setzeY(double y) { this.y = y; } // Abstrakte Methoden public abstract String toString(); 53 Programmieren in Java public abstract double umfang(); public abstract double flaeche(); } import java.lang.*; public class Kreis extends GeomObjekt { // Instanzvariable private double r; // Radius // Konstruktoren public Kreis() { this(0.0,0.0,1.0); } public Kreis(double r) { this(0.0,0.0,r); } public Kreis(double x, double y, double r) { super(); super.setzeX(x); super.setzeY(y); this.setzeR(r); } // Instanzmethoden public double holeR() { return r; } public void setzeR(double r) { this.r = (r >= 0.0 ? r : -r); } public double umfang() { double u = 2 * r * Math.PI; return u; } public double flaeche() { return r * r * Math.PI; } public String toString() { return "Kreis: (" + holeX() + ", " + holeY() + ") " + "r = " + r; } } import java.lang.*; public class Rechteck extends GeomObjekt { // Instanzvariable private double breite, hoehe; // Konstruktoren public Rechteck() { this(0.0,0.0,1.0,1.0); } public Rechteck(double breite, double hoehe) { this(0.0,0.0,breite,hoehe); } 54 Programmieren in Java public Rechteck(double x, double y, double breite, double hoehe) { super(); super.setzeX(x); super.setzeY(y); this.setzeBreite(breite); this.setzeHoehe(hoehe); } // Instanzmethoden public double holeBreite() { return breite; } public void setzeBreite(double breite) { this.breite = ( breite >= 0 ? breite : -breite); } public double holeHoehe() { return hoehe; } public void setzeHoehe(double hoehe) { this.hoehe = ( hoehe >= 0 ? hoehe : -hoehe); } public double umfang() { return 2 * breite + 2 * hoehe; } public double flaeche() { return breite * hoehe; } public String toString() { return "Rechteck: (" + holeX() + ", " + holeY() + ") " + "breite = " + breite + " hoehe = " + hoehe; } } Die Klasse "Test" Funktionsfähigkeit: überprüft die vorliegende import java.lang.*; public class Test extends Object { public static void main(String args[]) { // Erzeuge einen Kreis Kreis k1 = new Kreis(); System.out.println(k1.toString()); double umfang; umfang = k1.umfang(); System.out.println("Umfang = " + umfang); System.out.println("Flaeche = " + k1.flaeche()); // Erzeuge einen anderen Kreises Kreis k2 = new Kreis(1.0,1.0,2.0); System.out.println(k2.toString()); umfang = k2.umfang(); System.out.println("Umfang = " + umfang); System.out.println("Flaeche = " + k2.flaeche()); // Erzeuge einen dritten Kreis Kreis k3 = new Kreis(-2.0); 55 Klassenhierarchie auf Programmieren in Java System.out.println(k3.toString()); umfang = k3.umfang(); System.out.println("Umfang = " + umfang); System.out.println("Flaeche = " + k3.flaeche()); // Erzeugen eines Rechteck Rechteck r1 = new Rechteck(2.0,2.0); System.out.println(r1.toString()); umfang = r1.umfang(); System.out.println("Umfang = " + umfang); System.out.println("Flaeche = " + r1.flaeche()); // Erzeugen eines weiteren Rechtecks Rechteck r2 = new Rechteck(2.0,2.0,2.0,3.0); System.out.println(r2.toString()); umfang = r2.umfang(); System.out.println("Umfang = " + umfang); System.out.println("Flaeche = " + r2.flaeche()); } } 1.4.1.3 Referenzen und Referenztypen Referenzen Java erlaubt die Definition von Klassen, aus denen Objekte erzeugt werden können. Objekte können als Referenztypen behandelt werden, die von Variablen angelegt und verwendet werden. Beim Zuweisen von Variablen für Objekte bzw. beim Weiterreichen von Objekten als Argumente an Methoden werden Referenzen auf diese Objekte festgelegt. Bsp.: Referenzen auf Objekte in der Klasse Rechentafel bzw. Rechentafeltest System.out.println("Referenzen auf Objekte"); // Referenzen auf Objekte Rechentafel rechenObj1 = new Rechentafel(); rechenObj1.ersterOperand = 200; rechenObj1.zweiterOperand = 300; Rechentafel rechenObj3 = new Rechentafel(300,200); System.out.println("Operanden von Instanz 1: " + rechenObj1.ersterOperand + ", " + rechenObj1.zweiterOperand); System.out.println("Operanden von Instanz 2 vor Zuweisung: " + rechenObj3.ersterOperand + ", " + rechenObj3.zweiterOperand); rechenObj3 = rechenObj1; System.out.println("Operanden von Instanz 2 nach Zuweisung: " + rechenObj3.ersterOperand + ", " + rechenObj3.zweiterOperand); // Test auf Gleichheit if (rechenObj1 == rechenObj3) { System.out.println("Die beiden Objekte sind tatsaechlich gleich"); } Der Test mit „==“63 bestätigt: Es handelt sich jetzt (nach der Zuweisung) um das gleiche Objekt, d.h.: die gleiche Referenz. Die „null“-Referenz. In Java gibt es das spezielle Literal null. Es zeigt an, daß eine Referenz auf kein Objekt verweist. Der Wert ist nur für Referenzen vorgesehen und kann nicht in einen primitiven Typ umgewandelt werden. Die „null“-Referenz ist typenlos, d.h.: Sie kann jedem Objekt zugewiesen werden, z.B.: 63 Der Operator „==“ überprüft die Gleichheit der Referenzen. 56 Programmieren in Java Point p = null; String s = null; Da sich hinter „null“ kein Objekt verbirgt, kann darüber auch kein Objekt aufgerufen werden. Java verfügt über ein automatisches Speichermanagement. Deshalb braucht der Java-Programmierer sich nicht um die Rückgabe von Speicher zu kümmern, der von Referenzvariablen belegt ist. Ein mit niederer Priorität im Hintergrund arbeitender „Garbage Collector“ sucht ständig nach Objekten, die nicht mehr referenziert werden, um den belegten Speicher freizugeben. Finalizer-Methoden. Sie werden aufgerufen, kurz bevor das Objekt im Papierkob landet und sein Speicher freigegeben wird. Zum Erstellen einer Finalizer-Methode dient der folgende Eintrag in der Klassendefinition void finalize(){ ... }. Im Körper dieser Methode können alle möglichen Reinigungsprozeduren stehen, die das Objekt ausführen soll. Der Aufruf der Methode finalize() bewirkt nicht unmittelbar die Ablage im Papierkorb. Nur durch das Entfernen aller Referenzen auf das Objekt wird das Objekt zum Löschen markiert. Referenztypen Java kennt zwei Arten von Typen: einfache Typen und Referenztypen. Einfache Typen sind: boolean, byte, short, int, long, char, float und double. Klassen und Arrays sind Referenztypen. Ein Referenzwert enthält eine Referenz auf die Daten eines Objekts bzw. Arrays. Referenztypen müssen explizit mit Hilfe des Operators new erzeugt werden. 1.4.1.4 Konvertieren von Objekten und Primitivtypen „Casting“ ist ein Mechanismus zum Konvertieren des Werts eines Objekts oder eines primitiven Typs in einen anderen Typ. „Casting“ ergibt ein neues Objekt oder einen neuen Wert und wirkt sich nicht auf das ursprüngliche Objekt bzw. den ursprünglichen Wert aus. Konvertieren von Primitivtypen Ist der angestrebte Typ „größer“ als der zu konvertierende Typ, ist häufig kein explizites „Casting“ nötig. Ein „Byte“ oder ein Zeichen kann automatisch z.B. als „int“, ein „int“ als „long“, ein „int“ als „float“ behandelt werden. Es gehen beim Konvertieren des Werts keine Informationen verloren, weil der größere Typ mehr Genauigkeit bietet als der kleinere. Ist der angestrebte Typ „kleiner“ als der zu konvertierende Typ, ist „Casting“ nötig. Explizites Casting sieht so aus: (typname) wert „typname“ ist der Name des Typs, auf den konvertiert wird, „wert“ ist ein Ausdruck, der den zu konvertierenden Wert ergibt, z.B.: „(int) x/y;“. Dieser Ausdruck teilt x durch y und wandelt dann das Ergebnis in „int“ um. Da Casting eine höhere Priorität als Arithmetik hat, müssen die Klammern angegeben werden. 57 Programmieren in Java Konvertieren von Objekten Mit einer Einschränkung können auch Klasseninstanzen in Instanzen anderer Klassen konvertiert werden. Die betroffenen Klassen müssen durch Vererbung miteinander verbunden sein, d.h.: Ein Objekt kann nur in eine Instanz der Sub- oder Superklassen konvertiert werden, nicht aber in eine beliebige Klasse. Durch Konvertieren eines Objekts in eine Instanz der Superklasse gehen Informationen, die die Subklasse bereitgestellt hat, verloren. Spezifisches Casting ist erforderlich: (klassename) objekt. „klassename“ ist der Name der Klasse, in die das Objekt konvertiert werden soll. „objekt“ ist eine Referenz auf das zu konvertierende Objekt. Konvertieren von Primitivtypen in Objekte und umgekehrt Primitive Typen und Objekte sind in Java zwei völlig verschiedene Dinge. Konvertierung und automatisches Casting sind nicht möglich. Allerdings enthält das Paket „java.lang“ mehrere Subklassen, die je einem primitiven Datentyp entsprechen: Integer, Float, Boolean usw. Mit den in diesen Klassen definierten Klassenmethoden kann mit Hilfe von new für alle Primitivtypen eine „Art Objekt“ erstellt werden, z.B.: „Integer intObjekt = new Integer(13);“. Mit intValue() wird ein primitiver „int“-Wert aus einem Integer-Objekt extrahiert, z.B.: int ganzeZahl = intObjekt.intValue(); 1.4.1.5 Manipulieren von Objekten Vergleichen von Objekten Die relationalen Operatoren funktionieren in der Regel nur mit Primitivtypen, nicht mit Objekten. Die Ausnahme zu dieser Regel bilden die Operatoren „==“ und „!=“. Die Operatoren können für Objekte benutzt werden und prüfen, ob sich zwei Operanden auf genau das gleiche Objekt beziehen. Java besitzt kein Konzept für das Überladen (Overloading) von Operatoren. Zum Vergleichen von Instanzen eigendefinierter Klassen müssen Sondermethoden in die Klasse einbezogen werden. Bsp.: Gleichheitstest mit Objekten der „String“-Klasse64. Die String-Klasse definiert die Methode equals(), die jeden Zeichen der Zeichenkette prüft und „true“ ausgibt, falls die beiden Zeichenketten den gleichen Wert haben. Nach dem Operator „==“ sind die beiden „String“Objekte nicht gleich, weil sie zwar den gleichen Inhalt haben, aber nicht die gleichen Objekte sind. // Besonderheiten im Zusammenhang mit String-Objekten final class StringManipulationen { public static void main(String args[]) 64 pr14110 58 Programmieren in Java { String a = "test"; String b = "test"; String c = new String("test"); String d = new String("test"); // Díe folgenden Anweisungen geben wahr oder falsch // an die Konsole aus System.out.println("a == b " + (a==b)); System.out.println("c == d " + (c==d)); System.out.println("a == c " + (a==c)); System.out.println("a == \"test\" " + (a=="test")); System.out.println("c == \"test\" " + (c=="test")); System.out.println(); System.out.println("a.equals(b) " + a.equals(b)); System.out.println("c.equals(d) " + c.equals(d)); System.out.println("a.equals(c) " + a.equals(c)); System.out.println("a.equals(\"test\") " + a.equals("test")); System.out.println("c.equals(\"test\") " + c.equals("test")); } } // // // // // true false false true false // // // // // true true true true true Kopieren von Objekten Die clone()-Methode erzeugt ein Duplikat als Kopie eines Objekts. Damit ein Objekt geklont werden kann, muß es die „Cloneable“-Schnittstelle unterstützen. Die Cloneable-Schnittstelle im Paket java.lang hat selbst keine Methoden, sie dient lediglich als Indikator dafür, daß ein Objekt geklont werden kann. Das Format für die clone()-Methode ist: protected Object clone() throws CloneNotSupportedException Bsp.: Die Klasse Rechentafel erhält eine clone()-Methode zum Klonen von Rechentafel-Objekten65. public class Rechentafel implements Cloneable { // Instanzvariable private int ersterOperand = 0; private int zweiterOperand = 0; private int nummer; // Identifikationsnummer // Klassenvariable static int anzRechenobjekte = 0; // Konstruktoren Rechentafel() { this(0,1); } Rechentafel(int ersterOperand, int zweiterOperand) { super(); this.ersterOperand = ersterOperand; this.zweiterOperand = zweiterOperand; nummer = ++anzRechenobjekte; } // Kopierkonstruktor Rechentafel(Rechentafel rechenObjekt) { this(rechenObjekt.ersterOperand,rechenObjekt.zweiterOperand); } // Operationen public int holersterOperand() 65 pr14202 59 Programmieren in Java { return ersterOperand; } public void setzersterOperand(int ersterOperand) { this.ersterOperand = ersterOperand; } public int holzweiterOperand() { return zweiterOperand; } public void setzweiterOperand(int zweiterOperand) { this.zweiterOperand = zweiterOperand; } public int addiere() { return ersterOperand + zweiterOperand; } public int subtrahiere() { return ersterOperand - zweiterOperand; } public int multipliziere() { return ersterOperand * zweiterOperand; } public int dividiere() { // ganzzahlige Division try { /* Innerhalb des try-Blocks werden diejenigen kritischen Aktionen durchgefuehrt, die Ausnahmen erzeugen koennen */ return (ersterOperand / zweiterOperand); } catch(java.lang.ArithmeticException a) { System.out.println(" Fehler: Division durch Null"); System.out.println("Divisor wird auf 1 gesetzt!"); zweiterOperand = 1; return (ersterOperand / zweiterOperand); } } public String toString() { return "Rechenobjekt# " + nummer + " Erster Operand: " + ersterOperand + " Zweiter Operand: " + zweiterOperand; } public Object clone() { Object o = null; try { o = super.clone(); } catch(CloneNotSupportedException e) { System.out.println("Objekt kann nicht geklont werden"); } return o; } } 60 Programmieren in Java Diese Methode kann dann über ein in der Klasse Rechentafeltest instanziertes Objekt der Klasse Rechentafel aufgerufen werden: import java.lang.*; class Rechentafeltest extends Object { public static void main(String argv[]) { // Erzeugen Instanz der Klasse Rechentafel Rechentafel erstesRechenobjekt = new Rechentafel(3,2); System.out.println("Eine Instanz der Klasse " + erstesRechenobjekt.getClass().getName() + " wurde erzeugt."); System.out.println(erstesRechenobjekt.toString()); // Klonen eines Rechenobjekts System.out.println("Klonen des Objekts"); Rechentafel zweitesRechenobjekt = (Rechentafel) erstesRechenobjekt.clone(); System.out.println(zweitesRechenobjekt.toString()); if (erstesRechenobjekt == zweitesRechenobjekt) System.out.println("Erstes Objekt == Zweites Objekt"); else System.out.println("Erstes Objekt != Zweites Objekt"); // Manipulation System.out.println( "Aenderungen am Original bleiben unberuecksichtigt"); erstesRechenobjekt.setzersterOperand(7); erstesRechenobjekt.setzweiterOperand(5); System.out.println(erstesRechenobjekt.toString()); System.out.println(zweitesRechenobjekt.toString()); // Zuweisung System.out.println("Zuweisen des Objekts"); Rechentafel drittesRechenobjekt; drittesRechenobjekt = erstesRechenobjekt; System.out.println(drittesRechenobjekt.toString()); if (erstesRechenobjekt == drittesRechenobjekt) System.out.println("Erstes Objekt == Drittes Objekt"); else System.out.println("Erstes Objekt != Drittes Objekt"); // Manipulieren System.out.println("Aenderungen beziehen sich auch auf die Kopie"); erstesRechenobjekt.setzersterOperand(2); erstesRechenobjekt.setzweiterOperand(3); System.out.println(erstesRechenobjekt.toString()); System.out.println(drittesRechenobjekt.toString()); // Kopierkonstruktor System.out.println("Arbeiten mit einem Kopierkonstruktor"); Rechentafel viertesRechenobjekt = new Rechentafel(erstesRechenobjekt); System.out.println(viertesRechenobjekt.toString()); } } Der Test zeigt: Das geklonte Objekt enthält die gleichen Werte. Bestimmen der Klasse eines Objekts Die Klasse Class stellt über die Methode getName() den Namen einer Klasse zur Verfügung: public String getName() Bsp.: Der folgende Aufruf Rechentafel einRechenobjekt = new 61 Programmieren in Java Rechentafel(3,2); System.out.print("Eine Instanz der Klasse "); einRechenobjekt.zeigeKlasse(); betimmt den Namen der Klasse von „einRechenobjekt“. Die Methode „zeigeKlasse“ enthält die Methode getName() der Klasse Class. Das Objekt, dessen Klassenzugehörigkeit bestimmt werden soll ruft die Methode getClass() der Klasse Object auf. Das Ergebnis dieser Methode ist ein Class-Objekt, das die Methode getName() kennt. „getName()“ gibt die Zeichenkette aus. void zeigeKlasse() { System.out.print(this.getClass().getName()); } Ein anderen Test bietet der Operator „instanceof“. Er hat zwei Operanden: Ein Objekt links und den Namen der Klasse rechts. Der Ausdruck gibt true oder false aus, je nach dem, ob das Objekt eine Instanz der benannten Klasse ist, z.B.: if (!(anderesObj instanceof Rechentafel)) return false; der Operator instanceof kann auch als Schnittstelle benutzt werden. Falls ein Objekt eine Schnittstelle implementiert, gibt instanceof mit dem Schnittstellennamen auf der rechten Seite true aus. 1.4.1.6 Innere (lokale) und anonyme Klassen Bisher gab es nur Klassen, die entweder in Paketen organisiert waren oder sich in einem Verzeichnis befanden. Diese Form von Klassen heißen „Top-Level“-Klassen. Klassen können aber auch in andere Klassen aufgenommen werden. Das nennt sich dann „innere Klassen“. class Aussen { ... class Innen { ... } ... } Lokale Klassen Seit JDK 1.1 kann man innerhalb einer bestehenden Klasse z.B. X eine neue Klasse z.B. Y definieren66. Diese Klasse ist nur innerhalb von X sichtbar. Objekte von Y können damit nur aus X erzeugt werden. Y kann aber auf alle Instanzmerkmale von X zugreifen. Bei der Instanzierung wird (neben einem impliziten this-Zeiger) ein weiterer Verweis auf die erzeugende Instanz der umschließenden Klasse übergeben, der es ermöglicht, auf sie zuzugreifen. Die Anwendung lokaler Klassen für die Ereignisbehandlung besteht darin, mit ihrer Hilfe die benötigten EventListener zu implementieren. Dazu wird das GUI-Objekt, das 66 Im JDK wird das Konzept als Inner Classes bezeichnet 62 Programmieren in Java einen Event-Handler benötigt, eine lokale Klasse definiert und aus einer passenden Adapter-Klasse abgeleitet. Es braucht nicht mehr das gesamte Interface implementiert zu werden, ( denn die Methodenrümpfe werden ja aus der Adapterklasse geerbt,) sondern lediglich die tatsächlich benötigetn Methoden. Anonyme Klassen Eine Variante der lokalen Klassen sind anonyme Klassen. Sie werden ebenfalls lokal zu einer anderen Klasse erzeugt, kommen aber ohne Klassennamen aus. Dazu werden sie bei der Übergabe eines Objekts an eine Methode oder als Rückgabewert einer Methode innerhalb einer einzigen Anweisung definiert und instanziert. Damit eine anonyme Klasse überhaupt irgendeiner sinnvollen Aufgabe zugeführt werden kann, muß sie aus einer anderen Klasse abgeleitet sein oder ein bestehendes Interface implementieren. Adapterklassen Eine Adapterklasse implementiert ein vorgegebenes Interface mit leeren Methodenrümpfen. Adapterklassen können verwendet werden, wenn aus einem Interface lediglich ein Teil der Methoden benötigt wird, der Rest aber uninteressant ist. In diesem Fall leitet man eine neue Klasse aus der Adapterklasse ab, anstatt das zugehörige Interface zu implementieren und überlagert die benötigten Methoden. Alle übrigen Methoden des Interfaces werden von der Basisklasse zur Verfügung gestellt. Zu jedem der Low-Level-Ereignisempfänger stellt java.awt.event eine passende Adapterklasse zur Verfügung. 1.4.1.7 Schnittstellen und Pakete Schnittstellen Definition Eine Schnittstelle ist in Java eine Sammlung von Methodennamen ohne konkrete Definition. Klassen haben die Möglichkeit zur Definition eines Objekts, Schnittstellen können lediglich ein paar abstrakte Methoden und Konstanten (finale Daten) definieren67. Schnittstellen bilden in Java den Ersatz für Mehrfachvererbung. Eine Klasse kann demnach nur eine Superklasse, jedoch dafür mehrere Schnittstellen haben. Ein Schnittstelle („Interface“) ist eine besondere Form der Klasse, die ausschließlich abstrakte Methoden und Konstanten enthält. Anstelle von class dient zur Definition eines „Interface“ das Schlüsselwort „interface“. "Interfaces" werden formal wie Klassen definiert, z.B.: public interface meineSchnittstelle { // Abstrakte Methoden 67 Angaben zur Implementierung sind nicht möglich 63 Programmieren in Java } bzw. public interface meineSpezialschnittstelle extends meineSchnittstelle { // Abstrakte Methoden } "Interfaces" können benutzt werden, indem sie in einer Klasse implementiert werden, z.B.: public class MeineKlasse extends Object implements meineSchnittstelle { /* Normale Klassendefinition + Methoden aus meineSchnittstelle */ } Eine Klasse kann mehrere Schnittstellen implementieren, z.B. public class MeineKlasse extends Object implements meineSchnittstelle1, meineSchnittstelle2 { /* Normale Klassendefinition + Methoden aus meinerSchnittstelle1 und meinerSchnittstelle2 */ } Verwendung Bei der Vererbung von Klassen spricht man von Ableitung, bei Interfaces nennt man es Implementierung. Durch Implementieren einer Schnittstelle verpflichtet sich die Klasse, alle Methoden, die im Interface definiert sind, zu implementieren. Die Implementierung eines Interface wird durch das Schlüsselwort „implements“ bei der Klassendefinition angezeigt. Bsp.: Natürliche Ordnungsbeziehungen sind in Java über das Interface Comparable eingefangen: /**************** Comparable.java ***************************** /** Das Interface deklariert eine Methode anhand der sich das * das aufgerufene Objekt mit dem uebergebenen vergleicht. * Fuer jeden vom Objekt abgeleiteten Datentyp muss eine solche * Vergleichsklasse implementiert werden. * Die Methode erzeugt eine Fehlermeldung, wenn "a" ein Objekt * einer anderen Klasse als dieses Objekt ist. * * int compareTo(Comparable a) * liefert 0, wenn this == 0 * liefert < 0, wenn this < a * liefert > 0, wenn this > a * */ public interface Comparable { public int compareTo(Comparable a); } Zahlreiche von Java bereitgestellte Referenztypen (Klassen) haben dieses Interface implementiert. Deshalb sortiert die nachfolgenden Sortierroutine (Sort) String- und Zahlen-Objekte, wie der Aufrufe aus SortTest beweisen. 64 Programmieren in Java public class Sort { public void sort(Comparable x[]) { bubbleSort(x); } public void bubbleSort(Comparable x[]) { for (int i = 0; i < x.length; i++) { for (int j = i + 1; j < x.length; j++) { if (x[i].compareTo(x[j]) > 0) { Comparable temp = x[i]; x[i] = x[j]; x[j] = temp; } } } } } public class SortTest { public static void main(String args[]) { String sa[] = { "Juergen","Christian","Bernd","Werner","Uwe", "Erich","Kurt","Karl","Emil","Ernst" }; // Ausgabe des noch nicht sortierten x System.out.println("Vor dem Sortieren:"); for (int i = 0; i < sa.length; i++) { System.out.println("sa["+i+"] = " + sa[i]); } // Aufruf der Sortieroutine Sort s = new Sort(); s.sort(sa); // Gib das sortierte Feld x aus System.out.println(); System.out.println("Nach dem Sortieren:"); for (int i = 0; i < sa.length; i++) { System.out.println("sa["+i+"] = " + sa[i]); } System.out.println(); int za[] = { 50, 70, 80, 10, 20, 30, 1, 2, 3, 99, 12, 11, 13}; Integer o[] = new Integer[za.length]; for (int i = 0; i < za.length; i++) { o[i] = new Integer(za[i]); } // Ausgabe des noch nicht sortierten za System.out.println("Vor dem Sortieren:"); for (int i = 0; i < za.length; i++) { System.out.println("za["+i+"] = " + za[i]); } // Aufruf der Sortieroutine Sort zs = new Sort(); zs.sort(o); // Gib das sortierte Feld x aus 65 Programmieren in Java for (int i = 0; i < za.length; i++) { za[i] = ((Integer) o[i]).intValue();; } System.out.println(); System.out.println("Nach dem Sortieren:"); for (int i = 0; i < za.length; i++) { System.out.println("za["+i+"] = " + za[i]); } } } Konstanten. Neben abstrakten Methoden können Interfaces auch Konstanten (Variablen mit dem Attributen static und final) enthalten. Wenn eine Klasse ein solches Interface implementiert, erbt es gleichzeitig auch alle seine Konstanten. Ein Interface kann ausschließlich Konstanten enthalten. Pakete Definition: Pakete sind in Java Zusammenfassungen von Klassen und Schnittstellen. Sie entsprechen in Java den Bibliotheken anderer Programmiersprachen. Pakete ermöglichen, daß modulare Klassengruppen nur verfügbar sind, wenn sie gebraucht werden. Potentielle Konflikte zwischen Klassenamen in unterschiedlichen Klassengruppen können dadurch vermieden werden. Für Methoden und Variablen innerhalb von Paketen besteht eine Zugriffsschutzschablone. Jedes Paket ist gegenüber anderen, nicht zum Paket zählenden Klassen abgeschirmt. Klassen, Methoden oder Variablen sind nur sichtbar für Klassen im gleichen Paket. Klassen, die ohne „package“ Anweisung definiert sind, werden vom Compiler in ein „Standardpaket“ gestellt. Voraussetzung dafür ist: Die „.java“- und „.class“-Dateien dieses Pakets befinden sich im aktuellen Verzeichnis (oder in einen darunter liegenden Klassenverzeichnis). Die Java-Klassenbibliothek68 von Java 1.0 enthält folgende Pakete: - java.lang: Klassen, die unmittelbar zur Sprache gehören. Das Paket umfaßt u.a. die Klassen Object, String, System, außerdem die Sonderklassen für die Primitivtypen (Integer, Character, Float, etc.) Object Class String StringBuffer Thread ThreadGroup Throwable 68 Aus dieser Klasse leiten sich alle weiteren Klassen ab. Ohne explizite Angabe der Klasse, die eine neue Klasse erweitern soll, erweitert die neue Klasse die Object-Klasse. Die Klasse Object ist die Basis-Klasse jeder anderen Klasse in Java. Sie definiert Methoden, die von allen Klasse in Java unterstützt werden. Für jede in Java definierte Klasse gibt es eine Instanz von Class, die diese Klasse beschreibt Enthält Methoden zur Manipulation von Java-Zeichenketten Dient zum Erstellen von Java-Zeichenketten Stellt einen Ausführungs-Thread in einem Java-Programm dar. Jedes Programm kann mehrere Threads laufen lassen Ermöglicht die Verknüpfung von Threads untereinander. Einige Thread-Operationen können nur von Threads aus der gleichen ThreadGroup ausgeführt werden. Ist die Basisklasse für Ausnahmen. Jedes Objekt, das mit der "catch"-Anweisung Die Klassenbibliothek des JDK befindet sich in einem Paket mit dem namen „java“. 66 Programmieren in Java gefangen oder mit der "throw"-Anweisung verworfen wird, muß eine Subklasse von Throwable sein. System Stellt spezielle Utilities auf Systemebene zur Verfügung Runtime Enthält eine Vielzahl gleicher Funktionen wie System, behandelt aber auch das Laufen externer Programme Process Stellt ein externes Programm dar, das von einem Runtime-Objekt gestartet wurde. Math Stellt eine Reihe mathematischer Funktionen zur verfügung Number Ist die Basisklasse für Double,Float, Integer und Long (Objeckt-Wrapper) Character Ist ein Objekt-Wrapper für den datentyp char und enthält eine Reihe nützlicher zeichenorientierter Operationen Boolean Ist ein Objekt-Wrapper für den Datentyp boolean ClassLoader Ermöglicht der Laufzeit-Umgebung von Java neue Klassen hinzuzufügen SecurityManager Legt die Sicherheits-Restriktionen der aktuellen Laufzeitumgebung fest. Viele JavaKlassen benutzen den Security-Manager zur Sicherstellung, daß eine Operation auch tatsächlich genehmigt ist. Compiler Ermöglicht, falls vorhanden, den Zugriff auf den "just-in-time" Compiler Abb.: Klassen des java.lang-Pakets Zusätzlich enthält das java.lang-Paket noch zwei Schnittstellen: Cloneable Runnable Muß von einem anderen Objekt implementiert werden, das dann geklont oder kopiert werden kann Wird zusammen mit der Thread-Klasse benutzt, um die aufgerufene Methode zu definieren, wenn ein Thread gestartet wird. Abb.: Schnittstellen im java.lang-Paket - java.util - java.io: Klassen zum Lesen und Schreiben von Datenströmen und zum Handhaben von Dateien. - java.net: Klassen zur Netzunterstützung, z.B. socket und URL (eine Klasse zum Darstellen von Referenzen auf Dokumente im World Wide Web). - java.awt (Abstract Windowing Toolkit): Klassen zum Implementieren einer grafischen Benutzeroberfläche. Das Paket enthält auch eine Klasse für Grafik (java.awt.Graphics) und Bildverarbeitung (java.awt.Image). - java.applet: Klassen zum Implementieren von Applets, z.B. die Klasse Applet. Die Java Version 1.1 hat die Klassenbibliothek umfassend erweitert69: Paket Java.applet Java.awt Java.awt.datatranfer Java.awt.event Java.awt.image Java.beans Java.io Java.lang Java.lang.reflect 69 Bedeutung Applets Abstract Window Toolkit ClipBoard-Funktionalität (Copy / Paste) AWT Event-handling Bildanzeige Java Beans Ein- und Ausgabe, Streams Elementare Sprachunterstützung Introspektion, besserer Zugriff auf Klassen durch Zusätzlich 15 weitere Packages, etwa 500 Klassen und Schnittstellen 67 Programmieren in Java Debugger und Inspektoren Java.math Java.net Java.rmi Java.rmi.dgc Java.rmi.registry Java.rmi.server Java.security Java.security.aci Java.security.interfaces Java.sql Java.util Java.util.zip Abb.: Klassenbibliothek der Java-Version 1.1 Netzzugriffe Remote Method Invocation, Zugriff auf Objekte in anderen virtuellen Maschinen RMI Distributed Garbage Collection Verwaltet Datenbank, die RMI-Verbindungen koordiniert RMI-Server Sicherheit durch digitale Signaturen, Schlüssel Access Control Lists Digital Signature Algorithm (DAS-Klassen) Datenbankzugriff (JDBC) Diverse Utilities, Datenstrukturen JAR-Files, Kompression, Prüfsummen Verwendung Jede Klasse ist Bestandteil eines Pakets. Der vollständige Name einer Klasse besteht aus dem Namen des Pakets, danach kommt der ein Punkt, gefolgt von dem eigentlichen Namen der Klasse. Zur Verwendung einer Klasse muß angegeben werden, in welchem Paket sie liegt. Hier gibt es zwei unterschiedliche Möglichkeiten: - - - Die Klasse wird über ihren vollen (qualifizieren) Namen angesprochen, z.B. java.util.Date d = new java.util.Date(); Am Anfang des Klassenprogramms werden die gewünschten Klassen mit Hilfe der importAnweisung eingebunden, z.B.: import java util.*; ...... Date d = new Date(); Die import-Anweisung gibt es in unterschiedlichen Ausprägungen: Mit "import paket.Klasse" wird genau eine Klasse importiert, alle anderen Klassen des Pakets bleiben verborgen Mit "import paket.*"70 können alle Klassen des angegebenen Pakets auf einmal importiert werden. Standardmäßig haben Java-Klassen Zugang zu den in java.lang befindlichen Klassen. Klassen aus anderen Paketen müssen explizit über den Paketnamen einbezogen oder in die Quelldatei importiert werden. 1.4.1.8 Polymorphismus und Binden Polymorphismus Definition 70 type import on demand, d.h.: Die Klasse wird erst dann in dem angegebenen Paket gesucht, wenn das Programm sie wirklich benötigt. 68 Programmieren in Java Polymorphismus ist die Eigenschaft von Objekten, mehreren Klassen (und nicht nur genau einer Klasse) anzugehören, d.h. je nach Zusammenhang in unterschiedlicher Gestalt (polymorph) aufzutreten. Java ist polymorph. Bsp.71: Das folgende Programm Poly ist eine Anwendung zu der bereits bekannten Klassenhierarchie für geometrische Objekte. Es behandelt Erscheinungsformen des Polymorphismus. import java.lang.*; public class Poly extends Object { public static void main(String args[]) { Kreis k = new Kreis(0.0,1.0,2.0); Rechteck r = new Rechteck(0.0,1.0,2.0,2.0); // GeomObjekt geom = new GeomObjekt(); /* unzulaessig, da GeomObjekt abstrakt ist */ Object o = new Object(); // Folgende Zuweisungen sind zulaessig GeomObjekt geom1 = k; GeomObjekt geom2 = new Kreis(1.0,1.0,1.0); GeomObjekt geom3 = r; GeomObjekt geom4 = new Rechteck(1.0,1.0,1.0,4.0); o = k; // Aufruf der Methoden toString() und umfang() // ueber geom1 System.out.println(geom1.toString()); System.out.println("Umfang: " + geom1.umfang()); /* geom1 ist ein GeomObjekt. GeomObjekt enthaelt eine (abstrakte) Definition von toString() und von umfang(). Zur Laufzeit ist der Wert von geom1 eine Referenz auf ein Objekt der Klasse Kreis, daher werden die im Kreis definierten (allgemeiner: die in Kreis definierten bzw. ererbten) Methoden toString() und umfang() ausgefuehrt. */ // Aufruf der Methoden toString() und umfang() ueber o // System.out.println(o.toString()); /* o ist ein Object. Object enthaelt eine Definition von toString(), daher akzeptiert der Java-Compiler (zur Uebersetzungszeit) o.toString(). */ // System.out.println("Umfang: " + o.umfang()); /* o ist ein Object. Object enthaelt oder erbt keine Definition von umfang(), daher akzeptiert der JavaCompiler (zur Uebersetzungszeit) o.umfang() nicht. */ // System.out.println("Umfang: " + (Rechteck) o.umfang()); /* (Rechteck) o.umfang() akzeptiert der Java-Compiler (zur Uebesersetzungszeit) aus dem gleichen Grund wie zuvor nicht. Die Schreibweise (Rechteck) o.umfang() bedeutet auch: Der Rueckgabewert von o.umfang() soll als RechteckReferenz interpretiert werden. */ System.out.println("Umfang: " + ((Rechteck) o).umfang()); /* o ist ein Object, (Rechteck o) bedeutet: o soll als Rechteck interprtiert werden. Fuer Rechteck-Objekte ist umfang() erkaert, der Java-Compiler akzeptiert (zur Uebersetzungszeit) ((Rechteck o).umfang(). Zur Laufzeit wird die in Recteck definierte (allgemeiner: die in Recheck definierte bzw. ererbte) 71 Vgl. pr14181 69 Programmieren in Java Methode umfang() ausgefuehrt. */ } } Binden Das Schema einer Botschaft wird aus "Empfänger Methode Argument" gebildet. Darüber soll eine Nachricht die physikalische Adresse eines Objekts im Speicher finden. Trotz Polymorphismus und Vererbung ist der Methodenname (oft. Selektor genannt) nur eine Verknüpfung zum Programmcode einer Methode. Vor der Ausführung einer Methode muß daher eine genaue Zuordnung zwischen dem Selektor und der physikalischen Adresse des tatsächlichen Programmcodes erfolgen (Binden oder Linken). In der objektorientierten Programmierung unterscheidet man: frühes und spätes Binden: Frühes Binden: Ein Compiler ordnet schon zum Zeitpunkt der Übersetzung des Programms die tatsächliche, physikalische Adresse der Methode dem Methodenaufruf zu. Spätes Binden: Hier wir erst zur Laufzeit des Programms die tatsächliche Verknüpfung zwischen Selektor und Code hergestellt. Die richtige Verbindung übernimmt das Laufzeitprogramm der Programmiersprache. Java unterstützt das Konzept des „Late Binding“. 70 Programmieren in Java 1.4.2 Klassen des Pakets java.lang 1.4.2.1 Die Klasse Object Die Object-Klasse ist die Basisklasse für jede andere Klasse in Java. Sie definiert Methoden, die von allen Klassen in Java unterstützt werden. Test auf Gleichheit von Objekten: public boolean equals(Object obj) Darüber kann ermittelt werden, ob zwei Objekte gleich sind. Subklassen überschreiben diese Methode zur Durchführung eines inhaltlichen Vergleichs. In jeder Klasse ist die Methode auch gut aufgehoben, denn nur ein Objekt weiß, wann es mit einem anderen gleich ist. Ob zwei Referenzen auf das gleiche Objekt zeigen, läßt sich durch den Vergleichsoperator == feststellen. Zeichenkettendarstellung von Objekten: public String toString() Darüber kann ein Objekt an einen Ausgabestrom übergeben werden. Berechnung von Hashcode: public int hashCode() berechnet den numerischen Wert, der als Index zur Speicherung eines Objekts in einer Hashtabelle verwendet werden kann. Hashsodes werden zur Speicherung von Elementen in Hashtabellen. Das sind Datenstrukturen, die eine effizienten Zugriff auf ihre Elemente erlauben. Klonen von Objekten: protected Object clone() throws CloneNotSupportedException, OutOfMemoryError erzeugt ein exaktes Duplikat eines Objekts. Ein Objekt kann nur geklont werden, falls es die CloneableSchnittstelle unterstützt. Fragen nach dem Namen der Objektklasse: public final Class getClass() Benachrichtungsmechanismus. Hierfür gibt es die Methoden: public final void notify() bzw. public final void notifyAll() und public final void wait() throws InterruptedException bzw. public final void wait(long timeout) throws InterruptedException. Threads72 können miteinander kommunizieren und gegenseitig auf sich warten. Object definiert fünf Versionen von wait() und notify() zur Synchronisation von Threads. Aufräumen: protected void finalize() throws Throwable. Eine spezielle Methode finalize() wird immer dann aufgerufen, wenn der Garbage Collector das Objekt entfernen möchte. Objekte sollten diese Funktion überschreiben, wenn sie bspw. noch Dateien schliessen müssen. Objektidentifikation: public String toString(). Jedes Objekt sollte sich durch die Methode toString() mit einer Zeichenkette identifizieren. Die Zeichenkette gibt die Inhalt der Attribute an. Neue Objekte sollten diese Methode überschreiben. Ist dies nicht der Fall, gelangt das zugehörige Programm zur Standardimplementierung in Object. 72 vgl. 1.4.2.4 71 Programmieren in Java 1.4.2.2 Die Class-Klasse Diese Klasse stellt Methoden zur Abfrage von Eigenschaften von Klassen zur Verfügung und erlaubt es, Klassen dynamisch zu laden und Instanzen dynamisch zu erzeugen. In Java hat jede Klasse eine entsprechende Instanz von Class. In Java liegen Klassen selbst als Objekt vor. Neben normalen Klassen werden auch Interfaces über Class-Objekte abgelegt. Für ein bestimmtes Objekt kann man auf mehreren Wegen das zugehörige Class-Objekt in Erfahrung bringen. Für ein bestimmtes Objekt kann man auf mehreren Wegen das zugehörige Class-Objekt in Erfahrung bringen: - Ist ein Exemplar der Klasse verfügbar, dann ruft man die Methode getClass() von java.lang.Object auf. Man erhält die zugehörige Klasse. - Ist die Klasse bekannt, besteht aber zusätzlich Interesse an den Vorfahren, dann kann man das mit getSuperClass() herausfinden - Ist die Klasse nicht zur Compilezeit aber zur Laufzeit bekannt, dann hilft die Klassenmethode forName() weiter. Ein Klassenobjekt kann eine Schnittstelle, eine Klasse, einen primitiven Datentyp oder auch ein Array beschreiben. Class public static Class forName(String className) throws ClassNotFoundException // liefert das Klassen-Objekt mit dem vollqualifizierten Namen der Klasse oder der Schnittstelle // className. Das Metaobjekt wird, falls es nicht von der VM eingebunden ist, gesucht und // geladen. Der Rückgabewert ist nicht null, falls es nicht gefunden werden konnte, sondern eine // ClassNotFoundException. public String getName() // liefert den voll qualifizierten Namen der Klasse, der Schittstelle, des Arrays oder des // primitiven Datentyps des Class-Objekts public Class getSuperclass() // liefert die Superklasse des Klassen-Objekts. public boolean isInterface() // liefert true, falls das Class-Objekt ein Interface beschreibt public boolean isArray() // liefert true, falls das Class-Objekt einen Array beschreibt public boolean isPrimitive() // testet, ob das Interface einen primitiven Datentyp beschreibt public int getModifiers() // liefert die Modifizierer für eine Klasse oder ein Interface public Field[] getFields() throws SecurityException // liefert ein Feld mit Field-Objekten73. public Method[] getMethods() throws SecurityException // gibt ein Array von Method-Objekten74 zurück, die alle öffentlichen Methoden der Klasse/ // Schnittstelle beschreiben ... Abb.: Die Klasse Class 73 74 Objekten der Klasse java.lang.reflect.Field Objekte der Klasse java.lang.reflect.Method 72 Programmieren in Java Class besitzt Methoden, die hauptsächlich zur Beschreibung der Schnittstellen einer Klasse und zur Einordnung in die Klassenhierarchie dienen. Diese abstrakte und dynamische Dokumentation von Klassen wird als Reflection bezeichnet75. Das folgende Programmstück76 testet Objekte systematisch durch: import java.util.*; class CheckClassTyp { public static void main(String args[]) { Class observer = Observer.class; Class observable = Observable.class; Class array = (new int[2][3][4]).getClass(); Class primitive = Integer.TYPE; checkClassType(observer); checkClassType(observable); checkClassType(array); checkClassType(primitive); } static void checkClassType(Class c) { String name = c.getName(); if (c.isArray()) System.out.println(name + " ist ein Array."); else if (c.isPrimitive()) System.out.println(name + " ist ein primitiver Datentyp."); else if (c.isInterface()) System.out.println(name + " ist ein Schnittstelle."); else System.out.println(name + " ist eine Klasse."); } } Liegt der Name der Klasse als Class-Objekt vor, kann man zur Laufzeit den voll qualifizierten Namen über die Methode getName() ausgeben. import java.util.Date; class BspName { public static void main(String [] args) { Date d = new Date(); Class c = d.getClass(); String s = c.getName(); System.out.println(s); System.out.println((new int[2][3][4]).getClass().getName()); } } Arrays werden über getName() mit führenden „[“ kodiert. Jede Klammer beschreibt die Tiefe des Arrays. Nach der Klammer folgt in kodierter Form, welchen Typ das Array speichert. So liefert System.out.println((new int [2] [3] [4] ). getClass().getName());den String „[[[I“. Nimmt das Array Objekte auf, wird dies mit einem „LKlassenname;“ kodiert. So ergibt (new Object[3]).getClass().getName() den String „[Ljava.lang.Object;“. 75 Die Klasse java.lang.Class ist Basis für Methoden, die Objekte vom Typ Constructor, Method, Field usw. zurückliefern. Die zugehörigen Klassen sind im Package java.lang.reflect definiert. 76 vgl. pr14220 73 Programmieren in Java 1.4.2.3 Die Klasse System Die Klasse System besteht nur aus Klassenvariablen und –methoden und kann nicht instanziiert werden. Sie stellt die Verbindung zum Betriebssystem her und erlaubt bspw. den Zugriff auf die Standardein- / Standardausgabe. System public static final InputStream in public static final PrintStream out public static final PrintStream err public static void setIn(Inputstream in) public static void setOut(PrintStream out) public static void setErr(PrintStream err) public static Securitymanager getSecurityManager() public static void setSecurityManager(SecurityManager s) public static long currentTimeMillis() public static Properties getProperties() public static void setProperties(Properties props) public static String getProperty(String key) public static void exit(int status) public static void gc() public static void load(String filename) Abb.: Die Klasse System System-Properties Properties sind Listen von Eigenschaften, die Programmen vom Java-Laufzeitsystem zur Verfügung gestellt werden. Jede Eigenschaft besitzt einen Name, unter dem auf sie zugegriffen werden kann. Property java.version java.vendor java.vendor.url java.home java.class.version java.class.path os.name os.arch os.version file.seperator path.seperator line.seperator user.name user.home user.dir Bedeutung Java-Versionsnummer Herstellerspez. Zeichenkette URL (Internet-Link) Installationsverzeichnis Versionsnummer der Java-Klassenbibliothek Aktueller Klassenpfad Name des Betriebssystems Betriebssystem-Architektur Versionsnummer des Betriebssystems Trennzeichen für die Bestandteile des Pfadnamens Trennzeichen für die Laufwerksangabe eines Pfadnamens Zeichenkette für Zeilenumschaltung Name des angemeldeten Benutzers Home-Verzeichnis Aktuelles Arbeitsverzeichnis Für den Zugriff auf diese Eigenschaften steht die Klasse Properties aus dem Paket java.util zur Verfügung. Sie erzeugt Property-Listen. Die Klasse Properties ist eine Ableitung der Klasse HashTable und stellt darüber eine Tabelle mit Schlüssel/Wertepaaren zur Verfügung. 74 Programmieren in Java Für den Zugriff auf einzelne Properties reicht meistens die Klassenmethode der Klasse System aus: public static String getProperty(String key) liefert die Eigenschaft zu dem key in Form einer Zeichenkette. Falls keine Eigenschaft unter diesem Namen (key) gefunden wird, wird „null“ zurückgegeben. Die Methode public static Properties getProperties(); liefert das komplette Properties-Objekt mit den System-Properties. Bsp.77: Ausgabe einer Liste aller System-Properties auf dem Bildschirm import java.lang.*; import java.util.*; class EigenschaftsTest extends Object { public static void main(String argv[]) { System.out.println(new Date()); // System-Eigenschaften Properties p = System.getProperties(); p.list(System.out); System.out.println("Speicher-Verwendung:"); Runtime rt = Runtime.getRuntime(); System.out.println("Totaler Speicher = " + rt.totalMemory() + " Freier Speicher = " + rt.freeMemory()); } } in, err und out out ist eine statische Variable vom Typ PrintStream, die beim Starten des System so initialisiert wird, daß ihre Ausgabe auf die Standardausgabe geleitet wird. Analog zu out gibt es err und in. Dabei dient err zur Ausgabe von Fehlermeldungen, in ist ein Standardeingabekanal, der zur Eingabe von der Tastatur verwendet wird. Mit Hilfe von public static void setIn(InputStream in); public static void setOut(PrintStream out); public static void setErr(PrintStream err); kann die Standardein- und Standardausgabe aus dem Programm heraus umgeleitet werden. public static void exit(int status) Mit System.exit(status) wird das laufende Programm beendet. „status“ dient als Fehleranzeige und wird als Exitcode an den Aufrufer des Programms zurückgegeben. Gemäß Konvention zeigt ein Wert größer oder gleich 1 einen Fehler während der Programmausführung an, 0 signalisiert ein fehlerfreies Programmende. 77 pr14231 75 Programmieren in Java public static void gc() führt einen expliziten Aufruf des Garbage Collector durch. Dieser sucht nach freiem Speicher und gibt ihn dann an das Laufzeitsystem zurück. Normalerweise ist ein aufruf dieser Methode nicht erforderlich, denn der Garbage Collector läuft als niedrig priorisierter Thread im Hintergrund. public static long currentTimeMillis() liefert die Anzahl der Millisekunden, die zum Zeitpunkt des Aufrufs seit Mitternacht des 1.1.1970 vergangen sind. Ob tatsächlich eine Auflösung von einer Millisekunde erreicht wird, ist von der Java-Implementierung abhängig. In PC-basierten Systemen orientiert sie sich meistens an der Auflösung des „System-Timer“. 1.4.2.4 Multithreading 1. Die Klasse Thread Threads werden in Java durch die Klasse Thread und das Interface Runnable implementiert. In beiden Fällen wird der Thread-Körper (, also der parallel auszuführende Code) in Form der zu überlagernden Methode run bereitgestellt. Die Kommunikation kann durch Zugriff auf die Instanz- oder Klassenvariablen oder durch Aufruf beliebiger Methoden, die innerhalb von run sichtbar sind, erfolgen. Ein Thread stellt einen einzigen Ablauf von Ausführungen in einem Java-Programm dar. Die Klasse Thread ist Bestandteil des Pakets java.lang und steht damit allen Anwendungen zur Verfügung. Über die Klasse Thread hat Java das (Betriebssystem-) Konzept der Nebenläufigkeit implementiert. Mit Nebenläufigkeit bezeichnet man die Fähigkeit eines Systems zwei oder mehr Vorgänge gleichzeitig oder quasi gleichzeitig ausführen zu lassen. Ein Thread ist ein eigenständiges Programmfragment, das parallel zu einem anderen Thread laufen kann. 76 Programmieren in Java << interface >> Runnable public abstract void run() Thread public static final int MIN_PRIORITY public static final int NORM_PRIORITY public static final int MAX_PRIORITY << Konstruktor >> public Thread() public Thread(Runnable target) << Methoden >> public static Thread currentThread() public static void yield() public static void sleep(long millis) throws InterruptedException public void start() public void run() public final void stop() //deprecated public final void stop(Throwable obj) //deprecated public void interrupt() public static boolean interrupted() public boolean isInterrupted() public void destroy() public final boolean isAlive() public final void suspend() //deprecated public final void resume() //deprecated public final void setPriority(int newPriority) public final int getPriority() ... public String toString() Abb.: Thread-Klassen Methoden der Klasse Thread public void run() Zum Erzeugen eines Thread muß eine eigene Klasse aus der Klasse Thread abgeleitet, und die Methode run überlagert werden. Mit Hilfe des Aufrufs der Methode start wird der Thread gestartet, die weitere Ausführung wird an run() übertragen. „start()“ wird nach dem Start beendet, der Aufrufer kann parallel zum neu erzeugten Thread fortfahren. Bsp.: Einfacher Thread, der in einer Endlosschleife einen Thread hochzählt. class MeinThread extends Thread { public void run() { int i = 0; while (true) 77 Programmieren in Java { // Endlosschleife, Abbruch durch Druecken von Strg+C System.out.println(i++); } } public static void main(String args[]) { // Erzeugen einer Instanz MeinThread t = new MeinThread(); // Start vom erzeugten Thread t.start(); // Impliziter Aufruf der Methode run() } } Der (quasi) parallel auszuführende Code wird durch die überlagerte Methode run bereitgestellt. Die Kommunikation kann durch Zugriff auf Instanz- und Klassenvariablen oder durch Aufruf beliebiger Methoden erfolgen, die innerhalb von run() sichtbar sind. Eine Java-Applikation wird immer dann beendet, wenn der letzte Thread beendet wurde(, der kein Hintergrund-Thread (Dämon) ist). Da ein einfaches Programm nur einen einzigen Vordergrund-Thread besitzt, (nämlich den, in dem main() läuft78,) wird es beendet, wenn main() beendet wird. Das Beispielprogramm erzeugt einen zusätzlichen Vordergrund-Thread und kann erst dann beendet werden, wenn dieser Thread beendet wurde. public static void sleep(long millis) throws InterruptedException sorgt dafür, daß der aktuelle Prozeß für die (in Millisekunden) angegebene Zeit unterbrochen wird. public static void sleep(long millis, int nanos) throws InterruptedException Die erzielbare Genauigkeit der Unterbrechungs-Wartezeit wird durch HardwareRestriktionen der Maschine begrenzt public final boolean isAlive() gibt true zurück, wenn der aktuelle Thread gestartet, aber noch nicht beendet wurde. Beendet wird ein Thread, wenn das Ende der run-Methode erreicht ist, oder wenn die Methode stop aufgerufen wurde. Bsp.: Beenden des Thread nach 2 Sekunden durch Aufruf von stop class MeinThread1 extends Thread { public void run() { int i = 0; while (true) { // Endlosschleife, Abbruch durch Druecken von Strg+C System.out.println(i++); } } public static void main(String args[]) { // Erzeugen einer Instanz 78 Beim Starten eines Java-Programms wird automatisch ein Thread für die Ausführung des Hauptprogramms angelegt. 78 Programmieren in Java MeinThread1 t = new MeinThread1(); // Start vom erzeugten Thread t.start(); // Impliziter Aufruf der Methode run() try { Thread.sleep(2000); } catch(InterruptedException e) { } t.stop(); } } Mit dem JDK 1.2 wurde stop() als deprecated markiert, da es nicht voirhersehbar und auch nicht definiert ist, an welcher Stelle ein Thread beim Aufruf von stop() abgebrochen wird. Ersatz für stop(): Man kann ein Flag für Threadabbruch setzen und dieses zyklisch in der Methode run des Thread abfragen. Falls dieses gesetzt ist, können alle Ressourcen freigegeben und der Thread durch breguläres Beenden von run() (bpw. Mit Hilfe von return) beendet werden. Das Flag darf allerdings nicht zu oft aber auch nicht zu selten abgefragt werden. Diese gegensätzlichen Anforderungen können sinnvoll durch einige Methoden der Klasse der Tread unterstützt werden: public void interrupt() setzt ein Flag, das eine Unterbrechungsanforderung signalisiert. public boolean isInterrupted() stellt fest, ob das Abbruchflag gesetzt wurde und der Thread beendet wurde. public static boolean interrupted() stellt den Status des Abbruchflag beim aktuellen Thread fest und setzt zusätzlich das Abbruchflag auf seinen Initialisierungswert zurück. Bsp.79: public class PR14240 extends Thread { int zaehler = 0; public void run() { while (true) { if (isInterrupted()) { break; } printLine(++zaehler); } } private void printLine(int cnt) { //Zeile ausgeben System.out.print(zaehler + ": "); for (int i = 0; i < 30; ++i) { System.out.print(i == zaehler % 30 ? "* " : ". "); } System.out.println(); 79 Vgl. pr14240 79 Programmieren in Java // 100 ms. warten try { Thread.sleep(100); } catch (InterruptedException e) { interrupt(); } } public static void main(String[] args) { PR14240 th = new PR14240(); { //Thread starten th.start(); //2 Sekunden warten try { Thread.sleep(2000); } catch (InterruptedException e) { } //Thread unterbrechen th.interrupt(); } } } Das Programm gibt zunächst über einen separaten Thread zunächst Textzeilen aus. Wahrscheinlich erfolgt der Aufruf von interrupt() während des Aufrufs von sleep(), da die Pause nach der Bildschirmausgabe vermulich länger dauert als die Bildschirmausgabe. Ist dies der Fall wird sleep() mit einer InterruptedException abgebrochen. Das Abbruchflag wird zurückgesetzt und der Aufruf von interrupt() damit wirkungslos. Da aber in der catch-Klausel interrupt() erneut aufgerufen wird, wird das Flag wieder gesetzt und run() die Abbruchaufforderung signalisiert. public final void join() throws InterruptedException Mit Hilfe dieser Methode kann gezielt auf die Beendigung eines Thread gewartet werden. So etwas ist sinnvoll, wenn zeitaufwendige Operationen vor dem Aufruf einer Folgeoperation noch beendet werden müssen. Der zeitaufwendigsten Operation ist ein eigener Thread zugeordnet. public static void yield() “Freiwilliges” Angebot des laufenden Thread, den Prozessor abzugeben. Unterbrechen eines Thread: Mit Hilfe der Methoden suspend bzw. resume ist es möglich einen Thread vorübergehend zu unterbrechen bzw. fortzusetzen. Durch suspend() wird die Ausführung unterbrochen, durch resume() wird der Thread (genauer: die Methode run) an der Stelle fortgesetzt, an der die Unterbrechung erfolgte. Sowohl suspend() als auch resume() sind aber deprecated und sollten nicht mehr verwendet werden. Verwaltung von Threads Threads besitzen administrative Eigenschaften, die sich in zwei Gruppen einteilen lassen: - Priorität und Name 80 Programmieren in Java - Thread-Gruppen Priorität, Name. Jeder Thread hat einen eindeutigen Namen. Ohne explizite Vorgabe erfolgt die Vergabe des Namens automatisch in der Form “Thread-“+n , wobei n ein int ist. Mit public void setName(String name) kann einem Thread ein Name zugewiesen werden. Mit public String getName() kann der Name eines Thread abgefragt werden. public static Thread currentThread() liefert den Zugriff auf den aktuellen Thread, der die Methode gerade ausführt, z.B.: public class ThreadTest { public static void main(String args[]) { System.out.println("THREAD: " + Thread.currentThread() ); } } /* Ausgabe am Bildschirm: THREAD: Thread[main,5,main] Die Ausgabe umfasst : Name, Priorität und Gruppe */ Neben dem Namen besitzt ein Thread noch eine Priorität. Die Priorität steuert der Scheduler in der Weise, daß bei Vorhandensein mehrerer Threads diejenigen mit höherer Priorität vor denen mit niedrigerer Priorität ausgeführt werden. Beim Anlegen eines neuen Thread bekommt dieser die Priorität des aktuellen Thread. Mit public void setPriority(int neuePrioritaet) kann die neue Priorität gesetzt und mit public int getPriority() abgefragt werden. Als Parameter kann an setPriority() ein Wert übergeben werden, der zwischen den Konstanten Thread.MIN_PRIORITY und Thread.MAX_PRIORITY liegt. Java bietet 10 Prioritätsstufen zwischen 1 (Thread.MIN_PRIORITY) und 10 (Thread.MAX_PRIORITY) an. Der Normalwert wird durch die Konstante Thread.NORM_PRIORITY festgelegt80. Thread-Gruppen. Sie verwalten Informationen für eine ganze Gruppe und bieten Methoden an, die auf alle Threads einer Gruppe angewendet werden können. In der Klassenbibliothek gibt es dafür eine eigende ThreadGroup. ThreadGroup << Konstruktoren >> public ThreadGroup(String name) public ThreadGroup(ThreadGroup parent, String name) << Methoden >> public final String getName(); public final ThreadGroup getParent() public int activeCount() public int activeGroupCount() public int enumerate(Thread[] list) public enumerate(ThreadGroup[] list) public void interrupt(); public boolean isDaemon() public void setDaemon(boolean daemon) public final int getMaxPriority() public final void setMaxPriority() 80 im aktuellen JDK 1, 5, 10 81 Programmieren in Java Abb.: Die Klasse TheadGroup Die Klasse ThreadGroup stellt zur Verwaltung mehrerer Threads Methoden bereit. Sie besitzt eine Default-Priorität, kann alle Threads anhalten und kann sie als Daemons ablaufen lassen. Threads werden durch Konstruktoren einer Gruppe zugeordnet. Thread-Gruppen können auch ineinander verschachtelt werden. Daemon-Threads. Es gibt Aufgaben, die zur Laufzeit des Programms zu erledigen sind, aber nie terminieren, z.B. der Garbage Collector. Da die virtuelle Maschine erst beendet wird, wenn kein Java-Thread mehr aktiv ist, müssen am Ende der Anwendung alle Threads angehalten werden. Es wäre aber lästig (und manchmal schwierig), die Hintergrund-Threads explizit zu beenden. Hierfür existiert das Konzept der Daemon-Threads. Da sich die virtuelle Maschine beendet, wenn nur noch Daemon-Threads laufen, brauch die Beendigung dieser Threads nicht weiter berücksichtigt werden. Ein Thread ist ein Daemon, wenn ihm das durch public final void setDaemon(boolean on) mitgeteilt wurde. Die Methode public final boolean isDaemon() gibt Auskunft über die Art eines Thread. 2. Das Interface Runnable Nicht immer ist es möglich, eine Klasse, die als Thread laufen soll, von Thread abzuleiten. Ein Thread kann deshalb auch über Implementierung des Interface Runnable erzeugt werden. Runnable enthält nur eine einzige Deklaration nämlich die der Methode run: public interface Runnable { public abstract void run(); } Implementierung von Runnable: Jede Klasse, deren Instanzen als Thread laufen, muß das Interface Runnable implementieren81. Eine nicht von Thread abgeleitete Instanz kann nach folgenden Implementierungsschritten als Thread laufen: - Erzeugen eines neuen Thread-Objekts - Übergabe des Objekts, das parallel ausgeführt werden soll, an den Konstruktor - Aufruf der Methode start() des Thread-Objekts Über start() startet das Thread-Objekt die run-Metode des übergebenen Objekts (das sie durch die Übergabe an den Konstruktor kennt). Da dieses Objekt das Interface Runnable implementiert, ist garantiert, daß eine geeignete Methode run zur Verfügung steht. 81 sogar die Klasse Thread selbst 82 Programmieren in Java Bsp.82: ThreadDemo Thread Runnable << interface >> MAX_PRIORITY …. sleep() run() start() join() …. ErsterThread run() ZweiterThread runs run() class ErsterThread extends Thread { public void run() { for (int i = 0; i < 10; i++) try { Thread.sleep( Math.round(1000 * Math.random()) ); System.out.println(this + " " + i); } catch (InterruptedException e) { System.err.println(e); } } } class ZweiterThread implements Runnable { public void run() { for (int i = 0; i < 10; i++) try { Thread.sleep( Math.round(1000 * Math.random()) ); System.out.println(Thread.currentThread().toString() + " " + i); } catch (InterruptedException e) { System.err.println(e); } } } public class ThreadDemo { public static void main(String [] args) { ErsterThread thread1 = new ErsterThread(); thread1.start(); Thread thread2 = new Thread(new ZweiterThread()); thread2.start(); try { thread1.join(); thread2.join(); 82 vgl. pr14240 83 Programmieren in Java } catch (InterruptedException e) { System.err.println(e); } } } Abb.: Definition von Threads Die Klasse Thread implementiert Runnable. Dort ist die Methode run() deklariert. Mit der Methode start() wird ein Thread gestartet, in dem run() aufgerufen wird. „join()“ wartet auf die Beendigung des Thread. ErsterThread ist Subklasse von Thread. „run()“ von Thread wird überschrieben. „sleep()“ legt einen Thread für eine bestimmte Zeit (in Millisekunden) schlafen. Hier wird zufällig eine Zeitspanne zwischen 0 und 1000 ms gewartet und dann der Scheifenzählerwert ausgegeben. start thread2 start thread1 join thread1 join thread2 main thread1 thread2 Zeit Abb.: Zusammenspiel von Threads in ThreadDemo ZweiterThread implementiert Runnable. Hier muß explizit eine Instanz von Thread erzeugt werden. Die Instanz von ZweiterThread wird einem Thread-Objekt übergeben, der die run()-Methode betreibt. ErsterThread kann sich selber schlafen legen. ZweiterThread legt die benutzte Instanz von Thread schlafen. Bei Programmstart existiert immer automatisch ein ErsterThread, dessen Ausführung in der main()-Methode beginnt. Dieser main-Thread startet nun zwei weitere Threads und wartet (join()) auf das Ende dieser Threads. Bei jedem Ablauf ergibt sich nicht-deterministisch eine andere Reihenfolge der Ausgaben 84 Programmieren in Java 3. Prozesse und Threads in Java Ein Java-Programm besitzt in der Regel eine Klasse mit einer statischen Methode main(). Bei jedem Start eines Java-Programms erzeugt das Betriebssystem einen Prozeß und startet die virtuelle Maschine (VM). Die VM erzeugt einen Haupt-Thread (main thread) auch Programmfaden, Kontrollfluß oder leichtgewichtiger Prozeß genannrt. Der main thread führt daraufhin die Methode main() aus. Eine Methode im Quelltext besteht aus einer Sequenz von Anweisungen. Beim Übersetzen des Quelltextes, wird jede Anweisung in mehrere elementare Teilanweisungen (byte codes) für die VM zerlegt. Ein Java-Thread führt diese einzelnen elementaren Anweisungen einer Methode (z.B. main()) und die Anweisungen der darin enthaltenen Methoden squentiell aus. Java-Prozesse werden auf Betriebssystem-Prozesse abgebildet, d.h.: Auch JavaThreads können auf Betriebssystem-Threads abgebildet werden. Man spricht von einer virtuellen Maschine, die native Threads unterstützt. Anwendung_1 Anwendung_2 Betriebssystem Java_VM_1 Java_VM_2 VM glob. Variable Prozess_1 lok. Variable Prozess_2 lok. Variable Prozess_3 lok. Variable Abb. Betriebssystem - VM Die Abbildung der Java-Prozesse auf BS-Prozesse hängt von der Implementierung der VM ab. Es gibt Implementierungen - die das Scheduling der VM-Prozesse übernehmen - die das Scheduling dem darunterliegenden BS überlassen "scheduling strategies". Threads benötigen für ihre Ausführung einen Prozessor. Viele Java-Anwendungen unterstützen nur einen Prozessor. Bei nur einem Prozessor kann nur ein Thread aktiv sein. Der Thread wird dem Prozessor zugeteilt. Man spricht in diesem Zusammenhang von Scheduling. Die Zuteilungsstrategie beeinflusst das Verhalten des Systems bei mehreren Threads. Folgende Zustände eines Thread können unterschieden werden: - Initialzustand Der Thread wurde erzeugt, führt aber die noch keine Methode aus, d.h. start() wurde noch nicht aufgerufen. - Lauffähig Ein Thread geht vom Initialzustand mit start() in den Zustand "lauffähig" über. 85 Programmieren in Java - Aktiv Ein Thread wird aktiv, wenn er von der VM aus der Menge der lauffähigen Threads ausgewählt wird. In diesem Zustand führt der Thread tatsächlich Code aus. Der Thread wird von der VM dazu entweder in den Zustand lauffähig zurückgesetzt oder er kommt in den Zustand "blockiert". - Blockiert Ein Thread ist blockiert, wenn er auf den exklusiven Zugriff auf eines bei synchronized angegebenen Objekts oder auf das Ergebnis mit wait() wartet. Ist der Thread blockiert, dann geht er in den Zustand "lauffähig" über, falls ein anderer Thread mit einem notify() oder notifyAll() das Ergebnis meldet und daraufhin die VM des blockierten Threads auswählt. - Endzustand. Der Thread ist in seinem Endzustand, falls run() ausgeführt wurde oder mit einer Ausnahme beendet wurde. Auswahl durch VM nach notify() lauffähig blockiert notify(), notifyAll() Initial run() Ende sleep() oder Auswahl durch VM in synchronized() aktiv warten auf Zugriff Ende von run() oder unbehandelte Ausnahme Abb.: Automatendarstellung der Übergänge zwischen den Zuständen eines Thread start() 4. Synchronisation nebenläufiger Prozesse Einführendes Beispiel. Mehrere Threads mit gemeinsamer Variablen. // // Mehrere Threads mit gemeinsamen Variablen // class Zaehler implements Runnable { private long zaehler = 0; public void run() { Thread faden = Thread.currentThread(); boolean fortfahren = true; while (fortfahren) { { System.out.println(faden + "ZAEHLER: " + zaehler); if (zaehler < 20) { ++zaehler; } else fortfahren = false; } } } } public class MehrfachSyncThreadTest { public static void main(String args[]) { Zaehler zaehler = new Zaehler(); 86 Programmieren in Java Thread zweiterThread = new Thread(zaehler,"zweiter"); zweiterThread.start(); // Start des zweiten Thread, // der run() der Klasse Zaehler aufruft zaehler.run(); // der main-Thread laeuft parallel // weiter. run() wird von 2 Threads ausgeführt } } Die beiden Threads versuchen den Zählerstand zu inkrementieren. Ziel ist, dass beiden gemeinsam zaehler von 0 auf 20 hochzählen. Auszüge aus einer möglichen Ausgabe: Abb.: Mögliche Ausgabe vom nicht synchronisierten MehrfachSyncThreadTest Die Ausgaben sind, wenn man den Code sieht, nicht plausibel. .... Thread[main,5,main]ZAEHLER: 2 Thread[zweiter,5,main]ZAEHLER: 2 .... Offensichtlich ist es notwendig, beide Threads zu koordinieren. Sie müssen so synchronisiert werden, dass nur ein Thread das Prüfenn der Bedingung und das Inkrementieren atomar, d.h. vollständig ohne Unterbrechung ausführt. Hinweis. Wie immer bei fehlender Synchronisation muß der im Beispiel gezeigte Fehler nicht zwingend auftreten und kann lange Zeit völlig unbemerkt bleiben. Je nachdem, wann die Threads aktiv sind bzw. bei einem Prozessor sich abwechseln, ergeben sich unterschiedliche Resultate83. Synchronisation paralleler Prozesse in Java. Mehrere Threads können nur über gemeinsame Variable kommunizieren. Folgende Konflikte können dabei auftreten: - Gleichzeitiges Lesen und Schreiben Ein Thread liest und ein anderer ändert gerade das Objekt, ist aber noch nicht fertig. - Gleichzeitiges Schreiben Mehrere Threads versuchen das Objekt gleichzeitig zu ändern. 83 Dies macht es extrem schwierig in einem fertigen System fehlerhafte Code-Stellen zu finden. Vor allem kann allein das Suchen des Fehlers mit Hilfe vom Debugger den Fehler verschwinden lassen. Durch das Beobachten mit dem Debugger wird das Abwechseln von Threads beeinflusst. 87 Programmieren in Java Rufen mehrere Threads Methoden gleichzeitig auf, dann kann ohne Synchronisation ein Zwischenzustand einer Methode den Ablauf einer anderen Methode beeinflussen. Zur Vermeidung von Konflikten gibt es mehrere Möglichkeiten: - Schreiben nicht zulassen Eine Klasse wird so definiert, dass eine Änderung der Attribute nach dem Erzeugen von Objekten nicht mehr möglich ist (immutable class). Bei Java können bspw. mehrere Threads problemlos auf ein gleiches Objekt der Klasse java.lang.String zugreifen, da ein String nicht mehr geändert werden kann. Jede Methode der Klasse produziert bei einer Ämnderung des Werts ein neues String Objekt.84 - Lesen und Schreiben synchronisieren Für einen Bereich, in den gemeinsame Variable geschrieben oder gelesen werden, benötigt jeder Thread eine Sperre bevor er diesen Bereich ausführt. Danach gibt er die Sperre wieder frei. Mit diesem gegenseitigen Ausschluß (mutually exclusive lock) lassen sich Zugriffe auf Objekte so koordinieren, dass Konflikte vermieden werden. In Java müssen alle Bereiche, in dem gemeinsame Variable geschrieben oder gelesen werden, explizit mit dem Schlüsselwort synchronized zur Vermeidung von Konflikten versehen werden. synchronized(O) { S } O ist ein beliebiger Ausdruck, der ein Java-Objekt liefert, und S eine Menge von Anweisungen. Erreicht ein Thread die synchronized Anweisung, dann gelten folgende Regeln: - Der Thread versucht exklusiven Zugriff auf das Objekt O zu erhalten. - Wenn kein anderer Thread den exklusiven Zugriff auf das Objekt hat, geling dies. - Besitzt ein anderer Thread den exklusiven Zugriff auf das Objekt, dann wartet der Thread, bid das Objekt O wieder freigegeben wird. Es können mehrere Threads auf die Freigabe warten. - Erhält ein Thread den exklusiven Zugriff auf O, dann tritt er in den Anweisungsblock ein. - Verläßt der Thread den Anweisungsblock, in dem er den exklusiven Zugriff erhalten hat, dann gibt er das Objekt wieder frei. Wenn eine Ausnahme (exception) auftritt, die nicht innerhalb eines Anweisungsblocks abgefangen wird, dann wird der Ausnahmeblock automatisch verlassen und dabei auch die Sperre wieder freigegeben. Verschachteln von synchronisierten Anweisungsblöcken ist möglich, auch wenn mehrmals mit dem gleichen Objekt synchronisiert wird. Erst beim Verlassen des äußeren Anweisungsblocks wird das Objekt freigegeben. Kritischer Bereich. In das letzte Beispiel kann zum Synchronisieren das Schlüsselwort synchronized eingesetzt werden. Prüfung und Inkrementierung stellen einen kritischen Bereich dar. Das ist ein Bereich, in dem Konflikte auftreten können. // // Mehrere Threads mit gemeinsamen Variablen // class Zaehler implements Runnable { private long zaehler = 0; public void run() { Thread faden = Thread.currentThread(); 84 Für eine performante Änderung eines String steht die Klasse java.lang.StringBuffer zur Verfügung, diese ist aber nicht sicher für die gleichzeitige Verwendung durch mehrere Threads (thread safe). 88 Programmieren in Java boolean fortfahren = true; while (fortfahren) { synchronized(this) { System.out.println(faden + "ZAEHLER: " + zaehler); if (zaehler < 20) { ++zaehler; } else fortfahren = false; } // fortfahren = inkrementieren(); } } } public class MehrfachSyncThreadTest { public static void main(String args[]) { Zaehler zaehler = new Zaehler(); Thread zweiterThread = new Thread(zaehler,"zweiter"); zweiterThread.start(); // Start des zweiten Thread, // der run() der Klasse Zaehler aufruft zaehler.run(); // der main-Thread laeuft parallel // weiter. run() wird jetzt von 2 Threads ausgeführt } } Abb.: Mögliche Ausgabe von MehrfachSyncThreadTest mit Synchronisierung Den kritischen Bereich kann man auch in einer eigenen Methode zusammen fassen. Dazu wird der Klasse Zaehler folgendermaßen umgestellt: class Zaehler implements Runnable { private long zaehler = 0; public void run() { Thread faden = Thread.currentThread(); boolean fortfahren = true; while (fortfahren) { 89 Programmieren in Java fortfahren = inkrementieren(faden); } } public boolean inkrementieren(Thread faden) { synchronized(this) { System.out.println(faden + "ZAEHLER: " + zaehler); if (zaehler < 50) { ++zaehler; return true; } return false; } } } Java erlaubt für die Methode inkrementieren() die folgende semantisch äquivalente Schreibweise: public synchronized boolean inkrementieren(Thread faden) { // Synchronisierte Methode { System.out.println(faden + "ZAEHLER: " + zaehler); if (zaehler < 50) { ++zaehler; return true; } return false; } } Es wird gewährleistet, dass nur ein Thread gleichzeitig in genau dieser Methode aktiv sein kann. Es wird nicht gewährleistet, dass kein anderer Thread gleichzeitig in einer anderen Methode aktiv ist, d. h. zaehler kann in einer anderen Methode weiterhin gleichzeitig geändert werden. Das bedeudet auch: "Lesende Methoden müssen synchronisiert werden". Bsp.: Einführen der Methode getZaehler(), die den Zählerstand zurückliefert public long getZaehler() { return zaehler; } Die Methode getZaehler() kann aus folgenden Gründen ein fehlerhaftes Ergebnis haben: - getZaehler() kann ausgeführt werden, während irgendeine andere auch synchronisierte Methode (z.B. inkrementieren()) aus einer Subklasse heraus ausgeführt wird. Diese andere Methode könnte zaehler zwischenzeitlich auf –1 setzen und erst am Ende einen korrekten Wert zuweisen. Die Methode getZaehler() könnte zwischenzeitlich den unsinnigen Wert –1 liefern. - Auch wenn keiner den Wert zwischenzeitlich auf –1 setzt, kann getZaehler() einen falschen Wert liefern. Java garantiert, dass alle Operationen auf primitiven Typen atomar ausgeführt werden, außer für double und long. Das bedeutet: Die Ausführung von ++zaehler könnte in zwei Schritten ablaufen. Zwischen den beiden Schritten könnte ein Schrittwechsel auftreten. Der Wert von zaehler nach dem 1. Schritt ist nicht spezifiziert. Aus diesen Gründen muß getZaehler() synchronisiert werden: 90 Programmieren in Java public synchronized long getZaehler() { return zaehler; } Synchronisierte Methoden und Vererbung. Die Synchronisation gehört in Java nur zur Implementierung und wird nicht vererbt. Daher kann auch eine InterfaceDefinition nicht das Schlüsselwort synchronized enthalten. Synchronisation statischer Methoden. In Java gehört zu jeder Klasse eine, vom System erzeugte Instanz der Klasse java.lang.Class. Diese Instanz beschreibt den Aufbau der Klasse und macht dies zur Laufzeit verfügbar. Der Klassenname gefogt von ".class" liefert die Instanz. Wenn eine statische Methode als synchronized gekennzeichnet ist, entspricht dies einer Synchronisierung mit der zu dieser Instanz gehörenden Instanz der Klasse java.lang.Class85. Bsp.: public class Zaehler { private static long maximum = 1000; public static synchronized long getMaximum() { return maximum; } … } Die Methode getMaximum() entspricht folgender Implementierung: public static long getMaximum() { synchronized(Zaehler.class) { return maximum; } } Bewachte kritische Bereiche. Bisher konnte ein Thread nur auf eine einzige Bedingung warten und zwar darauf, ob ein Zugriff zur Verfügung steht oder nicht. Manchmal möchte man komplexere zustandabhängige Bedingungen formulieren, auf deren Erfüllung man wartet. Das könnte man so beschreiben: await E then S endwait, wobei E ein boolscher Ausdruck (Wächter) und S eine Menge von Anweisungen ist. Sobald E erfüllt ist, wird S exklusiv von einem einzigen Thread ausgeführt. public class ClassMitGurdedSuspension { … private synchronized void awaitConditionE() { while (!E) { try { wait(); } catch (InterruptedException ex) { } } } public void synchronized guardedStatementS { 85 vgl. 1.4.2.2 91 Programmieren in Java awaitConditionE(); S } } Die folgenden Methoden der Klasse Object übernehmen "Wächter"-Funktionen: public final void wait() throws InterruptedException public final void wait(long timeout) throws InterruptedException public final void wait(long timeout,int nanos) throws InterruptedException - Der aktuelle Thread wird auf wartend gesetzt und in einen mit dem Objekt verknüpften Wartebereich gestellt - Der exklusive Zugriff, den der Thread hat, wird freigegeben. Das ist Voraussetzung dafür, dass andere Threads überhaupt notify() bzw. notifyAll() aufrufen können. - Es existiert noch die Möglichkeit bei wait() die maximale Wartezeit anzugeben. Ist diese verstrichen, dann wird automatisch für diesen Thread wie bei notify() verfahren. public final void notify() - Wenn wartende Threads existieren, dann wird nichtdeterministisch einer ausgewählt und dieser aus dem Wartebereich entfernt - Der ausgewählte Thread versucht Zugriff auf das Objekt zu bekommen. Der ausgewählte Thread kann den Zugriff nur dann bekommen, wenn der Thread der notify() aufgerufen hat und noch im Besitz des Zugriffs, diesen freigibt, und kein anderer Thread den Zugriff erhält. - Erhält der ausgewählte Thread später den Zugriff, dann setzt er die Ausführung mit dem auf wait() folgenden Code fort. public final void notifyAll() - Verläuft wie notify() mit dem Unterschied, daß alle wartenden Threads aus dem Wartebereich entfernt werden und wieder versuchen die Sperre zu bekommen. Nur wenn ein Thread den exklusiven Zugriff auf ein Objekt hat, kann er dessen wait(), notify(), oder notifyAll() aufrufen. Ansonsten wird eine IllegalMonitorStateException vom Laufzeitsystem erzeugt. Wenn eine Unterbrechung während eines wait() passiert (z.B. durch Aufruf von interrupt() der Klasse Thread, dann wird wie bei notify() verfahren. Die Ausführung wird aber fortgefahren mit dem Werfen einer Ausnahme der Klasse InterruptedException. Das Ziel ist es mit wait() auf ein Ereignis zu warten. Das Ereignis wird durch das Objekt oder einen Zustand des Objekts repräsentiert. Mit notify() wird dann das Eintreten des Ereignisses mitgeteilt. Das Monitor-Konzept in Java. Zur Synchronisation nebenläufiger Prozesse hat Java das Konzept des Monitors implementiert. Ein Monitor ist die Kapselung eines kritischen Bereichs (also eines Programmteils, der nur von jeweils einem Prozeß zur aktuellen Zeit durchlaufen werden darf) mit Hilfe einer automatisch gesetzten Sperre. Die Sperre wird beim Eintritt in den Monitor gesetzt und beim Verlassen wieder zurückgenommen. Ist sie beim Eintritt in den Monitor bereits von einem anderen Prozeß gesetzt, dann muß der aktuelle Prozeß warten, bis der Konkurent die Sperre freigegeben und den Monitor verlassen hat. Das Monitor-Konzept wird mit Hilfe des Schlüsselworts synchronized realisiert. Durch synchronized kann entweder eine komplette Methode oder ein Block innerhalb einer Methode geschützt werden. Der Eintritt in den so deklarierten Monitor wird als Sperre der this-Pointer verwendet, anderenfalls ist die Objektvariable explizit anzugeben. Neben dem Monitorkonzept stehen mit den Methoden wait() und notify() der Klasse Object noch weitere Synchronisationsprimitive zur Verfügung. Zusätzlich zu der bereits erwähnten Sperre, die einem Objekt zugeordnet ist, besitzt ein Objekt eine Warteliste. Dabei handelt es sich um eine (möglicherweise leere) Menge von 92 Programmieren in Java Threads, die von einem Scheduler unterbrochen wurden und auf Ereignisse warten, um fortgesetzt werden zu können. wait() und notify() dürfen nur aufgerufen werden, wenn das Objekt bereits gesperrt ist, also nur innerhalb eines synchronized-Blocks. Ein Aufruf von wait() nimmt die bereits gewährten Sperren (temporär) zurück und stellt dem Prozeß, der den Aufruf von wait() verursachte, in die Warteliste des Objekts. Dadurch wird er unterbrochen. Ein Aufruf von notify() entfernt einen (beliebigen) Prozeß aus der Warteliste des Objekts, stellt die (temporär) aufgehobenen Sperren wieder her und führt ihn dem normalen Scheduling zu. „wait()“ und „notify()“ sind damit für elementare Synchronisationsaufgaben geeignet, bei denen es weniger auf die Kommunikation als auf die Steuerung zeitlicher Abläufe ankommt . Zusätzlich bietet die Klasse Object auch noch die Methode notifyAll(). Der Unterschied zu notify() ist, dass alle wartenden Threads „aufgeweckt“ (reaktiviert werden, bei notify() nur ein einziger. Threadkommunikation Verschiedene Threads können auf verschiedenen austauschen: Streams, gemeinsam genutzte Objekte, beiden zuletzt angegebenen Alternativen basieren darauf, einen gemeinsamen Adressraum (Heap) teilen. Somit Objekte diesselben Objekte im Speicher ansprechen. Wegen effizient Daten statische Variablen. Die dass verschieden Threads können mehrere Thread- 1.4.2.5 Die Klassen String und StringBuffer Ein String ist eine Sammluung von Zeichen, die im Speicher geordnet abgelegt werden. Die Zeichen setzen sich aus einem Zeichensatz zusammen, der in Java dem Unicode-Standard entspricht. In Java ist eine Symbiose zwischen String als Objekt und String als eingebautem Datentyp vorgenommen worden. Möglich ist eine Zuweisung von String-Literalen an Objekte und die Verknüpfung mehrerer Strings zu einem. Die Klasse String Die Klasse String repräsentiert Zeichenketten, die sich nicht ändern. Konstruktoren: String() String(String wert) String(char[] wert) Erzeugt ein leeres String-Objekt Erzeugt einen neuen String durch Duplizierung eines bereits vorhandenen. Erzeugt einen neuen String aus einem vorhandenen ZeichenArray. Zeichenextraktion char charAt(int index) Liefert das Zeichen an Position index. Dabei hat das erste Element eines String den Wert 0 und das letzte den Index 93 Programmieren in Java String substring(int anfang, int ende) String trim() „length()-1“. Falls der String kürzer als „index + 1“ ist, wird eine Ausnahme des Typs „IndexOutOfBoundsException“ erzeugt Liefert den Teilstring, der an Position anfang beginnt und an Position ende endet. Wie bei allen Zugriffen über einen numerischen Index beginnt hier das Zählen bei 0. „ende“ verweist auf das erste Zeichen hinter dem zu extrahierenden Teilstring. Der Rückgabewert ist also die Zeichenkette, die von Indexposition anfang bis zur Indexpostion „ende –1“ reicht Liefert den String, der entsteht, wenn auf beiden Seiten der Zeichenkette jeweils alle zusammenhängenden Leerzeichen entfernt werden. Dabei werden alle Zeichen, die einen Code kleiner 32 haben, als Leerzeichen angesehen. „trim“ entfernt immer Leerzeichen auf beiden Seiten. Da die Klasse String als final definiert wurde, gibt es keine Möglichkeit, entsprechende Methoden nachzurüsten, z.B. für das rechtsbündige oder linksbündige Entfernen von Leerzeichen. Länge der Zeichenkette: int length() liefert die aktuelle Länge des StringObjekts. Ist der Rückgabewert 0, dann ist der String leer. Wird ein Wert „n“ größer 0 zurückgegeben, dann enthält der String „n“ Zeichen, die an den Indexpositionen 0 bis „n-1“ liegen. Vergleichen von Zeichenketten: boolean equals(Object einObjekt) boolean startsWith(String s) boolean endsWith(String s) int compareTo(String s) boolean regionMatches(int toffset, String other, int ooffset, int len) Liefert true, wenn das aktuelle Objekt und einObjekt identisch sind. „equals“ testet auf inhaltlliche Gleichheit und nicht darauf, ob beide Strings dasselbe Objekt referenzieren. Neben „equals“ gibt es die Methode equalsIgnoreCase, die evtl. vorhandene Unterschiede in der Groß- und Kleinschreibung beider Zeichenketten ignoriert. Testet, ob das String-Objekt mit der Zeichenkette s beginnt. Ist dies der Fall, dann gibt die Methode „true“ zurück, anderenfalls „false“. Führt einen lexikalischen Vergleich der beiden Strings durch. Bei einem lexikalischen Vergleich werden die Zeichen paarweise von links nach rechts verglichen. Tritt ein Unterschied auf oder ist einer der beiden Strings beendet, wird das Ergebnis ermittelt. Ist das aktuelle String-Objekt kleiner als s, wird ein negativer Wert zurückgegeben. Ist er größer, wird ein positiver Wert zurückgegeben. Bei Gleichheit liefert die Methode den Rückgabewert 0. Testet, ob 2 String-Abschnitte gleich sind. Suchen in Zeichenketten int indexOf(String s) int IndexOf(String s, int vonIndex) Sucht das erste Vorkommen der Zeichenkette s innerhalb des String-Objekts. Wird s gefunden, liefert die Methode den Index des ersten übereinstimmenden Zeichens zurück, andernfalls wird –1 zurückgegeben. Die Methode gibt es auch in einer anderen Version, die einen Parameter vom Typ char akzeptiert. In diesem Fall sucht sie nach dem Auftreten des ersten angegebenen Zeichens. Diese Methode arbeitet wie die vorige, beginnt mit der Suche aber erst ab Position vonIndex. Wird s beginnend ab dieser Position gefunden, liefert die 94 Programmieren in Java int lastIndexOf(String s) Methode den Index des ersten übereinstimmenden Zeichens, andernfalls –1. Eine Variante dieser Methode erwartet anstelle eines String-Parameters ein Argument vom Typ char. Sucht nach dem letzten Vorkommen des Teilstrings s im aktuellen String-Objekt- Wird „s“ gefunden, liefert die Methode den Index des ersten übereinstimmenden Zeichens, andernfalls –1. Eine Variante dieser Methode erwartet ein Argument des Typs char. Ersetzen von Zeichenketten String toLowerCase() String toUpperCase() String replace(char altesZeichen, char neuesZeichen) Liefert den String zurück, der entsteht, wenn alle Zeichen in Kleinbuchstaben umgewandelt werden. Besitzt der String keine umwandelbaren Zeichen, dann wird der Original-string zurückgegeben. Liefert den String zurück, der entsteht, wenn alle Zeichen in Großbuchstaben umgewandelt werden. Besitzt der String keine umwandelbaren Zeichen, wird der Original-String zurückgegeben. Diese Methode führt eine zeichenweise Konvertierung des aktuellen String-Objekts durch. Jedes Auftreten von altesZeichen wird durch neuesZeichen ersetzt. Es gibt in Java keine Methode, die das Ersetzen von Teilstrings durch andere Teilstrings (von möglicherweise unterschiedlicher Länge) ermöglicht. „replace“ ist das einzige Mittel für Ersetzungen. Konvertierungsfunktionen: static static static static static static String String String String String String valueOf(boolean b) valueOf(char z) valueOf(int i) valueOf(long l) valueOf(float f) valueOf(double d) Die „valueOf“-Methoden sind als Klassenmethoden implementiert und können ohne String-Objekt aufgerufen werden. Da sie in der Regel zur Erzeugung von Strings verwendet werden, ist das sinnvoll. Ein Aufruf von „valueOf“ wandelt ein primitives Objekt mit Hilfe der Methode „toString()“, die von der zugehörigen WrapperKlassen bereitgestellt wird, in eine Zeichenkette um. Weitere Eigenschaften: Die Klasse String ist final. Von einer String-Klasse können keine neuen Klassen abgeleitet werden. String-Objekte sind nicht dynamisch. Es werden durch die Klasse String keine dynamischen Zeichenketten implementiert. Nach der Initialisierung eines String bleibt dessen Länge konstant, z.B.: String s = "Hallo Welt"; s = substring(0,5); Die „substring“-Methode erzeugt eine Kopie, die mit dem gewünschten Inhalt gefüllt wird. Diese gibt sie an den Aufrufer zurück, der das Ergebnis erneut s zuweist und damit die Originalinstanz für den Garbage Collector freigibt. Bsp.: Anwendung von Methoden der String-Klasse public class PruefeString { public static void main(String args[]) 95 Programmieren in Java { String str = "Java ist die beste Programmiersprache, " + "die Studenten der Informatik erlernen sollten."; System.out.println("Der String ist: " + str); System.out.println("Laenge des String: " + str.length()); System.out.println("Das Zeichen an Position 7: " + str.charAt(7)); System.out.println("Der Substring von 24 bis 31: " + str.substring(24,31)); System.out.println("Der Index zum Zeichen I: " + str.indexOf('I')); System.out.println("Der Index zum Anfang von " + "Substring \"beste\": " + str.indexOf("beste")); System.out.println("Der String in Großbuchstaben: " + str.toUpperCase()); } } Die Klasse StringBuffer Die Klasse StringBuffer dient zur Implementierung veränderlicher Zeichenketten. Die Methoden der Klasse StringBuffer dienen hauptsächlich dem Anhängen und Einfügen von Texten. Dabei wird der benötigte Speicherplatz automatisch der Größe angepaßt. Konstruktoren: StringBuffer() StringBuffer(String s) Erzeugt einen leeren StringBuffer Erzeugt ein neues StringBuffer-Objekt, Zeichenkette s ist. das eine Kopie der Einfügen von Elementen StringBuffer append(String s) Hängt String s an das Ende des StringBuffer-Objekts. Zurückgegeben wird das verlängerte StringBufferObjekt. Zusätzlich gibt es die Methode append in Varianten für das Anhängen aller Arten von primitiven Typen. Anstelle eines String-Objekts wird hier der entsprechende primitive Typ übergeben, in einen String konvertiert und an das Ende des Objekts angehängt. StringBuffer insert(int offset, String s) Fügt den String s an die Position offset in den aktuellen StringBuffer ein. Zurückgegeben wuird das verlängerte StringBuffer-Objekt. Auch diese Methode gibt es für primitive Typen. Der anstelle eines String übergebene Wert wird zunächst in einen String konvertiert und dann an die gewünschte Stelle eingefügt. Verändern von Elementen: „void setCharAt(int index, char c) throws IndexOutOfBoundsException“. Das an Position index stehende Zeichen wird durch c ersetzt. Falls StringBuffer zu kurz ist, löst die Methode eine Ausnahme des Typs IndexOutOfBoundsException aus. Länge eines String-Objekts: int length() liefert die Länge des Objekts (Anzahl der Zeichen, die zum Zeitpunkt des Aufrufs in dem StringBuffer enthalten sind). Konvertieren in einen String: String toString(). Nachdem die Konstruktion eines StringBuffer-Objekts abgeschlossen ist, kann es mit Hilfe dieser Methode in einen String verwandelt werden. Die Methode legt dabei keine Kopie des StringBuffer-Objekts an, sondern liefert einen Zeiger auf den internen Zeichenpuffer. 96 Programmieren in Java Erst wenn der StringBuffer erneut verändert werden soll, wird tatsächlich eine Kopie erzeugt. 97 Programmieren in Java 1.4.2.6 Die Math-Klasse Die Math-Klasse enthält nützliche numerische Konstanten und Funktionen: 1. Die Funktionen „min“ und „max“. 2. Der absolute Betrag 3. Zufallszahlen Die Klasse Math unterstützt die „random“-Methode: public static double random(). Die „random“-Methode gibt eine Zahl zwischen 0.0 und 1.0 aus. 4. Runden Runden einer Zahl vom Typ float: public static int round(float a). Gerundet wird auf die nächste ganze Zahl (z.B. 5.4 auf 5, 5.5. auf 6) Runden einer Zahl vom Typ double: public static long round(double a) Abrunden: public static double floor(double a). Math.floor rundet immer ab, z.B. ergibt Math.floor(4.99) die Zahl 4.0. Aufrunden: public static double ceil(double a). ceil runder immer auf, z.B.: Math.ceil(4.01) ergibt 5.0. Runden auf die nächste ganze Zahl: public static double rint(double a). 5. Exponenten und Logarithmen x Bestimmen des Expoentialwerts e : public static double exp(double x) Bestimmen des natürlichen algorithmus ln(x ) : public static double log(double x) 6. Trigonometrische Funktionen public static double sin(double winkel) public static double cos(double winkel) Der Wert von winkel wird im Bogenmaß angegeben. 7. Potenzen, Quadratwurzel y Bestimmen vom Wert der Potenz x : public static double pow(double x, double y) Bestimmen der Quadratwurzel von x: public static double sqrt(double x) 8. Mathematische Konstanten public static final double E; public static final double PI; 98 Programmieren in Java 1.4.2.7 Object-Wrapper-Klassen Die Object-Wrapper-Klassen sind Wrapper für primitive Typen. Zusätzlich besitzen sie Methoden zur Umwandlung von Zeichenketten in die verschiedenen Datentypen. Boolean Character public static final Boolean TRUE public static final Boolean FALSE public static final char MIN_VALUE public staic final char MAX_VALUE public boolean booleanValue() public static Boolean valueOf(String s) public static boolean getBoolean(String name) public char charValue() public Character(char value) public int hashCode() public boolean equals(Object obj) public String toString() public static boolean isLowerCase(char ch) public static boolean isUpperCase(char ch) public static boolean isDigit(char ch) public static boolean isLetter(char ch) public static boolean isLetterOrDigit(char ch) public static boolean isJavaLetter(char ch) public static boolean isJavaLetterOrDigit(char c) public static boolean isSpace(char ch) public static boolean isTitleCase(char ch) public static char toLowerCase(char ch) public static char toUpperCase(char ch) public static int digit(char ch, int radix) public static int getNumericValue(char ch) Number { abstract } public abstract int intValue() public abstract long longValue() public abstract float floatValue() public abstract double doubleValue() public byte byteValue() public short shortValue() Byte Short Integer Long Abb.: Wrapper-Klassen 99 Float Double Programmieren in Java Die Klasse Character Konstruktor: Ermitteln vom „char“-Wert: public char charValue() Klassifizieren von Zeichen: Methode isDigit isLetter isLetterOrDigit isLowerCase isUpperCase isJavaLetter isJavaLetterOrDigit isSpace isTitleCase Beschreibung Eine numerische Zahl zwischen 0 und 9 Ein Buchstabe des Alphabets Ein alphabetischer Buchstabe oder eine numerische Zahl Ein großgeschriebener alphabetischer Buchstabe Ein Buchstabe, ‚$‘ oder ‚_‘ Ein Buchstabe, eine Zahl, $ oder _ Eine Leerstelle, eine neue Zeile, ein Return, ein Tab oder ein Formularvorschub Spezielle zweibuchstabige groß- und kleingeschriebene Buchstaben Jede dieser Klassifikationsmerkmale gibt „true“ zurück, wenn das betreffende Zeichen zur Klassifikation gehört. Groß- und kleingeschriebene Variante eines Buchstabens: public static char toUppercase(char zeichen) public static char toLowerCase(char zeichen) Konvertierungsmethoden zur Umwandlung von Zahlen in Zeichenketten und Zeichenketten in Zahlen: public static int digit(char zeichen, int radix) gibt den numerischen Wert eines Zeichens der angegebenen Zahlendarstellung („radix“)86 wieder. Falls ein Zeichen keinem Wert in der angegebenen Zahlendarstellung entspricht, wird „-1“ zurückgegeben. public static char forDigit(int digit, int radix) Konvertiert einen numerischen Wert in ein Zeichen, das diesen Wert in einer bestimmten Zahlenbasis darstellt, z.B. Character.forDigit(6,8) ergibt 6 und Character.forDigit(12,16) ergibt "c“. Die Klasse Boolean Diese Klasse ist der Objekt-Wrapper für den primitiven Typ boolean. Konstruktoren: public Boolean(boolean wert) public Boolean(String str) Beschaffung eines booleschen Werts, der in einem Objekt abgelegt ist: public boolean booleanValue() Objekt-Wrapper-Varianten von true und false public final static Boolean FALSE; public final static Boolean TRUE; Die Klasse Number 86 z.B. 10 für dezimal, 8 für oktal, 16 für hexadezimal. Zahlenbasis kann Werte von 2 (Character.MIN_RADIX) bir 32 (Character.MAX_RADIX) umfassen. 100 Programmieren in Java Die Objekt-Wrapper der Typen int, long, float und double sind Subklassen der abstrakten Klasse Number. Vier Methoden sind für die Konvertierung einer Zahl in einen bestimmten primitiven Typ zuständig: public public public public abstract abstract abstract abstract int intValue(); long longValue(); float floatValue(); double doubleValue() 1. Die Klasse Integer Konstruktoren: public Integer(int wert) public Integer(String s) throws NumberFormatException Die Zahlenbasis (Radix) ist 10. Falls die Zeichenkette nicht numerische Zeichen enthält, wird die Ausnahme NumberFormatException ausgeworfen. Umwandeln Zeichenkette in ganze Zahlen: public public public public static static static static int parseInt(String s) int parseInt(String s, Integer valueOf(String Integer valueOf(String throws NumberFormatException int radix) throws NumberFormatException s) throws NumberFormatException s, int radix) throws NumberFormatException Der Unterschied zwischen parseInt und valueOf liegt darin: parseInt gibt ein „int“ zurück, valueOf ein Integer. Konvertieren einer ganzen Zahl in eine Zeichenkette: public static String toString(int i) public static String toString(int i, int radix) Die beiden Methoden dürfen nicht mit der Instanz-Methode toString verwechselt werden, die für alle Subklassen von Objekt definiert ist. Die Konstanten Integer.Min_VALUE und Integer.MAX_VALUE: public final static int MIN_VALUE public final static int MAX_VALUE 2. Die Klasse Long Sie ist weitgehend mit der Integer-Klasse identisch. Allerdings dient sie als Wrapper für long-Werte. 3. Die Klasse Float Sie enthält einen Objekt-Wrapper für den Datentyp float. Konstruktoren: public Float(float wert) public Float(double wert) public Float(String s) throws NumberFormatException Konvertierung von bzw. zu Zeichenketten: public static Float valueOf(String s) throws NumberFormatException public static String toString(float f) Überprüfen auf „Unendlich“ bzw. auf „keine Zahl“: public static boolean isInfinite(float f) public static boolean isNaN(float f) // Instanz-Variante public boolean isNaN() // Instanz-Variante Konstanten: public public public public final final final final static static static static float float float float MIN_VALUE MAX_VALUE NEGATIVE_INFINITY NaN 101 Programmieren in Java 4. Die Klasse Double Sie hat dieselbe Funktionalität wie die Float-Klasse. 1.4.3 Ausnahmen und Ausnahmenbehandlung Ausnahmen Eine Ausnahme (Exception) ist eine unerwünschte Erscheinung. Eine derartige Situation (z.B. Division durch Null) ist in einem Programm gegebenenfalls durch eine Fehlerbehandlungsroutine aufzufangen. Es gibt unterschiedliche Möglichkeiten zur Fehlerbehandlung: - die individuelle programmierte Fehlerbehandlung, z.B.: public int dividiere() { // ganzzahlige Division if (zweiterOperand == 0) { System.out.println("Fehler: Division durch Null"); System.out.println("Divisor wird auf 1 gesetzt!"); zweiterOperand = 1; } return ersterOperand / zweiterOperand; } - die von Java bereitgestellten Fehlerbehandlungen Ausnahmen sind in Java Objekte, die erzeugt werden, wenn eine Ausnahmebedingung vorliegt. Diese Objekte werden von der Methode an das aufrufende Programm zurückgegeben und müssen behandelt werden. Das Behandeln von Ausnahmen wird als Auffangen bezeichnet. Das Auffangen wird durch Plazieren von Anweisungen, die Ausnahmen erzeugen können, in eine trycatch Block erreicht, z.B.87: public int dividiere() { // ganzzahlige Division try { /* Innerhalb des try-Blocks werden diejenigen kritischen Aktionen durchgefuehrt, die Ausnahmen erzeugen koennen */ return (ersterOperand / zweiterOperand); } catch(java.lang.ArithmeticException a) { System.out.println(" Fehler: Division durch Null"); System.out.println("Divisor wird auf 1 gesetzt!"); zweiterOperand = 1; return (ersterOperand / zweiterOperand); } } 87 pr14103 102 Programmieren in Java In der catch-Klausel wird die Art der aufzufangenden Ausnahme definiert. Dort ist ein formaler Parameter angegeben, der beim Auftreten der Ausnahme ein Fehlerobjekt übernehmen soll. Fehlerobjekte sind Instanzen der Klasse Throwable (oder eine ihrer Unterklassen). Sie werden vom Aufrufer der Ausnahme erzeugt und als Parameter an die catch-Klausel übergeben. Das Fehlerobjekt enthält Informationen über die Art der aufgetretenen Ausnahme. Es kann dort eine der zahlreichen Standardausnahmen von Java stehen oder auch selbstdefinierte Ausnahmen. Das Ausnahme- / Fehlerobjekt besitzt einige nützliche Methoden, z.B. public String getMessage()88. Diese Methode gibt Fehlermeldungen zurück. public void printStackTrace(). Diese Methode druckt einen Auszug aus dem Laufzeit-Stack. Am Ende eines try-Blocks können beliebig viele „catch“-Klauseln stehen, so daß unterschiedliche Arten von Ausnahmen behandelt werden können. Mit Hilfe der finally-Klausel (letzter Bestandteil einer try-catch-Anweisung) kann ein Programmfragment definiert werden, das immer ausgeführt wird, wenn die zugehörige try-Klausel betreten wurde. Bsp.: Rückgabe von „Resourcen“ in der finally-Klausel In dem folgenden Programm stellt die finally-Klausel sicher, daß der Schalter tatsächlich nach dem Ende der Bearbeitung im try-catch-Block ausgeschaltet ist. class Schalter { boolean zustand = false; boolean lies() { return zustand; } void an() { zustand = true; } void aus() { zustand = false; } } public class AnAusSchalter { static Schalter schalter = new Schalter(); public static void main(String args[]) { try { schalter.an(); // hier kann sich Quellcode befinden, der // Ausnahmen ausloest } catch(NullPointerException a) { System.out.println("NullPointerException"); } catch(IllegalArgumentException a) { System.out.println("IllegalArgumentException"); } finally { schalter.aus(); } } } 88 Sie ist in der Klasse Throwable definiert und daher allen Exception-Objekten zugänglich. 103 Programmieren in Java Globales Exception-Handling Java realisiert das globale Handling von Ausnahmen über die optionale Erweiterung einer Methodendeklaration mit der „throws“-Klausel, z.B.: public class MeineAusnahmenKlasse { public void eineAusnahmenMethode() throws MeineAusnahme { ......... } } Hier wird dem Compiler mitgeteilt, daß der vorliegende Code eine Ausnahme („MeineAusnahme“) erzeugen könnte. Falls in der mit „throws“ gekennzeichneten Methode eine Ausnahmesituation auftritt, dann wird die aufrufende Methode nach einer Ausnahmebehandlung durchsucht. Enthält die aufrufende Methode eine Ausnahmebehandlungsmethode, wird mit dieser die Ausnahme bearbeitet. Ist keine Routine vorhanden, wird deren aufrufende Methode durchsucht und so fort. Die Ausnahme wird von der Hierarchieebene, wo sie aufgetreten ist, jeweils eine Hierarchieebene weiter nach oben gereicht. Wird die Ausnahme nirgends aufgefangen, dann bricht der JavaInterpreter normalerweise die Ausführung des Programms ab. Das Auslösen einer Ausnahme wird im Java-Sprachgebrauch als „throwing“ bezeichnet, das Behandeln einer Ausnahme wird „catching“ genannt. Das Grundprinzip des Exception-Mechanismus: - Ein Laufzeitfehler oder eine vom Entwickler gewollte Bedingung löst eine Ausnahme aus. - Diese kann entweder vom Programmteil, in dem sie angelegt wurde, behandelt werden, oder sie kann weitergegeben werden. - Wird die Ausnahme weitergegeben, so hat der Empfänger erneut die Möglichkeit sie entweder zu behandeln oder selbst weiterzugeben. - Wird die Ausnahme von keinem Programmteil behandelt, so führt sie zum Abbruch eines Programms, und zur Ausgabe einer Fehlermeldung. Auswertungen, die Ausnahmen auslösen, können entweder direkt in eine tryKlausel mit optionalen catch-Anweisungen (Behandlung vor Ort) eingefaßt oder über die throws-Klausel an den übergeordneten Programmblock weitergeleitet werden. Falls weder die „Behandlung vor Ort“ noch eine throws-Klausel verwendet wird, liefert der Compiler bereits bei der Übersetzung eine Fehlermeldung. Eine Ausnahme bildet die Klasse RuntimeException einschl. ihrer Subklassen. Für sie ist weder die Fehlerbehandlung noch Weiterleitung mit der throws-Klausel notwendig. Die Fehlerklassen von Java Zur Behandlung von Fehlern und Ausnahmen besitzt die Klasse Throwable zwei Subklassen: die Klassen Exception und Error. In beiden Ausnahmeklassen sind die wichtigsten Ausnahmen und Fehler der Java-Laufzeitbibliothek bereits enthalten. Ausnahmen der Klasse Error und ihrer Klasse RuntimeError (sowie RuntimeException) müssen nicht extra abgefangen werden. „Errors“ sind systembedingt, „Exceptions“ dagegen programmbedingt und müssen behandelt 104 Programmieren in Java werden. Es gibt fünf Typen von Ausnahmen, die in einer throws-Klausel aufgelistet werden müssen: ClassNotFoundException IllegalAccessException InstantiationException InterruptedException NoSuchMethodException Hinzu kommen verschiedene weitere Ausnahmen aus den Java-Paketen. So gibt es spezielle Fehlerklassen für die Ein- und Ausgabe, die Netzwerkkommunikation oder den Zugriff auf Datenfelder. Teilweise sind sie der Klasse „Error“ (oder ihrer Subklasse RuntimeError) zugeordnet und müssen nicht extra abgefangen und dokumentiert werden. Sind Ausnahmen der Klasse Exception (Ausnahme: RuntimeException) zugeordnet, dann müssen sie behandelt werden (z.B. alle Ausnahmen von java.io). Die Klasse RuntimeException ist die Superklasse für die Behandlung aller Laufzeitfehler, die behandelt werden können aber nicht müssen (die Entscheidung liegt beim Programmierer). UnknownError VirtualMachineError OutOfMemoryError InternalError Error AWTError LinkageError Object Throwable ClassNotFoundException Exception RuntimeException EOFException IOException FileNotFoundException InterruptedException UTFDataFormatException Abb.: Fehlerklassenhierarchie Auslösen von Ausnahmen Java verfügt in seinen Standardbibliotheken über zahlreiche vorgefertigte Ausnahmen für fast alle Standardsitiuationen (z.B. Dateioperationen auf der Basis der IO-Exception). Selbstdefinierte Ausnahmen benötigen eine Subklasse von Exception. Das Auslösen dieser selbstdefinierten Ausnahmen erfolgt über die throwAnweisung: throw AusnahmeObjekt. Die Behandlung selbstausgelöseter Ausnahmen erfolgt nach den üblichen Regeln: 105 Programmieren in Java - Suche nach einem Ausnahmen-Handler in den umgebenden Blöcken - Bei erfolgloser Suche Weitergabe des Fehlers an den Aufrufer. Wird die Ausnahme nicht innerhalb derselben Methode behandelt, dann ist sie mit Hilfe der "throws"-Klausel zu deklarieren und „weiter oben“ in der Aufrufkette zu behandeln. Bsp.: public class AusnahmeMethoden { public static void main(String args[]) { try { throw new Exception("Hier ist meine Ausnahme"); } catch (Exception a) { System.out.println("Eingefangene Ausnahme"); System.out.println("a.getMessage(): " + a.getMessage()); System.out.println("a.toString(): " + a.toString()); System.out.println("a.printStackTrace(): "); a.printStackTrace(); } } } /* Ausgabe: Eingefangene Ausnahme a.getMessage(): Hier ist meine Ausnahme a.toString(): java.lang.Exception: Hier ist meine Ausnahme a.printStackTrace(): java.lang.Exception: Hier ist meine Ausnahme at AusnahmeMethoden.main(AusnahmeMethoden.java) */ Die throw-Anweisung besitzt Merkmale einer Sprunganweisung. Sie unterbricht das Programm und verzweigt unmittelbar zur umgebenden catch-Klausel. Gibt es keine catch-Klausel, wird der Fehler weitergegeben. Tritt die Ausnahme innerhalb einer try-catch-Anweisung mit einer finally-Klausel auf, wird diese noch vor der Weitergabe ausgeführt. Auch das „Wiederauswerfen“ einer Ausnahme ist möglich, insbesondere dann, falls eine Ausnahme eingefangen wurde, sie aber in einem größeren Zusammenhang behandelt werden soll catch (Exception a) { System.out.println("Eine Ausnahme wurde eingefangen"); throw a; } Eigene, d.h. benutzerdefinierte Ausnahmen können direkt von der Klasse Exception abgeleitet werden, z.B.89: class MeineAusnahme extends Exception { private int i; public MeineAusnahme() {} public MeineAusnahme(String nachricht) 89 Vgl. pr14140 106 Programmieren in Java { super(nachricht); } public MeineAusnahme(String nachricht, int x) { super(nachricht); i = x; } public int wert() { return i; } } public class AuswurfMeineAusnahme { public static void f() throws MeineAusnahme { System.out.println("Auswurf von MeineAusnahme aus f()"); throw new MeineAusnahme(); } public static void g() throws MeineAusnahme { System.out.println("Auswurf von MeineAusnahme aus g()"); throw new MeineAusnahme("Ursprung in g()"); } public static void h() throws MeineAusnahme { System.out.println("Auswurf von MeineAusnahme aus h()"); throw new MeineAusnahme("Ursprung in h()",13); } public static void main(String args[]) { try { f(); } catch (MeineAusnahme a) { a.printStackTrace(); } try { g(); } catch (MeineAusnahme a) { a.printStackTrace(); } try { h(); } catch (MeineAusnahme a) { a.printStackTrace(); System.out.println("a.wert() = " + a.wert()); } } } /* Ausgabe: Auswurf vom MeineAusnahme aus f() MeineAusnahme at java.lang.Throwable.<init>(Compiled Code) at java.lang.Exception.<init>(Compiled Code) at MeineAusnahme.<init>(AuswurfMeineAusnahme.java:4) at AuswurMeineAusnahme.f(Vererbung.java:21) at AuswurfmeineAusnahme.main(Vererbung.java:36) Auswurf von MeineAusnahme aus g() MeineAusnahme: Ursprung in g() at java.lang.Throwable.<init>(Compiled Code) at java.lang.Exception.<init>(Compiled Code) at MeineAusnahme.<init>(AuswurfMeineAusnahme.java:7) at AuswurfMeineAusnahme.g(AuswurfMeineAusnahme.java:26) at AuswurfMeineAusnahme.main(AuswurfMeineAusnahme.java:43) Auswurf von MeineAusnahme aus h() MeineAusnahme: Ursprung in h() at java.lang.Throwable.<init>(Compiled Code) at java.lang.Exception.<init>(Compiled Code) at MeineAusnahme.<init>(AuswurfMeineAusnahme.java:11) at AuswurfMeineAusnahme.g(AuswurfMeineAusnahme.java:31) 107 Programmieren in Java at AuswurfMeineAusnahme.main(AuswurfMeineAusnahme.java:50) a.wert() = 13 */ 108 Programmieren in Java 1.4.4 Ein-, Ausgaben Quelle Eingabestrom Programm write() read() Ausgabestrom Programm Senke Abb.: Input und Output Stream Einlesen von Daten aus einer externen Quelle (source) oder Ausgabe zu einer externen Senke (sink). Daten können aus verschiedenen Quellen stammen: Tastatur, Datei, Speicher, Internet, anderes Programm. Daten können von verschiedener Art sein: Zeichen, Objekte, Bilder, .... Daten können zu verschiedenen Ausgabeeinheiten gehen: Bildschirm, Datei, Drucker, Speicher, Internet, anderes Programm. Daten werden generell sequentiell gelesen oder geschrienen 1.4.4.1 Ein- und Ausgabeströme der Klasse System Die Klasse System enthält drei Datenströme: public static InputStream90 in public static PrintStream91 out public static PrintStream err // Standardeingabe // Standardausgabe // Standardfehlerausgabe Diese Datenströme dienen zum Lesen bzw. Schreiben in das Fenster von dem die Anwendung gestartet wurde. Die Verwendung des Stroms System.in innerhalb von Applets ist aber nicht sinnvoll, weil die verschieden Browser diesen Datenstrom unterschiedlich behandeln. Die Ströme System.out und System.err sendet Netscape an das Fenster der Java-Konsole, der Appletviewer gibt sie in das Fenster weiter, aus dem der Appletviewer gestartet wurde, Einfache Ausgaben auf das Standard-Ausgabegerät Mit Hilfe des Kommandos „System.out.println“, das als einziges Argument eine Zeichenkette erwartet, können einfache Ausgaben auf den Bildschirm geschrieben werden. Mit Hilfe des Plus-Operators „+“ können Zeichenketten und numerische Argumente verknüpft werden, so daß man neben Text auch Zahlen ausgeben kann. Einfache Eingaben vom Standard-Eingabegerät 90 91 Mit der Klasse InputStream können Leseoperationen eines Bytestroms verwirklicht werden, vgl. 7.1.1 Die Klasse PrintStream bezieht sich auf die I/O von Java 1.0 109 Programmieren in Java Leider ist es etwas komplizierter Daten zeichenweise von der Tastatur zu lesen. Es steht zwar ein vordefinierter Eingabestrom System.in zur Verfügung, der aber nicht die eingelesenen Zeichen primitiver Typen konvertieren kann. Statt dessen muß eine Instanz der Klasse „InputStreamReader“92 und daraus ein „BufferedReader“ erzeugt werden. Dieser kann zum Lesen einer zeilenweisen Eingabe mit Umwandlung des Ergebnisses in einen primitiven Typ verwendet werden, z.B.93: "Einlesen von zwei ganzen Zahlen". import java.io.*; public class EingZweiZahlen { // Einlesen von zwei ganzen Zahlen public static void main(String args[]) throws IOException { int a, b, c; BufferedReader ein = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Bitte a eingeben: "); a = Integer.parseInt(ein.readLine()); System.out.print("Bitte b eingeben: "); b = Integer.parseInt(ein.readLine()); c = a + b; System.out.println("a + b = " + c); } } 1.4.4.2 Datenströme Das grundlegende Modell für Ein- und Ausgabe sind Datenströme (CharacterStreams), die im Paket "java.io" versammelt sind. Das Datenstrom-Modell geht davon aus, daß - Programme Zeichenfolgen aus einem Datenstrom lesen bzw. in einen Datenstrom schreiben - ein Datenstrom unabhängig von den Fähigkeiten des jeweiligen I/O-Geräts94 organisiert werden muß. Streams können auch verkettet werden und verschachtelt werden. Das Schachteln erlaubt die Konstruktion von Filtern, die bei der Ein-/Ausgabe bestimmte Zusatzfunktionen übernehmen, z.B. das Puffern von Zeichen, das Zählen von Zeichen oder die Interpretation binärer Dateien. Alle Klassen zur Datenein- bzw. Datenausgabe befinden sich im Paket java.io und sind für Zeichenströme95 von den abstrakten Klassen Reader bzw. Writer96 abgeleitet. Allgemeine Fehlanzeige im Paket java.io ist die Ausnahme IOException, die deshalb auch von einer Vielzahl von Methoden ausgelöst werden kann und dabei sehr unterschiedliche Bedeutung annehmen kann. 92 Das grundlegende Konzept für Ein- und Ausgabe sind Datenströme, die im Paket „java.io“ versammelt sind. 93 Vgl. pr14150 94 Hauptspeicher, Festplatte, Drucker, Netzwerk 95 Für Byte-Ströme gibt es die Klassen InputStream / OutputStream einschl. zahlreicher Subklassen 96 vgl. 7.6 110 Programmieren in Java Reader InputStreamReader BufferedReader FilterReader FileReader Writer OutputStreamWriter BufferedWriter FilterWriter FileWriter Abb. Subklassen von Reader / Writer Bsp.: Das folgende Programm liest eine Zeichenfolge aus einem Eingabestrom und schreibt die Zeichenfolge in einen Ausgabestrom97 import java.io.*; public class EchoEinAus extends Object { public static void main(String args[]) { InputStreamReader isr = new InputStreamReader(System.in); BufferedReader ein = new BufferedReader(isr); String eingabeZeile = null; // OutputStreamWriter osw = new OutputStreamWriter(System.out); PrintWriter aus = new PrintWriter(System.out); while (true) { aus.print("Gib eine Zeichenkette ein: "); aus.flush(); try { eingabeZeile = ein.readLine(); } catch (IOException e) { aus.println(e.toString()); System.exit(0); } if (eingabeZeile == null) { aus.println(); aus.flush(); break; } aus.println(eingabeZeile); } } } Für die Eingabe wurde herangezogen: 97 vgl. pr14150 111 Programmieren in Java - InputStream in der Klasse System aus dem Paket java.lang - die abstrakte Klasse InputStream des Pakets "java.io" - die Klasse InputStreamReader des Pakets "java.io" - die Methode "String readLine()" mit der Klasse BufferedReader des Pakets java.io Für die Ausgabe kann ebenso verfahren werden. Man zieht in diesem Zusammenhang zur Ausgabe heran: - die Klasse PrintStream (Subklasse der abstrakten Klasse OutputStream) - die Klasse BufferedWriter (Subklasse von Writer). Es gibt aber kein Gegenstück zu "String readLine()". - die Klasse PrintWriter (Subklasse von Writer) für println(...) 1.4.4.3 Eingabe und Ausgabe mit Dateien Dateien dienen zur Ablage von Daten auf externen Speichern. Dateien werden zur besseren Übersicht auf externen Speichern in sog. Verzeichnissen gespeichert – der Zugriff auf eine Datei ist somit über einen Verzeichnispfad und über den Dateinamen möglich. Unter der Vielzahl verschiedener Dateiformate sind besonders hervorzuheben: Text- und Binärdateien. Byte-weises Schreiben und Lesen von (Binär-) Dateien Zum (Byte-weisen) Schreiben und Lesen von Dateien werden verwendet: Die Klassen FileOutputStream und FileInputStream. 1. Bsp.: Ausgabe einer Binärdatei import java.io.*; public class pr14444a { public static void main (String args[]) { File f = new File("datei.bin"); try { DataOutputStream raus = new DataOutputStream( new FileOutputStream(f)); for (int i=1;i<=10;i++) raus.writeInt(i*i); } catch(IOException e) { System.err.println(e.toString()); } } } 2. Bsp.: Eingabe einer Binärdatei import java.io.*; public class pr14444b { public static void main (String args[]) { File f = new File("datei.bin"); 112 Programmieren in Java try { DataInputStream rein = new DataInputStream( new FileInputStream(f)); for (int i=1;i<=10;i++) { int zahl = rein.readInt(); System.out.println(zahl); } } catch (IOException e) { System.err.println(e.toString()); } } } Verarbeiten von 16-Bit Unicode Zeichen mit den Klassen FileReader und FileWriter Eingabe von Textdateien 1. Bsp.: Einlesen einer Eingabe-Datei import java.io.*; public class EingabeDatei extends Object { private BufferedReader ein; EingabeDatei(String dName) throws Exception { try { ein = new BufferedReader(new FileReader(dName)); } catch (FileNotFoundException a) { // Der Konstruktor von FileReader war nicht erfolgreich System.out.println("Kann nicht geoeffnet werden: " + dName); throw a; } catch (Exception a) { // Alle anderen Ausnahmen schliessen die Datei try { ein.close(); } catch (IOException a1) { System.out.println("ein.close() nicht erfolgreich"); } throw a; } finally { /* nicht geeignet fuer close() */ } } String holeZeile() throws IOException { String s; try { s = ein.readLine(); } catch (IOException a) { System.out.println("readLine() nicht erfolgreich"); s = "Fehlanzeige"; } return s; } void bereinigen() 113 Programmieren in Java { // Freigabe der Syntax-Resourcen try { ein.close(); } catch(IOException a2) { System.out.println("ein.close() nicht erfolgreich"); } } } 2. Bsp.: Das folgende Programm bearbeitet die Eingabedatei. import java.io.*; public class BearbeiteEingabeDatei extends Object { public static void main(String args[]) { try { // Erzeugen einer Instanz EingabeDatei ein = new EingabeDatei("BearbeiteEingabeDatei.java"); String s; int i = 1; while ((s = ein.holeZeile()) != null) System.out.println("" + i++ + ": " + s); ein.bereinigen(); } catch (Exception a) { System.out.println("Eingefangen in main(), a.printStackTrace()"); a.printStackTrace(); } } } Das Beispiel zeigt: Eingabeströme können verschachtelt werden. Folgende Klassen stehen dafür zur Verfügung (, die im Konstruktor einen Reader erwarten): BufferedReader, LineNumberReader, FilterReader und PushbackReader. Ein BufferedReader puffert Daten in einem 8192 Bytes großen Zwischenspeicher. Dadurch müssen weniger Zugriffe auf den Datenträger gemacht werden. Zusätzlich stellt BufferedReader die Methode public String readLine() zur Verfügung, die eine komplette Textzeile einliest und als String an den Aufrufer zurückgibt. „null“ wird zurückgegeben, wenn der Stream am Ende ist. Ein LineNumberReader liest die Eingabezeilen und zählt gleichzeitig die Zeilen, die gelesen wurden. Mit public int getLineNumber() bzw. public void setLineNumber(int zeilenNummer) läßt sich auf die die Zeilennummern zugreifen. Ausgabe mit Dateien Die Ausgabe in Dateien wird unterstützt durch die Klasse FileWriter. FileWriter ist die einzige Subklasse von OutputStreamWriter. Jeder OutputStreamWriter agiert mir einem CharToByteConverter und konvertiert so Zeichenströme in Byteströme. import java.io.*; public class CharArrayWriterDemo { 114 Programmieren in Java public static void main(String args[]) { String s; try { PrintWriter pw = new PrintWriter(new BufferedWriter( new FileWriter("charArrayWriterDemoPuffer.txt"))); for (int i = 1; i < 1000; i++) pw.println("Zeile " + i); pw.close(); } catch (IOException e) { System.out.println("Konnte Datei nicht erstellen"); } } } In Java gibt es drei Klassen BufferedWriter (mit standardmäßig 8192 Bytes großen Puffer), PrintWriter, FilterWriter, die einen Writer im Konstruktor erlauben und ihre Ausgabe dorthin weiterleiten. Zu Beginn des vorliegenden Programms98 wird ein FileWriter erstellt. Anschließend erzeugt man einen BufferedWriter, der die Daten, die in die Datei geschrieben werden, sammelt. Dieser BufferedWriter wird zu einem PrintWriter erweitert. Da ein PrintWriter Schreibfunktionen besitzt, ist man auf write()-Methoden nicht mehr angewiesen, sondern kann die komfortablen print()-Funktionen nutzen. Umlenken von Standardeingabe, Standardausgabe und Standardfehlerausgabe In Java 1.1 ermöglichen die folgenden Methoden das Umlenken: - setIn(InputStream) - setOut(PrintStream) - setErr(PrintStream) Bsp.: Demonstration der Umlenkung von Standardeingabe und Standardausgabe in Java 1.199 import java.io.*; class Umlenken { public static void main(String args[]) { try { BufferedInputStream ein = new BufferedInputStream(new FileInputStream("Umlenken.java")); // produziert "deprecation"-Nachrichten, System.setOut() und // System.setErr() erfordern ein PrintStream-Objekt als Argument PrintStream aus = new PrintStream( new BufferedOutputStream( new FileOutputStream("test.aus"))); System.setIn(ein); System.setOut(aus); System.setErr(aus); BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String s; while ((s = br.readLine()) != null) System.out.println(s); aus.close(); 98 99 vgl. pr14330 vgl. pr14150 115 Programmieren in Java } catch(IOException a) { a.printStackTrace(); } } } Random Access Files100 werden über die Klasse Random Access File realisiert. Beim Konstruktor RandomAccessFile(String,Zugriffsmodus) wird neben dem Dateinamen auch die Zugriffsart angegegeben ("r" nur für Lesezugriffe, "rw" zum Lesen und Schreiben). Mit der Methode seek(long) kann der Dateizeiger (FilePointer) gesetzt werden, getFilePointer() liefert die aktuelle Position des FilePointer. Weiterhin stehen zur Verfügung: length(), close(), readLine(), read(), readByte(), readShort(), readInt(), readLong(), readFloat(), readDouble(), readChar(), readBoolean() und die analogen write-Methoden. Bsp.: Ein- und Ausgabe von und nach Dateien mit wahlfreiem Zugriff101 1. Erzeugen der Datei rafrw (Datei mit wahlfreiem Zugriff). Auf rafrw werden ganze Zahlen geschrieben. 2. Erzeugen der Datei rafr (Datei mit wahlfreiem Zugriff), von der ganze Zahlen gelesen werden. import java.io.*; public class RandomAccessTest { public static void main(String[] args) throws java.io.IOException { BufferedReader br = new BufferedReader (new InputStreamReader(System.in)); int intLength = 4; // Laenge der Darstellung von ganzen Zahlen // updating part try { RandomAccessFile rafrw = new RandomAccessFile("testfile", "rw"); System.out.print("\nAendern eines Datensatzes der Datei\n"); // Datensatznummer erfragen und Dateizeiger setzen und anzeigen System.out.print("Datei geoeffnet. "); System.out.print("Dateizeiger="+rafrw.getFilePointer() +" DateiLaenge="+rafrw.length()+" Byte(s)\n"); System.out.print("\n? Welchen Datensatz aendern = "); long pos = intLength * (new Integer(br.readLine()).intValue()); rafrw.seek(pos); System.out.print( "Dateizeiger="+rafrw.getFilePointer()+"\n"); // Falls vorhanden, Datensatz anzeigen if (pos < rafrw.length()) { // Datensatz anzeigen, Dateizeiger wiederherstellen System.out.print("Datensatz = "+rafrw.readInt()+"\n"); rafrw.seek(pos); } else System.out.print("Aha, Datensatz soll angefuegt werden.\n"); // Neuen Datensatz einlesen System.out.print("\n? Neuer Datensatz = "); 100 101 vgl. 7.4 vgl. pr14444 116 Programmieren in Java int val = new Integer(br.readLine()).intValue(); // Neuen Datensatz in die Datei schreiben, Dateizeiger anzeigen rafrw.writeInt(val); System.out.print( "Nach dem Schreiben: Dateizeiger=" +rafrw.getFilePointer()+"\n"); // test outpout part RandomAccessFile rafr = new RandomAccessFile("testfile", "r"); System.out.print("\nVoila, die geaenderte Datei\n"); // Wiedergabe der Datensaetze aus der Datei // nach der Veraenderung int i; try { while (true) { i = rafr.readInt(); System.out.print(i+" "); } } catch (EOFException e) {} rafrw.close(); rafr.close(); } catch (IOException e) { System.err.println("RandomAccessTest: " + e); } System.out.print("\n"); } } 117 Programmieren in Java 1.4.4.4 Verwalten von Dateien und Verzeichnissen durch die Klasse File Informationen über eine Datei erhält man über ein File-Objekt von class File java.io.File implements Serializable, Comparable. Ein File-Objekt repräsentiert eine Datei oder ein Verzeichnis auf dem Dateisystem. Der Verweis auf Datei bzw. Verzeichnis wird durch einen Pfadnamen spezifiziert. Pfadnamen102 können absolut oder relativ zum aktuellen Verzeichnis angegeben werden. Dateieigenschaften und Dateiattribute können über zahlreiche Methoden der Klasse File103 gesetzt bzw. werden. Bsp.: import java.io.*; // import java.nio.charset.*; public class FileInfo { public static void main(String[] args) { File f = new File("./FileInfo.java"); System.out.println("The length of " + f.getName() + " is " + f.length() + " bytes."); try { System.out.println(f.getName() + " is in the " + f.getCanonicalPath() + " directory."); } catch (IOException e) { System.out.println("exception");} } } Weitere wichtige Methoden der KLasse File104 sind: exit(), list(), delete(), length(), mkdir(), renameTo(File ...), isDirectory(), isFile(). 1.4.4.5 Filtern mit den Klasse FilterReader und FilterWriter Die abstrakten Filterklassen von java.io erlauben beim Lesen und Schreiben zusätzliche Operationen auszuführen. Bsp.: Filtern von Textdateien mit der Extension .java aus dem aktuellen Verzeichnis /* * Auflisten des Inhalts vom aktuellen Verzeichnis und Filtern */ import java.io.*; class DateiFilter implements FilenameFilter { public boolean accept(File f,String s) { if (s.toLowerCase().endsWith(".java")) return true; return false; } } 102 Pfadnamen sind plattformabhängig: Bei Windows-Rechnern trennt ein BackSlash die Pfade Bei Unix-Rechnern trennt ein Slash die Pfade. 103 vgl. 7.3 104 vgl. 7.3 118 Programmieren in Java public class Verzeichnistest { public static void main(String args[]) { String nameVerz = System.getProperty("user.dir"); File userdir = new File(nameVerz); // System.out.println(System.getProperty("user.dir")); String eintraege [] = userdir.list(); System.out.println("Alle Eintraege aus dem Verzeichnis " + nameVerz); for (int i = 0;i < eintraege.length; i++) System.out.println(eintraege[i]); System.out.println("Alle Eintraege aus dem Verzeichnis mit Endung .java " + nameVerz); eintraege = userdir.list(new DateiFilter()); // Arbeitet wie die Methode list(), nur filtert ein spezielles FileName// Filter-Objekt bestimmte Namen heraus for (int i = 0;i < eintraege.length; i++) System.out.println(eintraege[i]); } } Eingabefilter Die Klasse FilterReader erhält den eigentlichen Reader über den Konstruktor und speichert ihn als Membervariable. Bsp.105: Die Klasse CaseFilter implementiert eine erweiterte Funktionalität für read(). Beim Einlesen werden Kleinbuchstaben in Großbuchstaben umgewandelt. import java.io.*; public class CaseFilter extends FilterReader { public CaseFilter(Reader f) { super(f); } public int read() throws IOException { int zeichen = super.read(); if (Character.isLowerCase((char) zeichen)) return Character.toUpperCase((char) zeichen); else return zeichen; } } In der Klasse DekoStream wird nun das read() eines CaseFilter-Objekts verwendet. Direkt beim Einlesen wird der Text formatiert. import java.io.*; public class DekoStream { public DekoStream() { String s = readNormal("DekoStream.java"); System.out.println(s); s = readFilter("DekoStream.java"); System.out.println(s); } 105 vgl. pr14341 119 Programmieren in Java private String readNormal(String fl) { StringBuffer s = new StringBuffer(); try { FileReader fread = new FileReader(fl); int zeichen; while ((zeichen = fread.read()) != -1) s.append((char) zeichen); fread.close(); } catch(IOException e) { System.err.println(e); } return s.toString(); } private String readFilter(String fl) { StringBuffer s = new StringBuffer(); try { FileReader fread = new FileReader(fl); CaseFilter ff = new CaseFilter(fread); /* Die CaseFilter-Klasse implementiert eine erweiterte Funktionalitaet fuer read(). Beim Einlesen werden Kleinbuchstaben in Grossbuchstaben umgewandelt */ int zeichen; while ((zeichen = ff.read()) != -1) s.append((char) zeichen); ff.close(); } catch(IOException e) { System.err.println(e); } return s.toString(); } public static void main(String [] args) { new DekoStream(); } } Ab JDK 1.1 gibt es einen vordefinierten Eingabefilter PushBackReader, der aus FilterReader abgeleitet ist. Ein PushBackReader erweitert die Klasse FilterReader um einen eib Byte großen PushBack-Puffer. Dieser erlaubt einer Anwendung, das zuletzt gelesenen Zeichen wieder in den Eingabestrom zurück zu schieben. Der nächste Lesezugriff liest dann nicht das folgende Zeichen im Eingabestrom, sondern das gerade zurückgelesene Zeichen. Ausgabefilter Die Architektur der Writer-Klassen macht es möglich eigene Filter zu konstruieren. Das kann bspw. durch Überlagern der Klasse Writer106 geschehen. Der offizielle Weg besteht allerdings in der Überlagerung der abstrakten Klasse FilterWriter. FilterWriter besitzt ein internes Writer-Objekt out, das bei der Initialisierung an den Konstruktor übergeben wird. Zusätzlich überlagert es drei der vier writeMethoden, um die Ausgabe auf out umzuleiten. Damit ein eigener FilterWriter erzeugt werden kann, muß beachtet werden: - die eigene Klasse leitet sich von FilterWriter ab - der Konstruktor bekommt als Parameter ein Writer-Objekt und ruft mit super(out) den Konstruktor der Superklasse auf. Das ist die Klasse FilterWriter. 106 wie es bspw. bei PrintWriter und BufferedWriter realisier wurde. 120 Programmieren in Java - Überlagern der drei write() Methoden, Ausführung der Filterfunktionen, Weitergabe der wahren Daten an den Writer Bsp.107: Konvertieren aller Zeichen in Großbuchstaben innerhalb eines Streams. import java.io.*; class UpCaseWriter extends FilterWriter { public UpCaseWriter(Writer out) { super(out); } public void write(int c) throws IOException { super.write(Character.toUpperCase((char) c)); } public void write(char cbuff[], int off, int len) throws IOException { for (int i = 0; i < len; i++) write(cbuff[off + i]); } public void write(String str, int off, int len) throws IOException { write(str.toCharArray(),off,len); } } Test: import java.io.*; public class PR14441 { public static void main(String args[]) { PrintWriter f; String s = " und diese Zeichenkette auch"; try { f = new PrintWriter( new UpCaseWriter(new FileWriter("upcase.txt"))); f.println("Diese Zeile wird gross geschrieben"); /* Die drei write() Methoden werden in UpCaseWriter do überlagert, dass zunächst Ausgabezeichen in Grossschrift konvertiert und anschliessend die passende Superklassen-Methode aufgerufen wird, um die Datei an den internen Writer out zu uebergeben */ // Test write(int); f.println(); // Test write(String) f.write(s); f.println(); // Test von write(String,int,int) f.write(s,0,23); f.println(); // Test von write(char[],int,int) f.write(s.toCharArray(),0,10); f.println(); f.close(); } catch(IOException e) { System.out.println("Fehler beim Erstellen der Datei"); } 107 vgl. pr14341 121 Programmieren in Java } } 1.4.4.6 Der Dateiauswahl-Dialog Eine spezielle Klasse FileDialog (class java.awt.FileDialog extends Dialog) zeigt einen betriebssystemunabhängigen Dateiauswahl-Dialog. Er kann für das Speichern und Öffnen konfiguriert sein. Er lassen sich die Pfade und ein FilenameFilter setzen. Erst nach dem Schließen und Beenden mit dem OKButton stehen ausgewählte Dateien zur Verfügung: Bsp.: import java.awt.*; public class DateiAuswahl extends Frame { public static void main(String args []) { Frame f = new Frame(); // ein Objeklt von Frame wird dem FileDialog mit auf den // Weg gegeben FileDialog d = new FileDialog(f,"oeffne was",FileDialog.LOAD); d.setFile("*.java"); d.show(); String datei = d.getDirectory() + d.getFile(); System.out.println(datei); } } 122 Programmieren in Java 2. Hauptbestandteile der Sprache 2.1 Token Token bedeuten in Java dasselbe wie Worte und Zeichensetzung für die menschliche Sprache. Wenn Java den Quellcode mit dem Java-Compiler (javac) kompiliert, findet eine Zerlegung des Quellcodes in kleine Bestandteile, Symbole oder Token genannt, statt. Token müssen sich in sinnvolle Einheiten und gültige Arten einordnen lassen. Das übernimmt der sog. Parser des javac, der Quelltext in Bytecode übersetzt und zusätzlich noch Leerzeichen und Kommentare (aus dem Quelltext) entfernt. Aus Token setzen sich die Ausdrucksformen von Java zusammen. Es gibt in Java fünf Arten von Token: Bezeichner oder Identifizierer, Schlüsselwörter, Literale, Operatoren, Trennzeichen. Token Schlüsselworte Beschreibung Alle Wörter, die ein wesentlicher Teil der Java-Sprachdefinition sind Bezeichner, Identifiziere Namen für Klassen,Objekte, Variablen, Konstanten, Methoden, etc.; Namen sind zusammengesetzt aus Unicode-Zeichen. An erster Stelle eines Bezeichners darf keine Zahl stehen. Literal Mit einem Literal können Variablen und Konstanten bestimmte Werte zugewiesen werden. Die können sämtliche in der Java-Sprachdefinition erlaubte Arten von Werten (numerische Werte, boolesche Werte, Zeichen, Zeichenketten) sein Trennzeichen Symbol zum Anzeigen für Trennungen und Zusammenfassungen von Code Operatoren Zeichen bzw. Zeichenkombinationen zur Angabe einer auszuführenden Operation mit einer oder mehreren Variablen oder Konstanten Zeichen, die in beliebiger Anzahl und an Leeraum108 jedem Ort zwischen Token mit Funktion zur übersichtlichen Gestaltung des Quellcodes plaziert werden können Kommentar Wird vom Compiler ignoriert. Eine Besonderheit ist der „javadoc“Kommentar, der vom JavaDokumentations-Tool ausgewertet wird. Bsp. Public, class, static, void, String, else, if, this, etc. “Hallo Java“, 13, false, true (){}[];., + - * / , >>> <<< Space, Tab, Formularvorschub Zeilenende, // .. Kommentar bis zum Zeilenende /* Eingebetter Kommentar*/ //* javadoc-Kommentar */ Abb.: Die Java-Token Java benutzt den 16-Bit-Unicode-Zeichensatz. Die Unicode-Spezifikation ist sehr umfangreich, die ersten 256 Zeichen entsprechen aber dem normalen ASCIIZeichensatz (Byte 1 ist immer auf 0 gesetzt). Der Java-Compiler erwartet Unicode109. 108 Technisch gesehen ist ein Leeraum kein Token Alle Literale (d.h. alle vorkommenden Zeichen (Zahlen, Buchstaben, Sonderzeichen, etc.) bestehen aus Unicode. 109 123 Programmieren in Java Bei der Kompilierung wird der Quellcode automatisch in eine Folge von UnicodeZeichen transformiert. Eine ASCII-Kodierung wird einfach mit Voranstellen der Zeichenfolge „\u00“ und folgender Hexadezimalzahl in das passende UnicodeZeichen übersetzt. Dies definiert in Java eine Escape-Sequenz, mit der alle UnicodeZeichen verschlüsselt werden können. Durch die „Escape-Sequenz-Darstellung“ des Quelltextes in Java ist die Verwendung von Umlauten und anderen Sonderzeichen in Bezeichnern möglich. Zwei Bezeichner gelten in Java als identisch, wenn ihre Unicode-Darstellung übereinstimmend ist. Deshalb muß auch in Java unbedingt zwischen Groß- und Kleinschreibung unterschieden werden. 2.1.1 Schlüsselworte Es gibt bestimmte Buchstabenkombinationen, die in Java eine besondere Bedeutung haben. Diese Buchstabenfolgen werden in Java Schlüsselworte genannt abstract catch do finally if interface outer return this var boolean char double float implements long package short throw void Break Class Else For Import Native Private Static Throws Volatile byte const extends future inner new protected super transient while case continue false generic instanceof null public switch true cast default final goto int operator rest synchronized try Abb.: Reservierte Java Schlüsselworte 2.1.2 Bezeichner und Namenskonventionen Bezeichner bzw. Identifizierer (identifier) sind Worte, die vom Programmierer gewählt, zu Token werden und die Variablen, Konstanten, Klassen, Objekte, Beschriftungen und Methoden darstellen. Bezeichner dürfen nicht mit JavaSchlüsselworten identisch sein. Es gibt in Java einige Regeln zur Namensvergabe, die unbedingt eingehalten werden müssen: - Bezeichner bestehen aus Unicode-Buchstaben und Zahlen in unbeschränkter Länge. Das erste Zeichen eines Bezeichners muß ein Buchstabe, der Unterstrich „_“ oder das Zeichen „$“ sein. Alle folgenden Zeichen sind entweder Buchstaben oder Zahlen. Zwei Token gelten nur dann als derselbe Bezeichner, wenn sie dieselbe Länge haben, und jedes Zeichen im ersten Token genau mit dem korrespondierenden Zeichen des zweiten Token übereinstimmt, d.h. den vollkommen identischen Unicode-Wert hat. Java unterscheidet Groß- und Kleinschreibung. Es hat sich eingebürgert, einige Namenskonventionen in Java einzuhalten: - Man sollte möglichst „sprechende“ Bezeichner wählen (Ausnahme sind Schleifen, wo für Zählvariablen meistens nur ein Buchstabe (z.B. i, j) verwendet wird. Konstanten (Elemente mit dem „Modifizierer“ „final“) sollten vollständig groß geschrieben werden. 124 Programmieren in Java - - Identifizierer von Klassen sollten mit einem Großbuchstaben beginnen und anschließend klein geschrieben werden. Wenn sich ein Bezeichner aus mehreren Wörtern zusammensetzt, dürfen diese nicht getrennt werden. Die jeweiligen Anfangsbuchstaben innerhalb des gesamten Bezeichners werden jedoch groß geschrieben. Die Identifizierer von Variablen, Methoden und Elementen beginnen mit Kleinbuchstaben und werden auch anschließend klein geschrieben. Wenn ein Bezeichner sich aus mehreren Wörtern zusammensetzt, dürfen diese nicht getrennt werden. Die jeweiligen Anfangsbuchstaben der folgenden Wörter können jedoch innerhalb des Gesamtbezeichners groß geschrieben werden. 2.1.3 Literale Literale sind Token, die für zu speichernde Werte der Datentypen byte, short, int, long, float, double, boolean und char stehen. Literale werden auch zur Darstellung von Werten benutzt, die in Zeichenketten gespeichert werden. Es gibt: Boolesche Literale, Zeichenliterale, Zeichenkettenliterale, Gleitpunktliterale, ganzzahlige Literale. 1. Ganzzahlige Literale Die Datentypen „int“ und „long“ können dezimal, hexadezimal und oktal beschrieben werden. Die Voreinstellung ist dezimal und gilt immer dann, wenn Werte ohne weitere Änderung dargestellt werden. Hexadezimale Darstellung beginnt immer mit der Sequenz 0x oder 0X110. Oktale Darstellungen beginnen mit einer führenden Null. Negativen Ganzzahlen wird ein Minuszeichen vorangestellt. Ganzzahlige Literale haben gemäß Voreinstellung den Typ „int“. Durch Anhängen vom „l“ bzw. „L“ kann man jedoch explizit den Typ „long“ wählen. 2. Gleitpunktliterale Gleitpunktliterale bestehen aus mehreren Teilen. Sie erscheinen in folgender Reihenfolge: Teil Ganzzahliger Teil Dezimalpunkt Gebrochener Teil Exponent Typensuffix Nötig? Nicht, wenn der gebrochene Teil vorhanden ist Nicht, wenn ein Exponent vorhanden ist. Muß vorliegen, wenn es einen Dezimalpunkt gibt Darf nicht vorhanden sein, wenn es keinen Dezimalpunkt gibt. Muß vorhanden sein, wenn es keinen ganzzahligen Teil gibt Nur, wenn es keinen Dezimalpunkt gibt Nein. Bei Abwesenheit eines Typensuffix wird angenommen, daß es sich um eine Zahl doppelter Genauigkeit handelt. Beispiele 0, 1, 2, ... , 9, 131140 0, 1, 1311, 41421, 9944, 718281828 E23, E-19, E6, e+307 f, F, d, D 110 Die Zahlen 0 bis 15 werden durch die Buchstaben „A“ bis „F“ bzw. „a“ bis „f“ dargestellt (Groß- oder Kleinschreibung ist hier nicht relevant. 125 Programmieren in Java Bsp.: Typensuffix für Ganzzahlen bzw. Gleitpunktzahlen class Literale { char c = 0xffff; // max. hex. Wert fuer char byte b = 0x7f; // max. hex. Wert fuer byte short s = 0x7fff; // max. hex. Wert fuer short int i1 = 0x2f; // hexadezimal in Kleinbuchstaben int i2 = 0X2F; // hexadezimal in Grossbuchstaben // hexadeimale und oktale Werte mit long long n1 = 200L; // "long"-Suffix long n2 = 200l; // "long"-suffix long n3 = 200; // float- und double-Literale float f1 = 1; float f2 = 1F; // "float"-Suffix float f3 = 1f; // "float"-Suffix float f4 = 1e-45f; // wissenschaftl. Schreibweise float f5 = 1e+9f; // "float"-Suffix double d1 = 1d; // "double"-Suffix double d2 = 1D; // "double"-Suffix; double d3 = 47e47d; // wissenschaftl. Schreibweise } 3. Boolesche Literale Es gibt zwei Boolesche Literale: true und false. Es gibt keinen Nullwert und kein numerisches Äquivalent. 4. Zeichenliterale Sie werden in einzelne, hochgestellte Anführungszeichen gesetzt. Als einzelne Zeichen gelten alle druckbaren Zeichen mit Ausnahme des Bindestrichs „-“ und des Backslash „\“. Bei Escape-Zeichenliteralen folgen den Anführungszeichen der Backslash und dem Backslash eine der folgenden Zeichen: - b, t, n, f, r, <<, ‚ oder \ - eine Serie von Oktalziffern (dreistellig)111 - ein „u“ gefolgt von einer vierstelligen Serie von Hexadezimalziffern, die für ein nicht zeilenbeendendes Unicode-Zeichen stehen. Die vier Stellen der hexadezimalen UnicodeDarstellung (\u000 bis \uFFF) stehen für 65535 mögliche Kodierungen. Escape-Literal ‘\b‘ ‘\t‘ ‘\n‘ ‘\f‘ ‘\r‘ ‘\“ ‘\‘ ‘\\‘ Unicode-Steuersequenz \u0008 \u0009 \u000a \u000c \u000d \u0022 \u0027 \u005c Oktal-Sequenz \010 \011 \012 \014 \015 \042 \047 \134 Bedeutung Backspace Tab Neue Zeile Formularvorschub Return Doppeltes Anführungszeichen Einfaches Anführungszeichen Backslash Abb.: Escape-Literale 111 Die als oktale Escape-Literale bezeichneten Zeichenliterale können zur Darstellung aller Unicode-Werte von „\u0000“ bis „\u00ff“ benutzt werden. Bei oktaler Darstellung ist diese Darstellung auf „\000“ bis auf „\u377“ begrenzt. 126 Programmieren in Java 5. Zeichenkettenliterale Zeichenkettenliterale sind aus mehreren Zeichenliteralen zusammengesetzte Ketten (Strings). Bei Zeichenkettenliteralen werden null oder mehr Zeichen in Anführungszeichen (“)dargestellt. Java erzeugt Zeichenketten als Instanz der Klasse String. Damit stehen alle Methoden der Klasse String zur Manipulation einer Zeichenkette zur Verfügung. Zeichenkettenliterale stehen zwischen zwei Anführungszeichen (“) und können Steuerzeichen wie Tabulatoren, Zeilenvorschübe, nichtdruckbare Unicode-Zeichen und druckbare Unicode-Spezialzeichen enthalten. Es kann sich auch um EscapeSequenzen handeln. Beide Anführungszeichen müssen in derselben Zeile des Quellcodes stehen. Jedes String-Literal ist eine Referenz auf ein Objekt der Klasse String. Falls der Compiler beim Übergeben des Quelltextes ein string-Literal findet, erzeut er ein neues String-Objekt und verwendet es anstelle des Literals. In Java ist der Operator + auch auf Strings definiert. Auf zwei String-Objekte angewendet, liefert er die Verkettung beider Objekte. 2.1.4 Trennzeichen Trennnzeichen sind Token, die aus einem einzigen Zeichen bestehen und andere Token trennen. Java kennt 9 Trennzeichen: ( ) { } [ ] ; , . Wird sowohl zum Öffnen einer Parameterliste für eine Methode als auch zur Festlegung eines Vorrangs für Operationen in einem Ausdruck benutzt. Wird sowohl zum Schließen einer Parameterliste für eine Methode als auch zur Festlegung eines Vorrangs für Operationen in einem Ausdruck benutzt Wird zu Beginn eines Blocks mit Anweisungen oder einer Initialisierungsliste gesetzt Wird an das Ende eines Blocks mit Anweisungen oder einer Initialisierungsliste gesetzt Steht vor einem Ausdruck, der als Index für ein Datenfeld (Array) steht Folgt einem Ausdruck, der als Index für ein Datenfeld dient Dient sowohl zum Beenden einer Ausdrucksanweisung als auch zum Trennen der Teile einer forAnweisung. wird in vielen Zusammenhängen als Begrenzer verwendet Wird sowohl als Dezimalpunkt als auch zum Trennen solcher Dinge wie Paketnamen von Klassennamen oder Variablennamen benutzt. Abb.: Die Java-Trennzeichen 127 Programmieren in Java 2.1.5 Operatoren Operatoren geben an, welche Operation mit einem oder mehreren gegebenen Operanden durchgeführt werden soll. Es gibt in Java 37 Zeichensequenzen, die Token darstellen, welche als Operatoren benutzt werden. Arithmetische Operatoren Arithmetische Operatoren benutzen nur zwei Operanden. Diese sind entweder ganzzahlige Werte oder Gleitpunktzahlen. Als Rückgabe einer arithmetischen Operation erhält man einen neuen Wert, dessen Datentyp sich folgendermaßen ergibt: - Zwei ganzzahlige Datentypen (byte, short, int oder long) als Operanden ergeben immer einen ganzzahligen Datentyp als Ergebnis. Dabei kann als Datentyp int oder long entstehen, byte und short sind nicht möglich. Der Datentyp long entsteht nur, wenn einer der beiden Operanden bereits vom Datentyp long war oder das Ergebnis von der Größe her nur als long dargestellt werden kann. - Zwei Gleitpunktzahlentypen als Operanden ergeben immer einen Gleitpunktzahlentyp als Ergebnis. Die Anzahl der Stellen des Ergebnisses ist immer das Maximum der Stellenanzahl der beiden Operanden. - Falls die Operanden ein ganzzahliger Typ und eine Gleitpunktzahlentyp sind, dann ist das Ergebnis immer ein Gleitpunktzahlentyp. Operator + * / % Bedeutung Additionsoperator Subtraktionsoperator Multiplikationsoperator Divisionsoperator Modulo-Operator Beispiel 13 + 11 13 - 11 13 * 11 13 / 11 13 % 11 Abb.: Die arithmetischen Java-Operatoren Einstellige arithmetische Operatoren Es gibt zwei einstellige (d.h. mit nur einem Operanden) arithmetische Operatoren in Java: - Einstellige arithmetische Negierung: - Das Gegenteil der arithmetischen Negierung: + Arithmetische Zuweisungsoperatoren Neben dem direkten Zuweisungsoperator gibt es die arithmetischen Zuweisungsoperatoren. Diese sind eigentlich nur eine Abkürzung für arithmetische Operationen mit ganzen Zahlen und Gleitpunktzahlen. Das Ergebnis einer Zuweisung über einen arithmetischen Zuweisungsoperator steht immer auf der linken Seite. Operator += -= *= Bedeutung Additions- und Zuweisungsoperator Subtraktions- und Zuweisungsoperator Multiplikations- und Zuweisungsoperator 128 Programmieren in Java /= %= = Divisions- und Zuweisungsoperator Modulo- und Zuweisungsoperator Direkter Zuweisungsoperator Abb.: Die arithmetischen Zuweisungsoperatoren Inkrement- / Dekrement-Operatoren Inkrement- und Dekrement-Operatoren sind einstellige Operatoren und werden nur in Verbindung mit einem ganzzahligen Wert oder einer Gleitpunktzahl benutzt. Der Inkrement-Operator (++) erhöht den Wert des Operanden um 1. Steht der Operator vor dem Operanden, erfolgt die Erhöhung des Werts, bevor der Wert dem Operanden zugewiesen wird. Wenn er hinter dem Operanden steht, erfolgt die Erhöhung, nachdem der Wert bereits zugewiesen wurde. Der Dekrement-Operator (--) erniedrigt den Wert des Operanden um 1. Steht der Operator vor dem Operanden, erfolgt die Erniedrigung des Werts, bevor der Wert dem Operanden zugewiesen wird. Wenn er hinter dem Operanden steht, erfolgt die Erniedrigung, nachdem der Wert bereits zugewiesen wurde. Bitweise arithmetische Operatoren Bitweise Arithmetik wird im wesentlichen zum Setzen und Testen einzelner Bits und Kombinationen einzelner Bits innerhalb einer Variablen benutzt. Operator & | ^ ~ Beschreibung Bitweiser AND-Operator Bitweiser OR-Operator Bitweiser XOR-Operator Bitweiser Komplement-Operator Abb.: Bitoperatoren in Java Bitweise Verschiebungsoperatoren Bitweise Verschiebungsoperatoren verschieben die Bits in einer ganzen Zahl. Operator << >> >>> Beschreibung Operator für bitweise Verschiebung nach links Operator für bitweise Verschiebung nach rechts Operator für binäre Verschiebung nach rechts mit Füllnullen Abb.: Die bitweisen Verschiebungsoperatoren Bsp.: Bitweises Manipulieren112 import java.util.*; public class BitManipulation { 112 pr21502 129 Programmieren in Java public static void main(String[] args) { Random rand = new Random(); int i = rand.nextInt(); int j = rand.nextInt(); pBinInt("-1",-1); pBinInt("+1",+1); int maxpos = 2147483647; pBinInt("maxpos", maxpos); int maxneg = -2147483648; pBinInt("maxneg",maxneg); pBinInt("i",i); pBinInt("~i",~i); pBinInt("-i",-i); pBinInt("j",j); pBinInt("i & j",i & j); pBinInt("i | j",i | j); pBinInt("i ^ j",i ^ j); pBinInt("i << 5",i << 5); pBinInt("i >> 5",i >> 5); pBinInt("(~i) >> 5",(~i) >> 5); pBinInt("i >>> 5",i >>> 5); pBinInt("(~i) >>> 5", (~i) >>> 5); long l = rand.nextLong(); long m = rand.nextLong(); pBinLong("-1L",-1L); pBinLong("+1L",+1L); long ll = 9223372036854775807L; pBinLong("maxpos", ll); long lln = -9223372036854775807L; pBinLong("maxneg",lln); pBinLong("l",l); pBinLong("~l",~l); pBinLong("-l",-l); pBinLong("m",m); pBinLong("l & m",l & m); pBinLong("l | m",l | m); pBinLong("l ^ m",l ^ m); pBinLong("l << 5",l << 5); pBinLong("l >> 5",l >> 5); pBinLong("(~l) >> 5",(~l) >> 5); pBinLong("l >>> 5",l >>> 5); pBinLong("(~l) >>> 5", (~l) >>> 5); } static void pBinInt(String s, int i) { System.out.println(s + ", int: " + i + ", binaer: "); System.out.print(" "); for (int j = 31; j >= 0; j--) if (((1 << j) & i) != 0) System.out.print("1"); else System.out.print("0"); System.out.println(); } static void pBinLong(String s, long l) { System.out.println(s + ", long: " + l + ", binaer: "); System.out.print(" "); for (int j = 63; j >= 0; j--) if (((1L << j) & l) != 0) System.out.print("1"); else System.out.print("0"); System.out.println(); } 130 Programmieren in Java } Die Ausgabe zu dieser Anwendung zeigt für den Bereich „int“ jeweils die interne Zahlendarstellung: -1, int: -1, binaer: 11111111111111111111111111111111 +1, int: 1, binaer: 00000000000000000000000000000001 maxpos, int: 2147483647, binaer: 01111111111111111111111111111111 maxneg, int: -2147483648, binaer: 10000000000000000000000000000000 i, int: -1465644750, binaer: 10101000101001000000100100110010 ~i, int: 1465644749, binaer: 01010111010110111111011011001101 -i, int: 1465644750, binaer: 01010111010110111111011011001110 j, int: -1273964081, binaer: 10110100000100001101100111001111 i & j, int: -1610610430, binaer: 10100000000000000000100100000010 i | j, int: -1128998401, binaer: 10111100101101001101100111111111 i ^ j, int: 481612029, binaer: 00011100101101001101000011111101 i << 5, int: 344008256, binaer: 00010100100000010010011001000000 i >> 5, int: -45801399, binaer: 11111101010001010010000001001001 (~i) >> 5, int: 45801398, binaer: 00000010101110101101111110110110 i >>> 5, int: 88416329, binaer: 00000101010001010010000001001001 (~i) >>> 5, int: 45801398, binaer: 00000010101110101101111110110110 Bitweise Zuweisungsoperatoren Bitweise Zuweisungsoperatoren verwenden einen Wert, führen eine entsprechende bitweise Operation mit dem zweiten Operanden durch und legen das Ergebnis als Inhalt des ersten Operanden ab. Operator &= |= ^= <<= >>= >>>= Beschreibung Bitweiser AND-Zuweisungsoperator Bitweiser OR-Zuweisungsoperator Bitweiser XOR-Zuweisungsoperator Zuweisungsoperator für die bitweise Verschiebung nach links Zuweisungsoperator für die bitweise Verschiebung nach rechts Zuweisungsoperator für die bitweise Verschiebung nach rechts mit Füllnullen Abb.: Die bitweisen Zuweisungsoperatoren Vergleichsoperatoren 131 Programmieren in Java Vergleichsoperatoren haben zwei Operanden und vergleichen diese (zwei Ganzzahlen oder zwei Gleitpunktzahlen)1. Als Rückgabewert der Operation entsteht ein boolescher Wert (true oder false). Operator == != < > <= >= Bedeutung Gleichheitsoperator Ungleichheitsoperator Kleiner-als-Operator Größer-als-Operator Kleiner-als-oder-gleich-Operator Größer-als-oder-gleich-Operator Abb.: Die Java-Vergleichsoperatoren Die relationalen Operatoren „==“ und „!=“ arbeiten mit allen Objekten. Ihre Anwendung zeigt häufig ein „verwirrendes Ergebnis“, z.B.114: public class GleichheitsTest { public static void main(String[] args) { Integer i1 = new Integer(13); Integer i2 = new Integer(13); System.out.println(i1 == i2); // false System.out.println(i1 != i2); // true } } Der Inhalt der Objekte ist zwar gleich, die Referenzen auf die Objekte sind nicht gleich. Falls der aktuelle Infalt auf Gleichheit überprüft werden soll, steht die Methode equals()115 bereit, z.B.: public class EqualsMethode { public static void main(String[] args) { Integer i1 = new Integer(13); Integer i2 = new Integer(13); System.out.println(i1.equals(i2)); } } // true Logische Vergleichsoperatoren Die logischen Vergleichsoperatoren werden nur auf boolesche Operatoren angewandt und erzeugen nur boolesche Ergebnisse. Operator && || ! Beschreibung Logischer AND-Operator Logischer OR-Operator Logischer NOT-Operator 113 Die Werte zweier Variablen vom Typ char können ebenfalls mit den Vergleichsoperatoren verglichen werden. Es werden die Variablen vom Typ char bei der Verwendung eines Vergleichsoperators wie ganze 16Bit-Zahlen entsprechend ihrer Unicode-Codierung behandelt. 114 Vgl. pr21500 115 Viele in der Java-Bibliorhek bereitgestellte Klassen implementieren die Methode equals() 132 Programmieren in Java Abb.: Logische Vergleichsoperatoren 2.1.6 Kommentare, eingebettete Dokumentation Es gibt drei Arten von Kommentaren - Einzeilige Kommentare beginnen mit // und enden mit dem Ende der Zeile - Mehrzeilige Kommentare (Blockkommentare) beginnen mit /* und enden mit */. Sie können sich über mehrere Zeilen erstrecken.. Blockkommentare dürfen nicht geschachtelt sein. - Dokumentationskommentare beginnen mit /** und enden mit */. Sie können sich ebenfalls über mehrere Zeilen erstrecken und sind spezielle Kommentare, die für das javadoc-System benutzt werden. Javadoc dient zum Erzeugen von API-Dokumentationen aus anderem Java-Quellcode. Das JDK verfügt über das Dokumentations-Tool javadoc, das auf der Basis von speziellen Kommando-Tags innerhalb einer Java-Quelltextdatei eine HTML-Datei als API-Dokumentation der angegebenen Datei116 oder des Pakets erzeugt. Tag @see [Klassenname] @see [Klassenname]#[Methodenname] @param [Parametername] [Beschreibung] @version [Text] @author [Name] @return [Beschreibung] @exception [Klassenname] @deprecated [Ausdruck] @since [Text] Beschreibung Dieses tag erzeugt einen Verweis in der HTML-Datei zu der Klasse, die von Klassenname spezifiziert wird. Dabei kann der Klassenname auch eine vollständige Pfadangabe sein Dieses Tag erzeugt einen Verweis („See also“-Link) in der HTMLDatei zu der Methode (innerhalb der mit Klassenname spezifizierten Klasse), die von Methodenname spezifiziert wird Dieses Tag dient zur Dokumentation der Parameter. Parametername ist selbsterklärend, und die Beschreibung spezifizeirt den Parameter. Geht die Beschreibung über eine Zeile hinaus, so wird dies in der nächsten Zeile fortgeführt. Mit diesem Tag kann die Version des Programms spezifiziert werden. Diese Tag fügt den Namen des Autors in die HTML-Datei ein Mit diesem Tag kann der wert beschrieben werden, der von einer Methode zurückgegeben wird Dieses Tag erzeugt einen Link auf Ausnahmen, die von der Klasse, die durch Klassenname spezifiziert ist, erzeugt werden. Neu seit Version 1.1. Das Tag markiert eine Klasse, ein Interface, ein Feld oder eine Methode als nicht verwendbar in weiteren Anwendungen´. Bereits existierender Code wird trotz dieser kennzeichnung weiter kompiliert und laufen, aber der Compiler wird eine Warnung generieren, Spezifiziert, wann das Release erstellt wurde. Abb.: Die javadoc-Tags Die Syntax von javadoc: javadoc [Optionen] [Dateiname] javadoc [Klassenname] [Paketname] Falls ein Paketname angegeben wurde, dann dokumentiert javadoc alle JavaQuelldateien innerhalb des aktuellen Verzeichnisses und anschließend das dazugehörige Paketverzeichnis117 relatin zum CLASSPATH. Für jede Klasse wird ein 116 117 vgl. 1.3.2.2, 3. Aufgabe als Paketname zu verstehen, nicht als physikalisches Verzeichnis 133 Programmieren in Java eigenes HTML-Dokument erzeugt und für die Klassen innerhalb eines Pakets ein HTML-Index generiert. Option -version -classpath [Verzeichnis] -v -verbose -d [Verzeichnis] -public -protected -private -package -Jflag -notree -doencoding [Name] -sourcepath [Pfad] Beschreibung Diese Option veranlaßt javadoc, die version des JDK anzuzeigen. Diese Angabe korrespondiert wie bei den anderen JDK-Komponenten mit der Umgebungsvariablen CLASSPATH und teilt in diesem Fall javadoc mit, in welchen Verzeichnissen nach den Quelltext-Dateien zu suchen ist. Die Verzeichnisse werden in der Unix-Syntax durch Doppelpunkte getrennt, Windows benutzt das Semikolon. Die angegebenen Pfade werden in der Reihenfolge ihres Auftretens durchsucht. Identisch mit der Option –y Mit dieser Option wird javadoc gezeigt, in welchem Verzeichnis sich das HTMLDokument zur Dokumentation nach der Generierung befinden soll. Normalerweise wird das aktuelle Verzeichnis verwendet. Zeigt nur die öffentlichen elemente an Zeigt die geschützten und die öffentlichen Klassen und Methoden an Zeigt alle Klassen und Methoden an Zeigt die Packages, die geschützten und öffentlichen Klassen und Methoden an Gibt ein Flag direkt an das Runtime-System weiter Wenn diese Option gesetzt ist, wird keine Klassenhierarchie generiert. Gibt den Suchpfad für zu dokumentierende Quelldateien an. Beeinflußt das laden von Klassendateien nicht. Wird die Option nicht verwendet, dokumentiert javadoc defaultmäßig nur die im aktuellen verzeichnis vorhandenen Klassen und Packages im Tree-Bereich der Dokumentation. Sollen Klassen und Packages in anderen verzeichnissen mit dokumentiert werden, müssen diese hier angegeben werden. Abb.: Die javadoc-Optionen 134 Programmieren in Java 2.2 Typen Ein Typ gibt in einer Computersprache an, wie etwas (z.B. eine Variable) im Speicher des Rechners dargestellt wird. In Java gibt es vier verschiedene Arten von Typen: „primitive Datentypen“, Datenfelder (Arrays) , Klassen und Schnittstellen 2.2.1 Primitive Datentypen Java besitzt 8 primitive Datentypen. Bezeichnung byte Typ 8 Bits short 16 Bits int 32 Bits long 64 Bits float 32 Bits double 64 Bits char 16 Bits boolean 1 Bit Beschreibung Kleinster Wertebereich. Mit Vorzeichen. Wird zum Darstellen von Ganzzahlwerten (ganzzahliges Zweierkomplement) von (-2 hoch 7 = 128) bis (+2 hoch 7 = 128) verwendet. Wertebereich mit Vorzeichen. Wird zum Darstellen von Ganzzahlwerten (ganzzahliges Zweierkomplement) von (-2 hoch 15 = -32167) bis (+2 hoch 15 - 1 = 32.167) verwendet. Standardwertebereich. Wird zum Darstellen von Ganzzahlwerten (ganzzahliges Zweierkomplement) von (-2 hoch 31 = -2.147.483.648) bis (+2 hoch 31 = 2.147.483.647) verwendet. Größter Wertebereich. Wird zum Darstellen von Ganzzahlwerten (ganzzahliges Zweierkomplement) von (-2 hoch 63) bis (+2 hoch 63) verwendet. Kürzester Wertebereich mit Vorzeichen zur Darstellung von Gleitpunktzahlenwerten. Dies entspricht Gleitpunktzahlen mit einfacher Genauigkeit, die den IEEE-754-1985-Standard benutzen. Es existiert ein Literal zur Darstellung von plus/minus unendlich sowie der Wert NaN (Not a Number) zur Darstellung von nicht definierten Ergebnissen Größter Wertebereich mit Vorzeichen zur Darstellung von Gleitpunktzahlenwerten. Der Wertebereich liegt zwischen +/-10 hoch 317. Auch diese Gleitpunktzahlen benutzen den IEEE-754-1985Standard. Es existiert wie beim Typ float ein Literal zur Darstellung von plus/minus unendlich sowie der Wert NaN (Not a Number) zur Darstellung von nicht definierten Ergebnissen Darstellung eines Zeichens des Unicode-Zeichensatzes. Zur Darstellung von alphanumerischen Zeichen wird dieselbe Kodierung wie beim ASCII-Zeichensatz verwendet, aber das höchste Bit ist auf 0 gesetzt. Der Datentyp ist als einziger primitiver Java-Datentyp vorzeichenlos. Der Maximalwert ist \uFFFF. Einer char-Variablen kann, da sie 2 Bytes lang ist, eine Ganzzahl zwischen 0 und 65535 ohne Konvertierung zugewiesen werden. Die Umkehrung – also die Zuweisung von char-Zeichen an andere Datentypen – funktioniert nur für int problemlos. Für andere Datentypen ist keine direkte Zuweisung möglich. Der boolschesche Datentyp umfaßt die Werte: true oder false (Defaultwert). Logische Vergleiche sind in Java vom Typ boolean. Auch boolesche Variablen sind dem Datentyp boolean zugeordnet. Werte vom Typ boolean sind zu allen anderen Datentypen inkompatibel und lassen sich nicht durch Casting in andere Typen überführen. Abb.: Primitive Datentypen der Java-Sprache 135 Programmieren in Java 2.2.2 Operationen mit primitiven Datentypen Operationen mit booleschen Variablen Operation = Name Zuweisung == Gleichheit != Ungleichheit ! & | ^ Logisches NOT AND OR XOR && Logisches AND || Logisches OR ?: if-then-else Bedeutung Einer booleschen Variable wird der Wert true oder false zugewiesen Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide boolesche Operanden denselben Wert (true oder false) haben. Anderenfalls wird false zurückgegeben. Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide boolesche Operanden unterschiedliche Werte (true oder false) haben. Anderenfalls wird false zurückgeben. Falls der Operand false ist, wird true zurückgegeben Rückgabewert ist true, falls beide Operanden true sind Rückgabewert ist false, falls beide Operanden false sind. Rückgabewert ist true, falls genau ein Operand true ist (exklusives Oder) Rückgabe von true nur dann, wenn die beiden Operanden true sind. Rückgabe von false nur dann, wenn beide Operanden false sind. Diese Operation benötigt einen booleschen Ausdruck vor dem Fragezeichen. Falls er true ist, wird der Wert vor dem Doppelpunkt zurückgegeben, ansonsten der Wert hinter dem Doppelpunkt. Abb.: Operationen mit booleschen Variablen Operationen mit Zeichenvariablen Zeichenvariablen können Operanden in jeder ganzzahligen Operation sein und werden wie ganze 16-Bit-Zahlen ohne Vorzeichen behandelt. Operation = == Name != Ungleichheit <,<=,>,>= +,- Relational Vorzeichen +,-,*,/ Binäre Arithmetik +=,-=,*=,/= Zuweisung ++,-- Binäre Arithmetik Bedeutung Einer Zeichenvariablen wird ein Wert zugewiesen Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide Operanden denselben Wert (Unicode-Werte stimmen überein) haben. Anderenfalls wird false zurückgegeben. Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide boolesche Operanden unterschiedliche Werte ( Bezogen auf die Unicode-Darstellung) haben. Anderenfalls wird false zurückgeben Operatoren zum Vergleich innerhalb einer Kontrollstruktur. Vorzeichenoperatoren bei einem Operanden Die Zeichenvariablen gehen in die Berechnung mit ihren Unicode-Werten ein. Additions-, Subtraktions-, Multiplikations-, DivisionsZuweisungen Inkrement- und Dekrement-Operatoren für den Unicode-Wert von Zeichenvariablen 136 Programmieren in Java <<,>>,>>> <<=,>>=, >>>= ~ & | ^ &=,|=,^= Verschiebung Bitweise Verschiebeoperatoren: Operatoren für bitweises Verschieben nach links, für bitweises Verschieben nach rechts und für das bitweise Verschieben nach rechts mit Füllnullen. Verschiebung und Bitweise Verschiebungs- und Zuweisungsoperatoren (nach Zuweisung links, nach rechts und nach rechts mit Füllnullen Bitweises NOT Einstellige bitweise Komplementbildung. Wenn ein Zeichen komplementiert wird, dann werden alle seine Bits invertiert. Bitweises AND Falls AND mit 2 Zeichenvariablen benutzt wird und das Ergebnis in einem dritten Zeichen abgelegt wird, dann hat das resultierende Zeichen nur für die Bits den Eintrag 1, wenn alle beiden Operanden an der gleichen Stelle Bits mit dem Wert 1 hatten Bitweises OR Falls OR mit 2 Zeichenvariablen benutzt wird und das Ergebnis in einem dritten Zeichen abgelegt wird, dann hat das resultierende Zeichen nur für die Bits den Eintrag 1, wenn einer der Operanden an dieser Position eine 1 hatte Bitweises exklusives Falls XOR mit 2 Zeichenvariablen benutzt wird und das OR Resultat in einem 3. Zeichen abgelegt wird, dann hat das resultierende Zeichen nur für die Bits den Eintrag 1, wenn das zugehörige Bit in genau einem der beiden Operanden gesetzt ist. Bitweise Zuweisung Bitweise AND-, OR-, exklusive OR (XOR)- und Zuweisungsoperatoren Abb.: Operationen mit Zeichenvariablen Operationen mit Gleitpunktzahlen Operation =,+=,-=,/= == Name Zuweisung Gleichheit != Ungleichheit <,<=,>,>= +,+,-,*,/ Relational Vorzeichen Binäre Arithmetik +=,-=,*=,/= Zuweisung ++,-- Binäre Arithmetik Bedeutung Einer Gleitpunktzahl wird ein Wert zugewiesen Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide Operanden denselben Wert haben. Anderenfalls wird false zurückgegeben Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide Operanden unterschiedliche Werte haben. Anderenfalls wird false zurückgeben Operatoren zum Vergleich innerhalb einer Kontrollstruktur Vorzeichenoperatoren bei einem Operanden Additions-, Subtraktions-, Multiplikations-, DivisionsOperatoren Additions-, Subtraktions-, Multiplikations-, DivisionsZuweisungen Inkrement- und Dekrementoperatoren für den Wert der Variablen. Abb.: Operationen mit den Typen float und double Java erzeugt keine Ausnahmen bei der Benutzung der Gleitpunktarithmetik. Ein Überlauf118 oder das Teilen aller möglichen Zahlen (außer „Null durch Null“) führt zur Ausgabe von positiven bzw. negativen „unendlichen“ Werten. Das Teilen von „Null 118 d.h.: Größeres Ergebnis von einer Operation als durch den Wertebereich des jeweiligen Typs ausgedrückt werden kann. 137 Programmieren in Java durch Null“ ergibt den Wert „NaN“ (keine Zahl). Ein Unterlauf119 gibt einen speziellen Wert aus: „positiv oder negativ Null“. Dieser Wert kann mit Vergleichsoperatoren ausgewertet werden (bewirkt false). Operationen mit ganzzahligen Variablen (bzw. ganzzahligen Ausdrücken) Operation =,+=,-=,/= == != <,<=,>,>= +,+,-,*,/ +=,-=,*=,/= ++,-<<,>>,>>> <<=,>>=, >>>= ~ & | ^ &=,|=,^= Name Zuweisung Gleichheit Bedeutung Einer ganzzahligen Variablen wird ein Wert zugewiesen Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide Operanden denselben Wert haben. Anderenfalls wird false zurückgegeben Ungleichheit Durchführung eines Vergleichs innerhalb einer Kontrollstruktur. Rückgabe ist true, wenn beide Operanden unterschiedliche Werte haben. Anderenfalls wird false zurückgeben Relational Weitere Operatoren zum Vergleich innerhalb einer Kontrollstruktur Vorzeichen Vorzeichenoperatoren bei einem Operanden Binäre Arithmetik Additions-, Subtraktions-, Multiplikations-, DivisionsOperatoren Zuweisung Additions-, Subtraktions-, Multiplikations-, DivisionsZuweisungen Binäre Arithmetik Additions-, Subtraktions-, Multiplikations-, DivisionsOperatoren Bitweise Verschiebeoperatoren: Operatoren für bitweises Verschieben nach links, für bitweises Verschieben nach rechts und für das bitweise Verschieben nach rechts mit Füllnullen Bitweise Verschiebungs- und Zuweisungsoperatoren (nach links, nach rechts und nach rechts mit Füllnullen Bitweises NOT Einstellige bitweise Operation Bitweises AND Falls AND mit 2 Ganzzahlen benutzt wird und das Ergebnis in einem dritten Ganzzahl abgelegt wird, dann hat die resultierende Ganzzahl nur für die Bits den Eintrag 1, wenn alle beiden Operanden an der gleichen Stelle Bits mit dem Wert 1 hatten Bitweises OR Falls OR mit 2 Ganzzahlen benutzt wird und das Ergebnis in einer dritten Ganzzahl abgelegt wird, dann hat die resultierende Ganzzahl nur für die Bits den Eintrag 1, wenn einer der Operanden an dieser Position eine 1 hatte Biweises exklusives Falls XOR mit 2 Ganzzahlen benutzt wird und das Resultat in OR einer 3. Ganzzahl abgelegt wird, dann hat die resultierende Ganzzahl nur für die Bits den Eintrag 1, wenn das zugehörige Bit in genau einem der beiden Operanden gesetzt ist. Bitweise Zuweisung Inkrement- und Dekrementoperatoren für den Wert der Variablen Abb.: Operationen mit ganzzahligen Variablen Ganzzahlige Variable werden in Java als „Zweierkomplement“-Zahlen mit Vorzeichen verwendet. 119 d.h.: kleineres Ergebnis (- außer Null -) von einer Operation als durch den Wertebereich des jeweiligen Typs ausgedrückt werden kann. 138 Programmieren in Java 2.2.3 Datenfelder (Arrays) Ein Datenfeld (Array) ist eine Ansammlung von Objekten eines bestimmten Typs1, die über einen laufenden Index adressierbar sind. Gegenüber „normalen Objekten“ haben Arrays zwei wesentliche Einschränkungen: 1. Arrays haben keine Konstruktoren. Statt dessen wird der Operator new mit spezieller Syntax aufgerufen. 2. Es können keine Subklassen eines Datenfelds definiert werden. Datenfelder gehören zu den Referenzvariablen1. Arrays können jeden Wertetyp (primitiver Typ oder Objekt) enthalten, jedoch können in einem Array keine unterschiedlichen Typen gespeichert werden. Arrays in Java sind Objekte und unterscheiden sich von Datenfeldern in anderen Programmiersprachen durch folgende Merkmale: - Array-Variable sind Referenzen - Arrays besitzen Methoden und Instanz-Variable - Arrays werden zur Laufzeit erzeugt 2.2.3.1 Deklarieren, Erstellen von Array-Objekten In Java umfaßt das Erstellen eines Datenfelds drei Schritte: 1. Deklaration einer Variablen zur Aufnahme eines Datenfelds. Array-Variable zeigen den Objekttyp an, den das Datenfeld aufnimmt und den namen des Arrays, gefolgt von leeren eckigen Klammern „[]“, z.B: int x[]. Alternativ dazu kann eine Array-Variable auch so (Klammern nach dem Typ) festgelegt sein, z.B. int[] x. 2. Erstellen eines neuen Array-Objekts und Zuweisen einer Array-Variablen. Das kann erfolgen - mit new, z.B. String[] namen = new String[10]; Hier wird ein neues Datenfeld von Strings mit 10 Elementen erstellt. Beim Erstellen eines Array-Objekts mit new ist anzugeben, wieviele Elemente das Array aufnehmen soll. Alle Elemente des Array werden automatisch initialisiert (0 für numerische Datenfelder, false für boolesche, ‘\0‘ für Zeichen-Arrays und „null“ für alles andere. Bsp.: Erzeugen von Datenfeldern mit „new“ a) Aufnahme von Werten primitiver Typen import java.util.*; public class FeldPrim { static Random rand = new Random(); static int zRand(int mod) { return Math.abs(rand.nextInt()) % mod; } public static void main(String[] args) 120 Es kann sich dabei um primitive Variablentypen (byte, char, short, int, long, float, double, boolean), aber auch um andere Datenfelder (verschachtelte Arrays) oder Objekte handeln. 121 Es gibt drei Arten von Referenzvariablen: Klassen, Schnittstellen und Datenfelder 139 Programmieren in Java { int[] a; // Die Groesse des Array wird zur Laufzeit // zufaellig bestimmt a = new int[zRand(20)]; ausgabe("Laenge von a = " + a.length); for (int i = 0; i < a.length; i++) ausgabe("a[" + i + "] = " + a[i]); } static void ausgabe(String s) { System.out.println(s); } } b) Aufnahme von Objekten Ein Datenfeld, dessen Komponenten keine primitiven Typen aufnehmen sollen, muß immer mit „new“ gefüllt werden. import java.util.*; public class FeldObj { static Random rand = new Random(); static int zRand(int mod) { return Math.abs(rand.nextInt()) % mod; } public static void main(String[] args) { Integer[] a; // Die Groesse des Array wird zur Laufzeit // zufaellig bestimmt a = new Integer[zRand(20)]; ausgabe("Laenge von a = " + a.length); for (int i = 0; i < a.length; i++) { a[i] = new Integer(zRand(500)); ausgabe("a[" + i + "] = " + a[i]); } } static void ausgabe(String s) { System.out.println(s); } } c) Variable Argumente Das zweite Format in der Feldinitialisierung bestimmt eine bequeme syntaktische Form für den „Aufruf von Methoden“, die den gleichen Effekt besitzt wie die „Argumentenliste in C“. Über eine solche Argumentenliste kann eine unbekannte Menge von Argumenten eines unbekannten Typs behandelt werden. Da alle Klassen von der Klasse Object abstammen, kann eine Methode mit einem Datenfeld als Argument verwendet werden, das aus Objekten vom Typ Object besteht, z.B. class A { int i; } public class VarArgs { static void f(Object[] x) 140 Programmieren in Java { for (int i = 0; i < x.length; i++) System.out.println(x[i]); } public static void main(String[] args) { f(new Object[] { new Integer(40), new VarArgs(), new Float(3.14), new Double(11.11) }); f(new Object[] { "eins", "zwei", "drei" }); f(new Object[] { new A(), new A(), new A() }); } } - durch direktes Initialisieren des Array-Inhalts, z.B.: String[] namen = {"juergen", "bernd", "liesel", "dieter", "hans", "vera", "christian", "theo", "emil", "karl"}; Alle Elemente der in der geschweiften Klammer stehenden Elemente müssen vom gleichen Typ sein 3. Speichern von Elementen im Array Zur Speicherung eines Werts in einem Array, wird der Array-Ausdruck „subscript“ benutzt, z.B.: x[subscript] . „subscript“ ist die Stelle (Position) für den Zugriff auf eine Datenfeld-Element. Das Array-Subskript muß vom Typ int sein. Wegen der vom Compiler vorgenommenen automatischen Typenkonvertierungen sind auch short, byte, char zulässig. Indexausdrücke werden vom Laufzeitsystem auf Einhaltung der Array-Grenzen überprüft. 2.2.3.2 Zugriff auf Datenfeld-Elemente, Ändern von Datenfeld-Elementen Datenfeld-Subskripte beginnen mit 0. Alle Array-Subskripte werden beim Kompilieren geprüft, ob sie sich innerhalb der Grenzem des Array befinden (Größer als 0, kleiner als die Länge des Datenfelds). String ort[] = new String[10]; ort[10] = "Dinkesbuehl"; ist falsch (Fehler beim Kompilieren). Das in „ort“ gespeicherte Array hat nur 10 ab 0 numerierte Elemente. Wird das Array-Subskript zur Laufzeit berechnet und resultiert daraus ein Wert außerhalb der Array-Grenzen, dann erzeugt der Java-Interpreter einen Fehler122. Die Länge eines Datenfelds kann mit der Instanzvariablen length getestet werden, z.B.: int laenge = ort.length;. Zum Zuweisen eines Werts wird hinter dem Zugriffsausdruck die Zuweisungsanweisung gestellt. Falls einem Array-Element ein Wert zugewiesen wird, dann wird auf das betreffende Objekt eine Referenz erzeugt. Datenfelder mit primitiven Typen (z.B. int, float) kopieren die Werte von einem Element in ein anderes. 122 Er weist auf eine „Ausnahme“ hin. 141 Programmieren in Java 2.2.3.3 Anwendungen mit eindimensionaler Datenfeldern 1. Sammeln Datenfelder sind die wohl nützlichsten Objekte in Java, mit denen Objekte in leicht zugänglichen Listen gesammelt werden können. 2. Suchen a) sequentielle Suche Aufgabenstellung: Gegeben ist eine ganze Zahl (Schlüssel schl) und ein Feld (Array) mit ganzzahligen Werten, die in diesem Array beliebig verteilt sind. Such die Indexposition, für die gilt „a[i] == schl“. Ist die Suche ergebnislos, dann gib „Nicht Gefunden“ zurück. b) binäre Suche Aufgabenstellung: Gegeben ist eine ganze Zahl (Schlüssel schl) und ein Array a mit ganzzahligen Werten, die in diesem Feld in aufsteigender Folge vorliegen. Suche die Indexposition, für die gilt: a[i] == schl“. Ist die Suche ergebnislos, dann gib „Nicht Gefunden“ zurück. Lösungsverfahren: Es wird geprüft, ob der gesuchte Schlüssel sich in der Mitte des Array befindet. Falls dies nicht der Fall ist, wird „schl < a[mitte]“ im linken Teil (von der Mitte aus gesehen) und für „schl > a[mitte]“ im rechten Teil des Array gesucht. Diese Strategie kann fortgesetzt werden, bis die Suche erfolgreich war oder der Schlüssel nicht gefunden wurde. 3. Sortieren123 Aufgabenstellung: Schreibe ein Programm, das die in einem Datenfeld („array“) gespeicherten ganzen Zahlen nach einem einfachen Sortierverfahren („Sortieren durch Austauschen, Bubble Sort“) in aufsteigende Sortierreihenfolge bringt. Lösungsverfahren (Algorithmus): Man kann jeweils den ersten Schlüssel gegen alle weiteren Schlüssel im Arbeitsspeicherfeld vergleichen und so an der ersten Position den kleinsten Schlüssel nach (N-1) Vergleichen ermitteln. Danach vergleicht man jeweils den Schlüssel aus der zweiten Position mit allen nachfolgenden Schlüsslwerten. Nach (N-2) Vergleichen steht der zweitkleinste Sachlüssel an der zweiten Position. Bei Fortsetzung dieser Verfahrensweise ist schließlich nur noch ein einziger Schlüssel vorhanden, der dann auf der richtigen, der letzten Position steht. Bsp.: 1 37 22 18 9 9 22 37 37 37 37 18 18 22 22 22 9 9 9 18 18 25 25 25 25 25 2 9 22 37 18 25 3 9 18 37 22 25 9 18 37 22 25 9 18 22 37 25 4 9 18 22 37 25 9 18 22 25 37 Abb.: Tabelle mit 5 Schlüsseln zur Demonstration des Bubble-Sort Vorschlag für die Implementierung: import java.util.*; class BubbleSort { public static void main(String args[]) { int[] x; x = new int[10]; 123 pr22301 142 Programmieren in Java Random zufallsZahl = new Random(); // Initialisieren des Array x for (int i = 0; i < 10; i++) { x[i] = zufallsZahl.nextInt(); } // Ausgabe des noch nicht sortierten x System.out.println("Vor dem Sortieren:"); for (int i = 0; i < 10; i++) { System.out.println("x["+i+"] = " + x[i]); } boolean sortiert = false; // Sortiere das Feld x while (!sortiert) { sortiert = true; for (int i=0; i < 9; i++) { if (x[i] > x[i+1]) { int temp = x[i]; x[i] = x[i+1]; x[i+1] = temp; sortiert = false; } } } // Gib das sortierte Feld x aus System.out.println(); System.out.println("Nach dem Sortieren:"); for (int i = 0; i < 10; i++) { System.out.println("x["+i+"] = " + x[i]); } } } 143 Programmieren in Java 2.2.3.4 Mehrdimensionale Datenfelder Java definiert mehrdimensionale Arrays durch Arrays von Arrays. Zweidimensionale Datenfelder Ein zweidimensionale Feld entspricht einer zweidimensionalen Wertetabelle, z.B.: Z0 Z1 Z2 Z3 Z4 S0 S1 S2 S3 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 1 2 3 4 Das vorliegende Datenfeld umfaßt 5 Zeilen und 4 Spalten. Ein Datenfeld „M“ mit 4 Zeilen und 5 Spalten ist folgendermaßen aufgebaut. M[0][0] M[1][0] M[2][0] M[3][0] M[0][1] M[1][1] M[2][1] M[3][1] M[0][2] M[1][2] M[2][2] M[3][2] M[0][3] M[1][3] M[2][3] M[3][3] M[0][4] M[1][4] M[2][4] M[3][4] Zweidimensionale Datenfelder werden, wie das folgende Beispiel zeigt, auf gleiche Weise erstellt und initialisiert wie eindimensionale Felder. class Einheitsmatrix { publich static void main(String args[]) { double[][] EM; EM = new double[4][4]; for (int zeile = 0; zeile < 4; zeile++) { for (int spalte = 0; spalte < 4; spalte++) { if (zeile != spalte) { EM[zeile][spalte] = 0.0; } else { EM[zeile][spalte] = 1.0; } } } } } Da mehrdimensionale Arrays als geschachtelte Arrays gespeichert werden, ist es möglich, „nicht-rechteckige“ Arrays zu erzeugen, z.B.: public class KeinRechteckFeld { public static void main(String args[]) { int a[][] = { {0}, 144 Programmieren in Java {1,2}, {3,4,5}, {6,7,8,9} }; for (int i = 0; i < a.length; i++) { for (int j = 0; j < a[i].length; j++) { System.out.print(a[i][j] + " "); } System.out.println(); } } } Beispiele 1. Das 8-Damen-Problem124 Aufgabenstellung: Acht Damen sollen so auf einem Schachbrett positioniert werden, daß sie sich nicht schlagen können, d.h.: Zwei Damen stehen nie in derselben Zeile oder Spalte oder Diagonale. Algorithmus zur Lösung: Zu Beginn wird ein zweidimensionales Feld mit 0 gefüllt. 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Danach wird zufällig im zweidimensionalen Feld eine „Dame“ gesetzt. Das Setzen der Dame wird durch eine 9 markiert. 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 Durch das Positionieren der Dame sind Positionierungen von weiteren Damen in derselben Zeile und Spalte wie die soeben positionierte Dame, aber auch in den zugehörigen Diagonalen nicht erlaubt. Die nicht mehr zulässigen Positionen werden mit 1 markiert. 0 1 0 1 0 1 0 0 124 0 0 1 1 1 0 0 0 1 1 1 9 1 1 1 1 0 0 1 1 1 0 0 0 0 1 0 1 0 1 0 0 1 0 0 1 0 0 1 0 0 0 0 1 0 0 0 1 0 0 0 1 0 0 0 0 vgl. pr22305 145 Programmieren in Java Damen dürfen jetzt nur noch auf die mit 0 markierten Komponenten des zweidimensionalen Felds gebracht werden. Das geschieht solange bis alle Komponenten des zweidimensionalen Felds mit 1 oder 9 gefüllt sind. Wurden beim Füllen des Felds insgesamt 8 Damen positioniert, dann wurde eine Lösung erreicht, die so aussehen könnte: 1 1 1 1 9 1 1 1 1 9 1 1 1 1 1 1 1 1 1 9 1 1 1 1 1 1 1 1 1 9 1 1 1 1 1 1 1 1 1 9 1 1 9 1 1 1 1 1 9 1 1 1 1 1 1 1 1 1 1 1 1 1 9 1 Vorschlag zur Implementierung: import java.util.*; public class Damen { // Konstanten final int N = 8; // Variable boolean fuellen; boolean ausgeben; Random rand = new Random(); int zeile, spalte; int[][] brett; int aktZahl = 0; int[] positionen = new int[N]; // Methoden int zRand(int mod) { return Math.abs(rand.nextInt() % mod); } public void initialisiere() { fuellen = false; ausgeben = false; aktZahl = 0; brett = new int[N][N]; for (int i = 0; i < N; i++) for (int j = 0; j < N; j++) { brett[i][j] = 0; } } public void nimmPosition() { do { zeile = zRand(N); // System.out.println(zeile); spalte = zRand(N); // System.out.println(spalte); } while (brett[zeile][spalte] != 0); brett[zeile][spalte] = 9; aktZahl++; } public void bewertePosition() { for (int i = 0; i < 8; i++) { if (i != spalte) brett[zeile][i] = 1; } 146 Programmieren in Java for (int i = 0; i < 8; i++) { if (i != zeile) brett[i][spalte] = 1; } int lDzeile = zeile; int lDspalte = spalte; // Beruecksichtigung der nach links laufenden Diagonale while ((lDzeile > 0) && (lDspalte > 0)) { lDzeile--; lDspalte--; } do { if ((lDzeile != zeile) && (lDspalte != spalte)) { brett[lDzeile][lDspalte] = 1; } lDzeile++; lDspalte++; } while ((lDzeile < 8) && (lDspalte < 8)); // ausgabe(); int rDzeile = zeile; int rDspalte = spalte; // Beruecksichtiung der nach rechts laufenden Diagonale while ((rDzeile > 0) && (rDspalte < 7)) { rDzeile--; rDspalte++; } do { if ((rDzeile != zeile) && (rDspalte != spalte)) { brett[rDzeile][rDspalte] = 1; } rDzeile++; rDspalte--; } while ((rDzeile < 8) && (rDspalte >= 0)); } public void pruefe() { int nullen = 0; int anzDamen = 0; for (int i = 0; i < 8; i++) for (int j = 0; j < 8; j++) { if (brett[i][j] == 0) nullen++; if (brett[i][j] == 9) anzDamen++; } if (nullen == 0) fuellen = true; if (anzDamen == 8) ausgeben = true; } public void ausgabe() { System.out.println(); for (int i = 0; i < 8; i++) { for (int j = 0; j < 8; j++) { System.out.print(brett[i][j] + " "); } System.out.println(); } System.out.println(); int k = 0; for (int i = 0; i < brett.length; i++) for (int j = 0; j < brett[i].length; j++) 147 Programmieren in Java { if (brett[i][j] == 9) positionen[k++] = j; } for (k = 0; k < N; k++) System.out.print(positionen[k]+" "); System.out.println(); } public void erzeugeDamen() { do { initialisiere(); do { nimmPosition(); // ausgabe(); bewertePosition(); // ausgabe(); if (aktZahl > 4) pruefe(); } while (!fuellen); } while (!ausgeben); ausgabe(); } } public class Damentest { public static void main(String args[]) { Damen d = new Damen(); d.erzeugeDamen(); } } 2. Pascal’sches Dreieck 1 1 1 1 1 1 1 2 3 4 5 6 1 1 3 6 10 15 1 4 10 20 1 5 15 1 6 1 Das Pascal'sche Dreieck wird mit Hilfe eines nicht rechteckigen Arrays abgebildet. Für die ersten Elemente kann geschrieben werden: int element[][] = { {1}, {1,1}, {1,2,1} }; In der folgenden Implementierung wird zu jeder Ebene dynamisch ein Feld mit der passenden Länge hinzugefügt. Bsp:125 public class Pascal 125 vgl. pr22331, Pascal.java 148 Programmieren in Java { public static void main(String args[]) { int dreieck[][] = new int[7][]; for (int i = 0; i < dreieck.length; i++) { dreieck[i] = new int[i+1]; for (int j = 0; j <= i; j++) { if ((j == 0) || (j == i)) dreieck[i][j] = 1; else dreieck[i][j] = dreieck[i - 1][j- 1] + dreieck[i - 1][j]; System.out.print(dreieck[i][j] + " "); } System.out.println(); } } } Multidimensionale Datenfelder Zweidimensionale Datenfelder bilden nicht das Ende. Java kann Felder mit 3, 4 oder mehr Dimensionen unterstützen. Es handelt sich dabei allerdings um ein Array mit Arrays (die wiederum Arrays enthalten können usw. über beliebig viele Dimensionen)126. Bsp.: Mehrdimensionale Felder import java.util.*; public class MehrdimFeld { static Random rand = new Random(); static int zRand(int mod) { return Math.abs(rand.nextInt()) % mod; } public static void main(String args[]) { // 1. Bsp: Erzeugen eines mehrdimensionalen Felds // mit Werten primitiver Typen int[][] a1 = { { 1, 2, 3, }, { 4, 5, 6, }, }; for (int i = 0; i < a1.length; i++) for (int j = 0; j < a1[i].length; j++) ausgabe("a1[" + i + "][" + j + "] = " + a1[i][j]); // 2. Bsp.: Dreidimensionales Feld mit fester Laenge int[][][] a2 = new int[2][2][4]; for (int i = 0; i < a2.length; i++) for (int j = 0; j < a2[i].length; j++) for (int k = 0; k < a2[i][j].length;k++) ausgabe("a2[" + i + "][" + j + "][" + k + "] = " + a2[i][j][k]); // 3. Bsp.: Dreidimensionales Feld mit variablen Laengen int[][][] a3 = new int[zRand(7)][][]; for (int i = 0; i < a3.length; i++) { a3[i] = new int[zRand(5)][]; for (int j = 0; j < a3[i].length; j++) 126 Im Java werden multidimensionale Arrays streng genommen nicht unterstützt 149 Programmieren in Java { a3[i][j] = new int[zRand(5)]; } } for (int i = 0; i < a3.length; i++) for (int j = 0; j < a3[i].length; j++) for (int k = 0; k < a3[i][j].length;k++) ausgabe("a3[" + i + "][" + j + "][" + k + "] = " + a3[i][j][k]); // Mehrdimensionales Feld mit nicht primitiven Objekten Integer[][] a4 = { { new Integer(1), new Integer(2) }, { new Integer(3), new Integer(4) }, { new Integer(5), new Integer(6) }, }; for (int i = 0; i < a4.length; i++) for (int j = 0; j < a4[i].length; j++) ausgabe("a4[" + i + "][" + j + "] = " + a4[i][j]); // Array mit nicht primitiven Objekten der stueckweise // aufgebaut wird Integer[][] a5; a5 = new Integer[3][]; for (int i = 0; i < a5.length; i++) { a5[i] = new Integer[3]; for (int j = 0; j < a5[i].length; j++) a5[i][j] = new Integer(i * j); } for (int i = 0; i < a5.length; i++) for (int j = 0; j < a5[i].length; j++) ausgabe("a5[" + i + "][" + j + "] = " + a5[i][j]); } static void ausgabe(String s) { System.out.println(s); } } 2.2.3.5 Die Klasse Arrays Seit dem JDK1.2 gibt es die Klasse Arrays im Paket java.util mit nützlichen Methoden zum Zugriff auf Arrays: public static void fill(int[] a, int wert) public static int binarySearch(int[] a, int schl) public static void sort(int[] a) public static boolean equals(int[] a1, int [] a2) Diese Methoden stehen auch in vergleichbaren Versionen für andere primitive Datentypen zur Verfügung. 150 Programmieren in Java 2.3 Ausdrücke Ein Ausdruck ist das Ergebnis einer Verknüpfung von Operanden und Operatoren nach den syntaktischen Regeln der Sprache. Ausdrücke werden üblicherweise zur Durchführung von Operationen (Manipulationen) an Variablen oder Werten verwendet. Ausdrücke gehören zu den kleinsten ausführbaren Einheiten eines Programms. Sie dienen zur Verzuweisung an Variable, zur Durchführung numerischer berechnungen und zur Formulierung logischer Bedingungen. Ein Ausdruck besteht immer aus mindestens einem Operanden, auf dem der Operator angewandt wird. Nach dem Typ der Operanden unterscheidet man numerische, relationale, logische, bitweise Operatoren. Jeder Ausdruck hat einen Rückgabewert, der durch die Anwendung des Operators auf die Operanden entsteht. Der Typ des Rückgabewerts bestimmt sich aus den Typen der Operanden und der Art des verwendeten Operators. 2.3.1 Arithmetische Ausdrücke Jede Programmiersprache hat einen Mechanismus für arithmetische Berechnungen. In Java werden solche Berechnungen in arithmetischen Ausdrücken durchgeführt. 2.3.2 Bewertung von Ausdrücken Bei der Bewertung von Ausdrücken spielen Operatorassoziativität, Operatorvorrang und Bewertungsreihenfolge eine Rolle. Operatorassoziativität Alle arithmetischen Operatoren assoziieren von links nach rechts, d.h.: Falls derselbe Operator in einem Ausdruck mehr als einmal vorkommt, dann wird der am weitesten links stehende zuerst bewertet, gefolgt von dem rechts daneben stehenden, usw. Die Assoziativitätsregel bestimmt, wie Kombinationen des gleichen Operators bewertet werden können. Priorität Java hält sich, wie die grundlegende Arithmetik, strikt an die Reglen der Vorrangigkeit. Die multiplikativen Operatoren (*, / und %) haben Vorrang vor den additiven Operatoren (+ und -). Immer wenn die Bewertungsreihenfolge von Operatoren in einem Ausdruck geändert werden soll, müssen Klammern benutzt werden. Jeder Ausdruck in Klammern wird zuerst bewertet. Der Vorrang der einstelligen arithmetischen Operatoren steht über allen anderen arithmetischen Operatoren. 151 Programmieren in Java Bewertungsreihenfolge Die Vorrangregeln helfen bei der Bewertung, welche Operatoren in einem Ausdruck zuerst benutzt werden und welche Operanden zu welchen Operatoren gehören. Die Regeln für die Bewertungsreihenfolge helfen festzulegen, wann welche Operanden bewertet werden. Die drei folgenden Regeln bestimmen, wie ein Ausdruck bewertet wird: - Bei allen binären Operatoren wird der linke Operand vor dem rechten bewertet. Zuerst werden die Operanden, danach die Operatoren bewertet. Falls mehrere Parameter, die durch Kommata voneinander getrennt sind, durch einen Methodenaufruf zur Verfügung gestellt werden, werden diese Parameter von links nach rechts bewertet. 2.3.3 Typkonvertierungen Java ist eine typisierte Sprache. Es finden gründliche Typüberprüfungen statt, und es gelten strikte Beschränkungen für die Konvertierung von Werten eines Typs zu einem anderen. Unter „Casting“ versteht man die Umwandlung von einem Datentyp in einen anderen. Java unterstützt explizite Konvertierungen und „ad hoc“-Konvertierungen. Ad-hoc-Konvertierungen. Hier gibt es folgende Regeln für numerische Datentypen: - Bei Operationen mit ausschl. ganzzahligen Operanden wird, falls einer der beiden Operanden den Datentyp long hat, der andere ebenfalls zu long konvertiert; ansonsten werden beide Operanden zu int konvertiert. Das Ergebnis ist dann ebenfalls vom Typ int. Ist allerdings der ausgegebene Wert zu groß, um im Wertebereich von int dargestellt zu werden, wird der Typ long verwendet. - Bei Operationen mit wenigstens einem Gleitpunkt-Operanden wird, wenn einer der Operanden den Datentyp double hat, der andere ebenfalls zu double konvertiert. Das Ergebnis ist dann ebenfalls vom Datentyp double, anderenfalls werden beide Operanden zum Datentyp float konvertiert. Das Ergebnis ist ebenfalls vom Typ float. Explizite Konvertierungen. Sie sind immer nötig, wenn eine Umwandlung in einen anderen Datentyp gewünscht wird und diese nicht „ad hoc“ eintritt. „Casting“ muß dann angewendet werden. Der zugehörige (Festlegungs-)Operator besteht aus einem Typnamen in runden Klammern. Er ist ein einstelliger Operator mit hoher Priorität und steht vor seinen Operanden. Er hat immer die folgende Form: „(Datentyp) Wert“. Der (Festlegungs-)Operator bestimmt den Wert seines Operanden, der auf den in Klammern bezeichneten Typ festgelegt wird. Es gibt folgende Casting-Operatoren: Operator (byte) Beispiel (byte) (x/y) (short) (int) (long) (float) (double) (char) (boolean) (short) x (int) (x/y) (long) x (float) x (double) x (char) x (boolean) 0 Erläuterung Wandelt das Errgebnis von x/y in einen Wert vom Datentyp byte um Wandelt x in einen Wert vom Datentyp short um Wandelt das Ergebnis von x/y in einen Wert vom Datentyp int Wandelt x in einen Wert vom Datentyp long um Wandelt x in einen Wert vom Datentyp float um Wandelt x in einen Wert vom Datentyp double um Wandelt x in einen Wert vom Datentyp char um Wandelt 0 in einen booleschen Datentyp um Abb.: Casting-Operatoren 152 Programmieren in Java Casting hat eine höhere Priorität als Arithmetik. Deshalb müssen arithmetische Operationen in Verbindung mit Casting in Klammern gesetzt werden Nicht alle Konvertierungen sind möglich. Variable eines arithmetischen Typs können auf jeden anderen arithmetischen Typ festgelegt werden. Boolesche Werte können nicht auf irgendeinen anderen Wert festgelegt werden. Die Umkehrung funktioniert mit Einschränkungen: 0 und 1 lassen sich in boolesche Werte konvertieren. Bsp.127: Konvertieren primitiver Typen public class KonvPrim { public static void main(String args[]) { char z = 'a'; System.out.println("char z = '" + z + '\''); System.out.println("Unicode von z: " + (int) z); int i = 17; System.out.println("int i = " + i); long l = 4L; System.out.println("long l = " + l); double d = 17.3; System.out.println("double d = " + d); float f = 4.5F; System.out.println("float f = " + f); double sd; float sf; long sl; int si; sd = i + l + d + f; System.out.println("sd = i + l + d + f = " + sd); // sf = i + l + d + f; /* Inkompatibler Typ! */ // System.out.println("sf = i + l + d + f = " + sf); sf = (float) (i + l + d + f); // sl = i + l + d + f; /* Inkompatibler Typ! */ // System.out.println("sl = i + l + d + f = " + sl); sl = i + l + (long) d + (long) f; System.out.println("sl = l + i + (long) d + (long) f = " + sl); sl = (long) (i + l + d + f); System.out.println("sl = (long) (i + l + d + f) = " + sl); // si = i + l + d + f; /* Inkompatibler Typ! */ // System.out.println("si = i + l + d + f = " + si); si = i + (int) l + (int) d + (int) f; System.out.println("sl = l + (int) i + (int) d + (int) f = " + sl); si = (int) (i + l + d + f); System.out.println("sl = (int) (i + l + d + f) = " + si); } } Konvertieren von Objekten. Mit Einschränkungen lassen sich Klasseninstanzen in Instanzen anderer Klassen konvertieren. Die Klassen müssen allerdings durch Vererbung miteinander verbunden sein. Allgemein gilt: Ein Objekt einer Klasse kann auf seine Superklasse festgelegt werden. Spezifische Informationen der Subklasse gehen dabei verloren. Das Konvertieren erfolgt immer nach folgender Form: (Klassenname) Objekt. 127 vgl. pr23301 153 Programmieren in Java Bsp.128: Konvertieren Datentyp Object import java.applet.Applet; import java.awt.*; import java.util.Vector; public class MittleresDrittel extends Applet { int appletHoehe; int appletBreite; Vector endpunkt = new Vector(); public void init() { Dimension d = getSize(); appletHoehe = d.height - 1; appletBreite = d.width - 1; // Anhaengen an Liste endpunkt.addElement(new Float(0.0f)); endpunkt.addElement(new Float(1.0f)); } public void paint(Graphics g) { float x1, x2; Float tempFloat; g.setColor(Color.yellow); g.fillRect(0,0,appletBreite,appletHoehe); g.setColor(Color.black); for (int i = 0; i < appletHoehe; i+=5) { // Zeichne die Linien for (int j = 0; j < endpunkt.size(); j+=2) { tempFloat = (Float) endpunkt.elementAt(j); x1 = tempFloat.floatValue(); tempFloat = (Float) endpunkt.elementAt(j+1); x2 = tempFloat.floatValue(); g.drawLine(Math.round(x1 * appletBreite),i, Math.round(x2 * appletBreite),i); } // Entferne das mittlere Drittel aus den Linien schneideausSegment(); tempFloat = (Float) endpunkt.elementAt(0); x1 = tempFloat.floatValue(); System.out.println(x1); tempFloat = (Float) endpunkt.elementAt(1); x2 = tempFloat.floatValue(); System.out.println(x2); if (Math.round(x1*appletBreite) == Math.round(x2*appletBreite)) break; } } public void schneideausSegment() { int index = 0; float luecke; Float tempFloat1, tempFloat2; int stop = endpunkt.size(); for (int i = 0; i < stop; i += 2) { schneideausMittleresDrittel(index,index+1); index += 4; } } 128 pr23302 154 Programmieren in Java public void schneideausMittleresDrittel(int links, int rechts) { float luecke; float x1, x2; Float tempFloat1, tempFloat2; // Zugriff an der angegebenen Position tempFloat1 = (Float) endpunkt.elementAt(links); tempFloat2 = (Float) endpunkt.elementAt(rechts); luecke = tempFloat2.floatValue() - tempFloat1.floatValue(); x1 = tempFloat1.floatValue() + luecke/3.0f; x2 = tempFloat2.floatValue() - luecke/3.0f; // Einfuegen an der angegebenen Stelle in der Liste endpunkt.insertElementAt(new Float(x2),rechts); endpunkt.insertElementAt(new Float(x1),rechts); } } 2.3.4 Vergleichsoperatoren Java hat eine Menge von Operatoren für das Vergleichen von zwei oder mehr Größen. Diese Operatoren lassen sich aufteilen in - relationale Operatoren. Sie sind zum Ordnen von Größen bestimmt, ob etwa ein Wert größer oder kleiner als ein anderer ist. Gleichheitsoperatoren. Sie sagen nur aus, ob zwei Werte gleich sind 2.3.5 Logische Ausdrücke Logische Operationen können auf zwei verschiedene Arten ausgeführt werden: - Short-turn-Operatoren (logisches AND && und logisches OR ||). Sie operieren nur mit booleschen Variablen. Bitweise Operatoren. Sie operieren mit jedem Bit zweier ganzzahliger Operanden. 155 Programmieren in Java 2.4 Anweisungen 2.4.1 Blöcke und Anweisungen Methoden und statische Initialisatoren werden in Java durch Anweisungsblöcke definiert. Ein Anweisungsblock besteht in der Regel aus einer Reihe von Anweisungen, die in geschweiften Klammern stehen. Das bedeutet: Es können in diesem Block lokale Variablen deklariert werden, die außerhalb des Blocks nicht verfügbar sind, und deren Existenz erlischt, wenn der Block ausgeführt wurde. 2.4.2 Leere Anweisungen In Java können leere Anweisungen erstellt werden. Für den Compiler sieht eine leere Anweisung nur wie ein zusätzliches Semikolon aus. 2.4.3 Benannte Anweisungen Jede Anweisung darf in Java eine „Benennung“ haben. Die „Benennung“ hat die gleichen Eigenschaften wie jeder andere Bezeichner. Die Reichweite der „Benennung“ erstreckt sich über den ganzen Block. Der Benennung folgt ein Doppelpunkt. „Benennungen“ werden nur von den Sprunganweisungen break und continue benutzt. 2.4.4 Deklarationen Eine Deklarationsanweisung definiert eine Variable, egal ob Klasse, Schnittstelle, Datenfeld, Objekt oder primitiver Typ. Das Format einer solchen Anweisung hängt davon ab, welcher der 5 verschiedenen Formen deklariert wird. Typname variablenName; bzw. Typname variablenName = initialerWert; 2.4.5 Ausdrucksanweisungen In Java gibt es sieben verschiedene Arten von Ausdrucksanweisungen: Ausdrucksanweisung Zuordnung Prä-Inkrement Prae-Dekrement Beispiel x=13; ++wert; --wert; 156 Programmieren in Java Post-Inkrement Post-Dekrement Methodenaufruf Zuweisungsausdruck wert++; wert--; System.out.println("Aller Anfang ist schwer!"); byte x = new byte; Abb.: Die sieben Ausdrucksanweisungen in Java Bsp.: Demonstrationsprogramm zur Wirkungsweise der Inkrement- und DekrementOperatoren129 // Demonstration der Operatoren ++ und -public class AutoInkr { public static void main(String[] args) { int i = 1; ausgabe("i: " + i); ausgabe("++i: " + ++i); // Pre-Inkrement ausgabe("i++: " + i++); // Post-Inkrement ausgabe("i: " + i); ausgabe("--i: " + --i); // Pre-Dekrement ausgabe("i--: " + i--); // Post-Inkrement ausgabe("i: " + i); } static void ausgabe(String s) { System.out.println(s); } } /* Ausgabe i: 1 ++i: 2 i++: 2 i: 3 --i: 2 i--: 2 i: 1 */ 2.4.6 Auswahlanweisungen if-Anweisungen Eine if-Anweisung testet eine boolesche Variable oder einen Ausdruck zur Feststellung, ob eine Anweisung oder ein Anweisungsblock ausgeführt werden soll. Hat die boolesche Variable den Wert true, wird der Block ausgeführt. Falls nicht, springt die Programmkontrolle zur nächsten Anweísung hinter dem Block. if (boolscher_Ausdruck) anweisung130 if-else-Anweisungen 129 vgl. pr24500 „anweisung“ bedeutet: Eine einfache Anweisung, die nur von einem Semikolon abgeschlossen ist bzw. eine zusammengesetzte Folge von anweisungen, die von { ... } umschlossen ist. 130 157 Programmieren in Java Ergänzt if um einen else-Teil. Dieser else-Teil reicht die Kontrolle an eine Anweisung oder Block weiter, wenn der boolesche Wert im if-Teil der Anweisung false war. if (boolscher_Ausdruck) anweisung else anweisung Bsp.: Demonstrationsprogramm zur if-Anweisung131 import java.lang.*; public class IfDemo extends Object { public static void main(String[] args) { int i; int abs; // if - else i = 13; System.out.println("i = " + i); if (i >= 0) { abs = i; } else { abs = -i; } System.out.println("abs = " + abs); i = -13; System.out.println("i = " + i); if (i >= 0) { abs = i; } else { abs = -i; } System.out.println("abs = " + abs); // if i = 13; abs = i; System.out.println("i = " + i); if (abs < 0) { abs = -abs; } System.out.println("abs = " + abs); i = -13; abs = i; System.out.println("i = " + i); if (abs < 0) { abs = -abs; } System.out.println("abs = " + abs); // Konditionaloperator i = -13; System.out.println("i = " + i); abs = ( i < 0 ? -i : i); System.out.println("abs = " + abs); 131 Vgl. pr24601 158 Programmieren in Java i = 13; System.out.println("i = " + i); abs = ( i < 0 ? -i : i); System.out.println("abs = " + abs); } } switch-Anweisungen Eine switch-Anweisung ermöglicht die Weitergabe des Kontrollflusses an eine von vielen Anweisungen in ihrem Block mit Unteranweisungen (ist abhängig vom Wert des Ausdrucks in der switch-Anweisung). switch (integraler_Selektor) { case integraler_Wert1: anweisung; break; case integraler_Wert2: anweisung; break; case integraler_Wert3: anweisung; break; ..... default: anweisung; } Ein integraler_Selektor ist ein Ausdruck zur Produktion eines integralen Werts132. „switch“ vergleicht das Ergebnis einer Auswertung vom integralen Selektor mit jedem integralen Wert. Falls ein passender Wert gefunden wird, wird die zugehörige Anweisung (einfach oder zusammengesetzt) ausgeführt. Anderenfalls kommt es zur Ausführung der bei „default“ angegebenen Anweisung. Die „break“-Anweisung ist optional. Fehlt sie, dann werden die folgenden Anweisungen ausgeführt, bis ein „break“ auftritt. Bsp.: Demonstrationsprogramm zur switch-Anweisung133 import java.lang.*; public class SwitchDemo extends Object { public static void main(String[] args) { int dezimal = 10; System.out.print("dezimal = " + dezimal + " hex: "); switch (dezimal) { case 0: case 1: case 2: case 3: case 4: case 5: case 6: case 7: case 8: case 9: System.out.print("" + dezimal); break; case 10: 132 Integrale Werte sind bspw. vom Typ „int“, „char“. Nichtintegrale Typen. Z.B. float, String, müssen über eine Serie von „if“-Anweisungen „switch“ simulieren 133 vgl. pr24601 159 Programmieren in Java System.out.print("A"); break; case 11: System.out.print("B"); break; case 12: System.out.print("C"); break; case 13: System.out.print("D"); break; case 14: System.out.print("E"); break; case 15: System.out.print("F"); break; default: System.out.print("Keine gueltige Hex-Ziffer"); break; } System.out.println(); } } 2.4.7 Iterationsanweisungen while-Anweisung Die while-Anweisung testet eine boolesche Variable oder einen Ausdruck. Ist er true, wird die Unteranweisung oder der Block solange ausgeführt, bis sich der Wert false einstellt. Ist die Variable oder der Ausdruck false, wird die Kontrolle an die nächste Anweisung nach der Unteranweisung oder nach dem Block der whileAnweisung weitergegeben. while (boolescher_Ausdruck) anweisung Bsp.134: public class WhileDemo extends Object { public static void main(String args[]) { double a = 2.0; int n = 10; System.out.println("a = " + a); System.out.println("n = " + n); int absn = (n < 0) ? -n : n; double aHochn = 1.0; int i = 1; while (i <= absn) { aHochn *= a; i++; } if ( n < 0) { 134 pr24700 160 Programmieren in Java aHochn = 1.0 / aHochn; } System.out.println("aHochn = " + aHochn); } } do-Anweisung Die do-Anweisung testet eine boolesche Variable oder einen Ausdruck. Solange dieser den Wert true hat, wird die Unteranweisung oder der Block ausgeführt. Erst wenn die boolesche Variable oder der Ausdruck den Wert false hat, wird die Wiederholung eingestellt und die Schleife verlassen. Der Code-Block innerhalb der do-Anweisung wird auf jeden Fall mindestens einmal ausgeführt. do anweisung while (boolescher_Ausdruck) Bsp.135: public class DoDemo extends Object { public static void main(String args[]) { double a = 2.0; int n = 10; System.out.println("a = " + a); System.out.println("n = " + n); int absn = (n < 0) ? -n : n; double aHochn = 1.0; int i = 1; do { if (n == 0) { break; } aHochn *= a; i++; } while (i <= absn); if ( n < 0) { aHochn = 1.0 / aHochn; } System.out.println("aHochn = " + aHochn); } } for-Anweisung Sie besteht aus - for, gefolgt von optionalem Leerraum, gefolgt von einer öffnenden Klammer. - Initialisierungsteil. Er enthält eine durch Kommata getrente Reihe von Deklarations- und Zuweisungsanweisungen, die durch ein Semikolon beendet wird. Die Deklarationen haben nur Gültigkeit für den Bereich der for-Anweisung 135 pr24700 161 Programmieren in Java - - und ihrer Unteranweisungen. Die Zuweisungen werden nur einmal vor der esrten Wiederholung der Unteranweisung oder des Blocks gemacht. Testteil. Enthält eine boolesche Variable oder einen Ausdruck, der einmal je Schleife neu bewertet wird. Falls der Ausdruck false wird, geht die Kontrolle zur nächsten Anweisung nach der for-Anweisung und ihrer Unteranweisung oder ihrem Block weiter. Die Zuweisungen werden nur einmal vor der ersten Wiederholung der Unteranweisung oder des Blocks gemacht. Inkrementteil. Enthält eine durch Kommata getrennte Reihe von Ausdrücken, die einmal je Durchlauf der Schleife bewertet werden. Dieser Teil wird gewöhnlich dazu verwendet, einen Index, der im Testteil überprüft wird, zu inkrementieren. Am Ende des Teils steht ein Semikolon. Jeder der drei Ausdrücke kann entfallen. for (initialisierung; boolescher_Ausdruck; naechster_Schritt) anweisung Bsp.: public class ForDemo extends Object { public static void main(String args[]) { double a = 2.0; int n = 10; System.out.println("a = " + a); System.out.println("n = " + n); int absn = (n < 0) ? -n : n; double aHochn = 1.0; for (int i = 1; i <= absn; i++) { aHochn *= a; } if ( n < 0) { aHochn = 1.0 / aHochn; } System.out.println("aHochn = " + aHochn); } } 2.4.8 Sprung-Anweisungen break-Anweisung Unteranweisungsblöcke von Schleifen und switch-Anweisungen können durch Verwendung einer break-Anweisung verlassen werden. Eine unbezeichnete breakAnweisung springt zur nächsten Zeile nach der aktuellen (innersten) Wiederholungsund switch-Anweisung. Mit einer bezeichneten break-Anweisung am Anfang einer Schleife kann an eine Anweisung mit dieser Bezeichnung in der derzeitigen Methode gesprungen werden. Am Anfangsteil der Schleife muß ein Label (eine Bezeichnung) mit einem Doppelpunkt stehen, z.B.: „label1:“. continue-Anweisung 162 Programmieren in Java Der aktuelle Schleifendurchlauf wird unterbrochen. Es wird zum Anfang der Schleife zurückgekehrt, falls hinter continue kein „Bezeichner“ steht. Anderenfalls wird zu einer äußeren Schleife zurückgekehrt, die eine Markierung (label) gleichen Namens enthält. Eine continue-Anweisung darf nur in einem Unterweisungsblock einer Iterationsanweisung stehen (while, do oder for). Bsp.136: „break“ und „continue“ innerhalb von „for“- bzw. „while“-Schleifen public class BreakundContinue { public static void main(String[] args) { for (int i = 0; i < 100; i++) { if (i == 13) break; // raus aus der Schleife if (i % 9 != 0) continue; // naechster Schleifendurchlauf System.out.println(i); } int i = 0; // eine "unendliche Schleife while (true) { i++; int j = i * 27; if (j == 1269) break; // raus aus der Schleife if (i % 10 != 0) continue; // zurueck an den Anfang der Schleife System.out.println(i); } } } Marken (labels) Eine Markierung bzw. ein „label“ bewirkt in Verbindung mit der „break“- bzw. „continue“-Anweisung eine Unterbrechnug des Schleifendurchgangs und einen Sprung zu der dem „label“ folgenden Anweisung. Sinnvollerweise steht dann ein „label“ am Anfang der Schleifen, z.B.: label1: äußere_Iteration { innere_Iteration { // ... break; // ... continue; // ... continue label1; // ... break label1; } } 136 // 1. Fall // 2. Fall // 3. Fall // 4. Fall pr24800 163 Programmieren in Java Im 1. Fall bricht „break“ aus der inneren Iteration heraus und führt in die äußere Iteration. Im 2. Fall verzweigt „continue“ auf den Anfang der inneren Iteration. Im 3. Fall führt „continue label1“ aus der inneren und äußeren Iteration heraus auf „label1“. Die Iteration wird mit dem Anfang der äußeren Iteration fortgesetzt. Im 4. Fall bricht „break label1“ aus der inneren Iteration heraus auf label1. Es kommt aber nicht zu einem erneuten Eintritt in die Iterationen, sondern zu einem Ausbruch aus beiden Iterationen. Bsp.137: Marken im Zusammenhang mit „for“- bzw. „while“-Schleifen 1. Marken im Zusammenhang mit „for“ public class MarkiertesFor { public static void main(String[] args) { int i = 0; aussen: // hier soll keine Anweisung stehen for (; true; ) // Endlos-Schleife { innen: // hier soll keine Anweisung stehen for (; i < 10; i++) { ausgabe("i = " + i); if (i == 2) { ausgabe("continue"); continue; } if (i == 3) { ausgabe("break"); i++; break; } if (i == 7) { ausgabe("continue aussen"); i++; continue aussen; } if (i == 8) { ausgabe("break aussen"); break aussen; } for (int k = 0; k < 5; k++) { if (k == 3) { ausgabe("continue innen"); continue innen; } } } } } static void ausgabe(String s) { System.out.println(s); } } /* Ausgabe i = 0 137 pr24800 164 Programmieren in Java continue innen i = 1 continue innen i = 2 continue i = 3 break i = 4 continue innen i = 5 continue innen i = 6 continue innen i = 7 continue innen i = 8 break aussen */ 2. Marken im Zusammenhang mit „while“ public class MarkiertesWhile { public static void main(String[] args) { int i = 0; aussen: while (true) { ausgabe("Aeussere While-Schleife"); while (true) { i++; ausgabe("i = " + i); if (i == 1) { ausgabe("continue"); continue; } if (i == 3) { ausgabe("continue aussen"); continue aussen; } if (i == 5) { ausgabe("break"); break; } if (i == 7) { ausgabe("break aussen"); break aussen; } } } } static void ausgabe(String s) { System.out.println(s); } } /* Ausgabe Aeussere While-Schleife i = 1 continue i = 2 i = 3 165 Programmieren in Java continue aussen Aeussere While-Schleife i = 4 i = 5 break Aeussere While-Schleife i = 6 i = 7 break aussen */ return-Anweisung Alle Funktionen haben einen eindeutigen Typ, der gleichzeitig Typ des Rückgabewerts ist. Mögliche Typen für die Rückgabe sind primitive Typen, Datenfelder (Arrays), Klassen, Schnittstellen. Es gibt einen speziellen Typ für eine Funktion ohne Rückgabewert: „void“. Der Rückgabewert ist der Wert einer return-Anweisung. throw-Anweisung Eine throw-Anweisung erzeugt eine Laufzeit-Ausnahme. 2.4.9 Synchronisationsanweisungen Sie wird für den Umgang mit „Multithreading“ benutzt. 2.4.10 Schutzanweisungen Java kennt drei Schutzanweisungen: try, catch, finally. Sie werden zur Handhabung von Ausnahmen in einer Methode benutzt, die eine Ausnahmesituation hervorrufen kann. 2.4.11 Unerreichbare Anweisungen Es ist das Schreiben einer Methode mit Codezeilen möglich, die nie erreicht werden können, z.B. Zeilen zwischen einer bedingungslosen return-Anweisung und der nächsten Bezeichnung oder dem Ende eines Blocks. Solche Anweisungen erzeugen einen Fehler beim Kompilieren. 166 Programmieren in Java 2.5 Klassen Klassen definieren Zustand und Verhalten von Objekten. Jedes Java-Programm besteht aus einer Sammlung von Klassen. Alle Klassen in Java haben eine gemeinsame Oberklasse, die Klasse Object. Auch Java selbst (als Entwicklungsplattform) ist aus Klassen aufgebaut, die mit dem JDK frei verfügbar sind. Das eigentliche RUNTIME-Modul besteht aus der Datei Java Core Classes (classes.zip), die normalerweise nicht entpackt wird und im Unterverzeichnis \lib des JDK vohanden ist. Die Datei enthält den vollständigen kompilierten Code von Java. Jede Klasse besteht formal aus zwei Teilen: der Deklaration und dem Body (Körper). Generell haben Klassendeklarationen folgendes Format: Modifizierer class NeueKlasse extends NameSuperklasse implements NameSchnittstelle Es gibt vier Eigenschaften einer Klasse, die in einer Deklaration definiert werden können: Modifizierer, Klassenname, Superklasse, Schnittstellen Modifizierer Sie stehen am Beginn der Klassendeklaration und legen fest, wie die Klasse während der weiteren Entwicklung gehandhabt werden kann. Klassen haben einen voreingestellten, „freundlichen“ Defaultstatus. Er wird immer dann verwendet, wenn kein Modifizierer am Anfang einer Klassendefinition steht. „Freundlich“ bedeutet: Die Klasse darf erweitert und von anderen Klassen benutzt werden, aber nur von Objekten innerhalb desselben Pakets. Die Grundeinstellung bezieht sich also auf die Sichtbarkeit von anderen Klassen und deren Objekten. Falls davon abgewichen werden soll, ist einer der folgenden Modifizierer zu verwenden: public , final, abstract. Öffentliche Klassen – der Modifizierer public. Eine Klasse wird als öffentlich deklariert, wenn man den Modifizierer public vor die Klassendeklaration setzt. Alle Objekte dürfen auf public-Klassen zugreifen, d.h.: Sie können von allen Objekten benutzt und erweitert werden, ganz egal zu welchem Paket sie gehören.. Die Deklaration einer öffentlichen Klasse muß immer identisch sein mit dem Namen, unter dem die Quelle dieser Datei gespeichert ist. Finale Klassen – der Modifizierer final. Finale Klassen dürfen keine Subklassen haben. Der Modifizierer final muß am Beginn der Klassendeklaration gesetzt sein. Abstrakte Klassen – der Modifizierer abstract. Von einer derartig beschriebenen Klasse wird nie eine direkte Instanz benötigt und kann auch nie eine Instanz gebildet werden. Sie darf keine Implementierung einer Methode enthalten. In einer abstrakten Klasse gibt es mindestens eine nicht vollständig angegebene Methode. Superklasse und das Schlüsselwort extends Wird der Name einer Klasse nach dem Schlüsselwort extend angegeben, dann wird damit diese Klasse als Superklasse spezifiziert, auf der eine neue Klasse aufbaut. Durch Erweiterung der Superklasse entsteht eine neue Kopie dieser Klasse, an der Veränderungen möglich sind. Die neue Klasse erbt alle Daten und Methoden, die in der Originalklasse definiert wurden. 167 Programmieren in Java 2.6 Methoden 2.6.1 Die Deklaration Die Deklarationen von Methoden haben folgendes Aussehen138: Zugriffsspezifizierer Modifizierer Returnwert NameMethode(Parameter) throws Exceptionliste Methodenunterschrift139. Unter Unterschrift einer Methode versteht man eine Kombination aus Teilen der Definition: dem Namen der Methode, dem Rückgabetyp und den verschiedenen Parametern. 2.6.2 Die Zugriffsspezifizierung Freundliche Methoden – der voreingestellte Defaultstatus. Öffentliche Methoden – der Zugriffsspezifizierer public. Geschützt Methoden – der Zugriffsspezifizierer protected. Prvate Methoden – der Zugriffsspezifizierer private. Privat geschützt – die Zugriffsspezifizierer private und protected in Kombination. Methoden, die als private protected deklariert wurden, sind sowohl für eine Klasse als auch für eine Subklasse vefügbar, aber nicht für den Rest des Pakets oder auch für Klassen außerhalb des Pakets. Das bedeutet: Subklassen einer gegebenen Klasse können Methoden, die private protected deklariert wurden aufrufen. Instanzen der Subklasse können dies aber nicht. 2.6.3 Die Methodenmodifizierer Klassenmethoden – der Modifizierer static. Abstrakte Methoden – der Modifizierer abstract Finale Methoden – der Modifizierer final. Das Schlüsselwort final vor einer Methodendeklaration verhindert, daß irgendwelche Subklassen die derzeitige Klasse dieser Methode überschreiben. Methoden, die auf keinem Fall geändert werden sollen, sollten deshalb immer als final deklariert werden. 138 139 Kursiv Geschriebenes ist optional Oft wird für denselben Zusammenhang der Begriff „methodensignatur“ verwendet. 168 Programmieren in Java Native Methoden – der Modifizierer native. Native Methoden sind Methoden, die nicht in Java geschrieben sind, aber dennoch innerhalb von Java verwendet werden sollen. Der Modifizierer native wird vor der Methode deklariert, der Body (Körper) der Methode wird durch ein Semikolon ersetzt. Methoden synchronisieren – der Modifizierer synchronized. Wird das Schlüsselwort sychronized vor eine Methodendeklaration gesetzt, dann werden Datenverletzungen verhindert, die entstehen können, wenn zwei Methoden gleichzeitig versuchen auf dieselben Daten zuzugreifen. 2.6.4 Rückgabewerte von Methoden Rückgabewerte vonJava-Methoden können von jedem erlaubten Datentyp140 sein. Eine Methode muß immer einen Wert zurückgeben (und zwar genau den Datentyp, der in der Deklaration angegeben wurde), es sei denn, sie wurde mit void deklariert. Die Methode hat dann keinen Rückgabewert. 2.6.5 Methodenname und Parameterliste Bzgl. der Methodennamen gelten die gleichen Regeln wie bei allen Token. Eine Parameterliste hat folgende Struktur: Datentyp variablenname, Datentyp variablenname, .... Die Anzahl der Parameter ist beliebig und kann Null sein. 2.6.6 Rekursion 2.6.6.1 Rekursive Funktionen Eine Funktion ist rekursiv, falls die Ausführung des Rumpfs der Funktion wiederum zum Aufruf der Funktion führt. Man unterscheidet Direkte Rekursion (Eine Funktion ruft sich selbst im Rumpf auf) und Indirekte Rekursion (Der rekursive Aufruf befidet sich nicht im Funktionsrumpf. So ruft bspw. eine Funktion A eine Funktion B auf und diese startet in ihrem Rumpf die Funktion A). Wie Wiederholungsanweisungen neigen auch rekursive Funktionen zur Gefahr nicht abbrechbarer Berechnungen. Eine Terminierung ist unbedingt erforderlich. Das geschieht über eine Bedingung von der der rekursive Aufruf abhängt.. Bei jeder rekursiven Anwendung wird ein Satz lokaler, gebundener Variablen kreiert. Sie haben zwar denselben Namen wie Objekte des vorangegengenen Aufrufs der 140 Nicht nur primitive Datentypen, sondern auch komplexe Objekte. 169 Programmieren in Java Prozedur, besitzen aber verschiedene Werte. Die Namen beziehen sich immer auf die zuletzt erzeugten Variablen. 2.6.6.2 Rekursion und Iteration Man kann zeigen, daß sich jeder rekursive Algorithmus in einen iterativen umwandeln läßt. Da die Iteration in den meisten Fällen wesentlich effizienter ist als die die Rekursion, ist die Frage berechtigt, weshalb man überhaupt die Rekusion verwendet. Für die Rekursion spricht: 1. 2. Es gibt bestimmte rekursiv formulierte Algorithmen, die schneller oder wenigstens gleich schnell arbeiten als vergleichbare iterative. Es lassen sich viele Probleme rekursiv „sehr einfach“ lösen. Rekursiv formulierte Algorithmen bieten sich insbesondere an, wenn das zugrundeliegende Problem oder die zu behandelnden Daten rekursiv definiert sind. Das ist aber noch keine Garantie dafür, daß ein rekursiver Algorithmus auch der beste Weg zur Lösung des Problems ist. 170 Programmieren in Java 3. Grafische Benutzeroberflächen und Applets 3.1 Ereignisse in grafischen Benutzeroberflächen Mit Hilfe einfacher Benutzerschnittstellen des Graphical User Interface (GUI)) lassen sich Ein- und Ausgaben141 bequem gestalten. Generell sollte Ein-/Ausgabe in Java über ein „GUI“ in Abhängigkeit von den vom Betriebssystem registrierten Ereignisarten und Zustandsänderungen erfolgen. 3.1.1 Gestaltung von GUI mit Hilfe der AWT-Klassen Java enthält ein einfach zu bedienendes System für Gestaltung grafische Benutzeroberflächen: das Abstract Windowing Toolkit (AWT). Die Fähigkeiten des AWT umfassen: Grafische Operationen zum Zeichnen von Linien oder Füllen von Flächen und zur Ausgabe von Text Methode zur Steuerung des Programmablaufs auf der Basis von Nachrichten für Tatstatur-, Maus- und Fensterereignisse. Dialogelemente zur Kommunikation mit dem Anwender und Funktion zum Design von Dialogboxen. Grafikfunktionen zur Darstellung von Bitmaps und Ausgabe von "Sound". - Zum Einbinden der Grafikfähigkeiten dient die Anweisung import java.awt.*; zu Beginn der Klassendefinition. Danach stehen alle Klassen aus dem Paket java.awt zur Verfügung. Zur Ausgabe von grafischen Elementen benötigt eine Anwendung ein Fenster, das eine Applikation (im Gegensatz zu einem Applet) selbst erzeugen muß. Das AWT enthält verschiedene Fensterklassen: Panel Component Applet Container Window Dialog FileDialog Frame Component Container 141 Abstrakte Klasse mit der Aufgabe: Repäsentation von Programmelementen, die eine Größe und Position haben und die auf eine Vielzahl von Ereignissen reagieren können bzw. Ereignisse senden können Abstrakte Klasse mit der Aufgabe: Aufnahme von Komponenten innerhalb anderer Komponenten. Container stellt für das Hinzufügen bzw. Entfernen von Komponenten Methoden bereit und realisiert mit Hilfe von "Layout-Manager"Klassen Positionierung und Anordnung von Komponenten. Standardein- und Standardausgabe spielen in Java nur im Rahmen des „Debugging“ eine Rolle. 171 Programmieren in Java Panel Applet Window Frame Dialog FileDialog Ist die konkrete Klasse mit den Eigenschaften von Component und Container. Sie erbt alle Eigenschaften von Container, kann Komponenten aufnehmen und mit Hilfe des Layoutmanagers auf dem Bildschirm anordnen. Erweitert die Funktionalität der Klasse Applet um Methoden, die für das Ausführen von Applets von Bedeutung sind. Damit entsteht ein Programmelement, das eine Größe und eine Position hat, auf Ereignisse reagieren kann und in der Lage ist, weitere Komponenten aufzunehmen. Bestimmt ein Top-Level-Window ohne Rahmen, Titelleiste und Menü. Sie ist für Anwendungen geeignet, die Rahmenelemente selbst zeichnen oder volle Kontrolle über das gesamte Fenster benötigen. Repräsentiert ein Top-Level-Window mit Rahmen, Titelleiste und optionalem Menü. Realisiert modale und nicht modale Dialoge. Stellt ein Standard-Dateidialog des jeweiligen Systems bereit. Dieser kann beim Laden oder Speichern einer datei zur Eingabe oder zur Auswahl eines Dateinamens verwendet werden. Abb. Fensterklassen-Hierarchie Alle Fensterklassen sind von einer gemeinsamen abstrakten Basisklasse abgeleitet, die die Funktionalität für Aussehen, Positionierung, Ausgabe und Laufzeitverhalten( Fokus, Mauszeiger, Sichtbarkeit) bereitstellt. Component { abstract } public String getName() public void setName(String name) public Container getParent() public boolean contains(int x, int y) public boolean isVisible() public void setVisible(boolean b) public boolean isEnabled() public void setEnabled(boolean b) public boolean isShowing() public Color getForeground() public void setForeground(Color c) public Color getBackground() public void setBackground(Color c) public Font getFont() public void setFont(Font f) public Point getLocation() public Point getLocationOnScreen() public void setLocation(int x, int y) public void setLocation(Point p) public Dimension getSize() public void setSize(int breite, int hoehe) public void setSize(Dimension d) public Rectangle getBounds() public void setBounds(int x, int y, int breite, int hoehe) public void setBounds(Rectangle r) public Graphics getGraphics() public FontMetrics getFontMetrics(Font font) public void paintAll(Graphics g) public void repaint() public boolean imageUpdate(Image bild, int flags, int x, int y, int breite, int hoehe) public Image createImage(ImageProducer erz) public Image createImage(int breite, int hoehe) public boolean prepareImage(Image bild, ImageObserver obs) public boolean prepareImage(Image bild, int breite, int hoehe, ImageObserver obs) public int checkImage(Image bild, ImageObserver obs) 172 Programmieren in Java public int checkImage(Image bild, int breite, int hoehe, ImageObserver obs) public void addComponentListener(ComponentListener l) public void removeComponentListener(ComponentListener l) public void addFocusListener(FocusListener l) public void removeFocusListener(FocusListener l) public void addKeyListener(KeyListener l) public void removeKeyListener(KeyListener l) public void addMouseListener(MouseListener l) public void removeMouseListener(MouseListener l) public void addMouseMotionListener(MouseMotionListener l) public void removeMouseMotionListener(MouseMotionListener l) public final void enableEvents(long eventsToEnable) public final void disableEvents(long eventsToDisable) protected void processComponentEvent(ComponentEvent e) protected void processFocusEvent(FocusEvent e) protected void processKeyEvent(KeyEvent e) protected void processMouseEvent(MouseEvent e protected void processMouseMotionEvent(MouseEvent e) public boolean gotFocus(Event evt) //deprecated, -> processFocusEvent(FocusEvent e) public boolean lostFocus(Event evt, Object was) public void requestFocus() public void add(PopupMenu popup) public void remove(MenuComponent popup) public String toString() …… ……. ……. ……. ……. Abb.: Die Klasse Component Container sind Fenster oder Fensterbereiche, in denen andere Kontrollelemente platziert werden können. Component Container {abstract} public int countComponents() public Component getComponent(int n) public Components[] getComponents() public Insets getInsets() public Component add(Component comp) public void remove(Component comp) public void removeAll() public LayoutManager getLayout() public void setLayout(LayoutManager mgr) public Dimension getPreferredSize() public Dimension getMinimumSize() public Dimension getMaximumSize() public void invalidate() public void paint(Graphics g) //container public void update(Graphics g) //container protected void processEvent(AWTEvent e) //container public void addNotify() //container public void removeNotify() //container 173 Programmieren in Java Abb.: Die Klasse Container Zur Anzeige eines Fensters auf dem Bildschirm muß eine der Fensterklassen, Window, Frame, Dialog, Applet, FileDialog instanziert werden. Zum Ableiten einer eigenen Fensterklasse wird in der Regel die Klasse Frame oder Dialog verwendet, die beibe aus der Klasse Window abgeleitet sind. Container Window << Methoden >> public void show() public boolean isShowing() public void dispose() public void pack() public void toFront() public void toBack() public void addWindowListener(WindowListener l) Frame Dialog public static final int DEFAULT_CURSOR << Konstruktor >> public Frame() public Frame(String titel) << Methoden >> public String getTitle() public void setTitle(String titel) public MenuBar getMenuBar() public void setmenuBar(MenuBar mb) public boolean isResizable() public void setResizable(boolean b) public void remove(MenuComponent m) << Konstruktor >> public Dialog(Frame eltern) public Dialog(Frame eltern, boolean modal) << Methoden >> public boolean isModal() public void setModal(boolean b) public void show() public boolean isResizable() public void setResizable(boolean b) Abb.: Die Klassen Frame und Dialog Bsp.142: Ein-, Ausgabe über Textfelder in einem ersten GUI Ein erstes GUI soll aus einem editierbaren und einem nicht editierbaren Textfeld bestehen. Den beiden Textfeldern soll jeweils ein Label mit der Beschriftung „Eingabestring:“ bzw. „Ausgabestring“ zugeordnet sein. Diese Komponenten sollen automatisch von links nach rechts und von oben nach unten angeordnet werden in einem Panel, das selbst wiederum einziges Objekt in einem Fenster (Frame) mit dem Titel „Echo“ ist. import java.lang.*; import java.awt.*; public class EchoMitBeno { // Einlesen und Ausgeben von Zeichenketten ueber // eine grafische Benutzeroberflaeche public static void main(String args[]) { 142 pr14160 174 Programmieren in Java TextField eingabeTextFeld = new TextField(20); Label eingabeTextFeldLabel = new Label("Eingabestring:"); eingabeTextFeld.setEditable(true); TextField ausgabeTextFeld = new TextField(20); Label ausgabeTextFeldLabel = new Label("Ausgabestring:"); ausgabeTextFeld.setEditable(false); Panel panel = new Panel(); panel.add(eingabeTextFeldLabel); panel.add(eingabeTextFeld); panel.add(ausgabeTextFeldLabel); panel.add(ausgabeTextFeld); Frame fenster = new Frame("Echo"); fenster.add(panel); fenster.pack(); fenster.setVisible(true); } } Die Implementierung Ausgabefenster: des vorliegenden Programms zeigt das folgende Abb. Das vorliegende Beispiel zeigt noch einige Mängel: - Zwar kann in das für die Eingabe vorgesehene Textfeld ein String eingegeben werden, das zweite Textfeld ist allerdings nicht zugänglich. Es passiert somit nicht gerade viel. Es werden noch keine Ereignisse aufgefangen und behandelt. - Noch nicht einmal kann das Fenster geschlossen werden, und die Applikation muß (nach dem Schließen des Frame) mit "CTRL-C" explizit abgebrochen werden. 3.1.2 Ereignisbehandlung unter grafischen Benutzeroberflächen Im Mittelpunkt der Programmierung unter einer GUI steht die Kommunikation zwischen System und Anwendungsprogramm. Die Anwendung wird über alle Arten von Ereignissen und Zustandsänderungen vom System durch Versenden von Nachrichten (z.B. über Mausklick, Tastatureingaben, Veränderungen an Größe und Lage der Fenster) informiert. Die Reaktion auf die Nachrichten erfolgt in spezielllen Ereignisempfängern (EventListeners), die das zum Ereignis passende EmpfängerInterface implementieren. Damit ein Ereignisempfänger Nachrichten einer bestimmten Ereignisquelle erhält, muß er sich bei der Quelle registrieren lassen, d.h.: Es muß eine EventListener-Klasse geschrieben, instanziert und bei der Ereignisquelle registriert werden. Zum Empfang von Nachrichten muß ein Objekt eine Reihe von Methoden implementieren, die von der Nachrichtenquelle, bei der es sich registriert hat, aufgerufen werden können. Die Ereignisempfänger stellen diese Methoden durch Implementierung von Interfaces bereit, die aus der Klasse EventListener des Pakets java.util abgeleitet sind. 175 Programmieren in Java Ereignistypen. Im JDK 1.1 werden Ereignistypen durch eine Hierarchie von Ereignisklassen repräsentiert, die alle aus der Klasse java.util.EventObject143 abgeleitet sind. 143 Speichert das Objekt, das die Nachricht ausgelöst hat und gibt durch Aufruf von "public Object getSource()" das Objekt an. 176 Programmieren in Java EventObject AWTEvent ComponentEvent FocusEvent InputEvent KeyEvent ActionEvent AdjustmentEvent ContainerEvent ItemEvent TextEvent WindowEvent MouseEvent Abb.: Spezifische Ereignisklassen Die Hierarchie der AWT-spezifischen Ereignisklassen beginnt mit der Klasse AWTEvent und befindet sich im Paket java.awt. AWTEvent ist Superklasse aller Ereignisklassen des AWT, die sich im Paket java.awt.event befinden. Dieses Paket ist in jede Klasse einzubeziehen, die sich mit dem Event-Handling von AWTAnwendungen beschäftigt. EventListener-Interface. Je Ereignisklasse gibt es ein EventListener-Interface. Es definiert eine seperate Methode für jede Ereignisart der Ereignisklasse. So besitzt bspw. das Interface MouseListener die Methoden mouseClicked, mouseEntered, mouseExited, mousePressed und mouseReleased, die beim Eintreffen des jeweiligen Ereignis aufgerufen werden. EventListener FocusListener ActionListener AdjustementListener ItemListener KeyListener MouseListener MouseMotionListener ComponentListener ContainerListener WindowListener Abb.: Hierarchie der EventListener-Interfaces Jede der Methoden eines Listener-Interface enthält als einziges Argument ein Objekt vom zugehörigen Ereignistyp. Alle Methoden sind vom Typ void. 177 Programmieren in Java 3.1.3 Anwendung lokaler Klassen für die Ereignisbehandlung Im GUI-Objekt, das einen Event-Handler benötigt wird eine lokale Klasse zur Implementierung des passenden Interface angegeben. Lokale Klassen werden lokal zu einer anderen Klasse erzeugt. Sie sind nur innerhalb dieser Klasse definiert und sichtbar. Objekte der lokalen Klasse können nur aus der erzeugenden Klasse produziert werden. Die lokale Klasse kann aber auf alle Instanzmerkmale der erzeugenden Klasse zugreifen. Eine Variante lokale Klassen sind anonyme Klassen. Sie werden ebenfalls lokal zu einer anderen Klasse erzeugt, kommen aber ohne Klassennamen aus. Dazu werden sie bei der Übergabe eines Objekts an eine Methode oder als Rückgabewert einer Methode innerhalb einer einzigen Anwendung definiert und instanziert. Damit einer anonymen Klasse überhaupt eine sinnvolle Aufgabe zugeführt werden kann, muß sie aus einer anderen Klasse abgeleitet sein oder ein bestehendes Interface implementieren. Bsp.: ActionListener-Interface für die Übernahme eines Textfeld-Inhalts in ein Textfeld zur Ausgabe Falls das Textfeld für die Ausgabe den im Textfeld für die Eingabe angegebenen Text wiedergeben soll, muß ein ActionListener (eine zusätzlich innere, anonyme Klasse) mit der Methode public void actionPerformed (ActionEvent a) definiert werden. Diese Methode umfaßt Aufrufe der Methoden getText() und setText() der Klasse TextField. Darüber kann in das Eingabetextfeld eingegebener Text in das Ausgabefeld ausgegeben werden. Da die "Methode actionPerformed" der anonymen inneren Klasse auf "eingabeTextFeld" bzw. "ausgabeTextFeld" zugreift, ist außerhalb von main() anzugeben: private static TextField eingabeTextFeld = new TextField(20); private static TextField ausgabeTextFeld = new TextField(20); Zum Schließen des Fensters wird über eine anonyme innere Klasse ein WindowAdapter (eine Art WindowListener) mit der Methode "public void windowClosing(WindowEvent e)" definiert und an den "Frame" eingekettet. Wird der Frame geschlossen, dann wird diese Methode aufgerufen, die über System.exit(0) die Applikation beendet. Eine Adapterklasse implementiert ein Interface mit mehreren Mehoden und erlaubt es somit abgeleiteten Klassen, nur noch die Methoden zu überlagern, die tatsächlich von Interesse sind. Passende Adapterklasssen stellt das Paket java.awt.event bereit, z.B. FocusAdapter, KeyAdapter, MouseAdapter, MouseMotionAdapter, ComponentAdapter, ContainerAdapter, WindowAdapter. Die Registrierung erfolgt mit der Methode addWindowListener, die in den Klassen Dialog und Frame zur Verfügung steht. Bsp.144: Textfeldbearbeitung mit "ActionListener-Interface" und WindowAdapter import java.lang.*; import java.awt.*; import java.awt.event.*; public class EchoMitBeno extends Object { // Einlesen und Ausgeben von Zeichenketten ueber // eine grafische Benutzeroberflaeche private static TextField eingabeTextFeld = new TextField(20); private static TextField ausgabeTextFeld = new TextField(20); 144 pr14160 178 Programmieren in Java public static void main(String args[]) { Frame fenster = new Frame("Echo"); fenster.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // TextField eingabeTextFeld = new TextField(20); Label eingabeTextFeldLabel = new Label("Eingabestring:"); eingabeTextFeld.setEditable(true); eingabeTextFeld.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { String s = eingabeTextFeld.getText(); ausgabeTextFeld.setText(s); } }); // TextField ausgabeTextFeld = new TextField(20); Label ausgabeTextFeldLabel = new Label("Ausgabestring:"); ausgabeTextFeld.setEditable(false); Panel panel = new Panel(); panel.add(eingabeTextFeldLabel); panel.add(eingabeTextFeld); panel.add(ausgabeTextFeldLabel); panel.add(ausgabeTextFeld); // Frame fenster = new Frame("Echo"); fenster.add(panel); fenster.pack(); fenster.setVisible(true); } } Alternativ zur vorliegenden Realisierung der Ereignisbehandlung über eine anonyme Klasse, kann auch ein Listener über eine lokale Klasse implementiert werden. Gewöhnlich ist die Anwendung eine Subklasse von Frame. import java.lang.*; import java.awt.*; import java.awt.event.*; public class Vorl11b extends Frame { private static Label eingabeTextFeldLabel = new Label("Eingabestring:"); private static TextField eingabeTextFeld = new TextField(20); private static Label ausgabeTextFeldLabel = new Label("Ausgabestring:"); private static TextField ausgabeTextFeld = new TextField(20); public Vorl11b() { eingabeTextFeld.setEditable(true); ausgabeTextFeld.setEditable(false); Panel panel = new Panel(); panel.add(eingabeTextFeldLabel); panel.add(eingabeTextFeld); panel.add(ausgabeTextFeldLabel); panel.add(ausgabeTextFeld); eingabeTextFeld.addActionListener(new TFL()); add(panel); pack(); setVisible(true); } class TFL implements ActionListener 179 Programmieren in Java { public void actionPerformed(ActionEvent ae) { String s = eingabeTextFeld.getText(); ausgabeTextFeld.setText(s); } } public static void main(String args[]) { Vorl11b vorl11b = new Vorl11b(); vorl11b.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } } Schließlich kann das Interface auch direkt implementiert Zweckmäßigerweise ist dann die Anwendung eine Subklasse von Frame. werden. import java.lang.*; import java.awt.*; import java.awt.event.*; public class Vorl11a extends Frame implements ActionListener { private static Label eingabeTextFeldLabel = new Label("Eingabestring:"); private static TextField eingabeTextFeld = new TextField(20); private static Label ausgabeTextFeldLabel = new Label("Ausgabestring:"); private static TextField ausgabeTextFeld = new TextField(20); public Vorl11a() { eingabeTextFeld.setEditable(true); ausgabeTextFeld.setEditable(false); Panel panel = new Panel(); panel.add(eingabeTextFeldLabel); panel.add(eingabeTextFeld); panel.add(ausgabeTextFeldLabel); panel.add(ausgabeTextFeld); eingabeTextFeld.addActionListener(this); add(panel); pack(); setVisible(true); } public void actionPerformed(ActionEvent ae) { System.out.println(ae.getActionCommand()); String s = eingabeTextFeld.getText(); ausgabeTextFeld.setText(s); } public static void main(String args[]) { Vorl11a vorl11a = new Vorl11a(); vorl11a.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } } 180 Programmieren in Java 181 Programmieren in Java 3.1.4 Externe und interne Darstellung von numerischen Werten Numerische Werte werden - extern (z.B. im Textfeld eines GUI) durch Zeichenketten dargestellt - intern als Werte von einem der elementaren Datentypen byte, short, int, long, float oder double oder als Objekte von einem der Referenzdatentypen (WrapperKlassen) Byte, Short, Integer, Long oder Double. Diese Klassen stellen Methoden zur Umwandlung von numerischen Werten in Zeichenketten und umgekehrt zur Verfügung. Da nicht jede beliebige Zeichenkette als interne Darstellung einer Zahl interpretiert werden kann, kann beim Versuch der Umwandlung eine NumberFormatExpression auftreten, die normalerweise mit einer try-catch-Anweisung entsprechend behandelt werden sollte. Bsp.145: Einlesen und Ausgeben von numersichen Werten import java.lang.*; import java.awt.*; import java.awt.event.*; public class EchoZahl extends Object { // Einlesen und Ausgeben von Zeichenketten ueber // eine grafische Benutzeroberflaeche private static TextField eingabeTextFeld = new TextField(20); private static TextField ausgabeTextFeld = new TextField(20); public static void main(String args[]) { Frame fenster = new Frame("Echo"); fenster.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); // TextField eingabeTextFeld = new TextField(20); Label eingabeTextFeldLabel = new Label("Zahleneingabe:"); eingabeTextFeld.setEditable(true); eingabeTextFeld.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent ae) { Double dRef = null; String s = eingabeTextFeld.getText(); try { dRef = new Double(s); } catch (NumberFormatException nfe) { System.err.println(nfe.toString()); } double d = dRef.doubleValue(); d++; String s1 = Double.toString(d); String s2 = String.valueOf(d); ausgabeTextFeld.setText(s2); } }); // TextField ausgabeTextFeld = new TextField(20); Label ausgabeTextFeldLabel = new Label("Ausgabestring:"); 145 pr14160 182 Programmieren in Java ausgabeTextFeld.setEditable(false); Panel panel= new Panel(); panel.add(eingabeTextFeldLabel); panel.add(eingabeTextFeld); panel.add(ausgabeTextFeldLabel); panel.add(ausgabeTextFeld); // Frame fenster = new Frame("Echo"); fenster.add(panel); fenster.pack(); fenster.setVisible(true); } } 3.1.5 Low-Level-Events Von der Klasse ComponenEvent sind Event-Klassen für "Low-Level"-Ereignisse abgeleitet. Sie sind für den Transfer von elementaren Nachrichten zuständig, die von Fenstern und Dialogelementen stammen. Component-Events Wird eine Komponente verschoben oder ihre Größe bzw. ihr Anzeigezustand verändert, dann wird ein Componen-Event generiert. Da Fenster und alle Dialogelemente aus der Klasse Component abgeleitet sind, gelten die hier angegebenen Ereignisse für nahezu alle GUI-Elemente. Der Empfänger für ComponentEvent muß das Interface ComponentListener implementieren und bekommt Events des Typs ComponentEvent übergeben. ComponentEvent erweiter AWTEvent und stellt neben getID, getSource die Methode public Component getComponent() bereit, mit der die Komponente ermittelt werden kann, die die Nachricht ausgelöst hat. Die Registrierung der Empfängerklasse erfolgt mit public void addComponentListener(ComponentListener l). Ereignismethode componentShown componentHidden componentMoved componentResized Bedeutung Eine Komponente wurde sichtbar Eine Komponente wurde unsichtbar Eine Komponente wurde verschoben Die Größe der Komponente hat sich verändert Abb.: Übersicht zu den Methoden von ComponentListener Window-Events Ein WindowEvent wird generiert, falls sich am Status eines Fensters eine Änderung ergeben hat, die für das Anwenderprogramm interessant sein könnte. Ein Empfänger für Window-Events muß das Interface WindowListener implementieren. WindowEvent stellt neben getID, getSource die Methode public Window getWindow() zur Verfügung, mit der das Fenster ermittelt werden kann, das die Nachricht ausgelöst hat. Die Registrierung der Empfängerklasse erfolgt über die Methode public void addWindowListener(WindowListener l) , die in den Klassen Dialog und Frame zur Verfügung steht. Ereignismethode Bedeutung 183 Programmieren in Java windowActivated windowClosed windowClosing windowDeactivated windowDeiconified windowIconified windowOpened Das Fenster wurde aktiviert. Die Methode wird nach dem Erstellen des Fensters aufgerufen und wenn ein Fenster, das im Hintergrund stand, erneut in den Vordergrund gelangt. Das Fenster wurde geschlossen Das Fenster soll geschlossen werden. Diese Methode wird aufgerufen, wenn der Anwender das Fenster über die TitelLeiste, das Systemmenü oder die Tastenkombination ALT+F4 schließen will. Die Anwendung hat den Code bereit zu stellen, der das Fenster schließt. Standardmäßig reagiert das Programm nicht auf diese Benutzeraktionen Das Fenster wurde deaktiviert, also in den Hintergrung gestellt Das Fenster wurde wiederhergestellt, nachdem es zuvor auf Symbolgröße verkleinert wurde Das Fenster wurde auf Symbolgröße verkleinert Das Fenster wurde geöffnet. Abb.: Übersicht zu den Methoden von WindowListener Mouse-Events Ein Mouse-Event entsteht, wenn der Anwender (innerhalb der Client-Area des Fensters) eine der Maustasten drückt oder losläßt. Dabei reagiert das Programm sowohl auf Klicks der linken als auch der ( - falls vorhanden -) der rechten Maustaste und zeigt an, welche der Umschalttasten STRG, ALT, UMSCHALT oder META während des Mausklicks gedrückt waren. Es ist möglich zwischen einfachen oder doppelten Mausklicks zu unterscheiden. Ein Empfänger für Mouse-Events erweitert die Klasse InputEvent und stellt neben getID, getSource eine Reihe zusätzlicher Methoden bereit. Die Registrierung der Empfängerklasse erfolgt mit public void addMouseListener(MouseListener l), die in allen Klassen zur Verfügung steht, die aus Component abgeleitet sind. Ereignismethode mousePressed mouseReleased mouseClicked mouseEntered mouseExited Bedeutung Die Maustaste wurde gedrückt Die gedrückte Maustaste wurde losgelassen Eine Maustaste wurde gedrückt und wieder losgelassen. Die Methode wird nach mouseReleased aufgerufen. Der Mauszeiger wurde in den Client-Bereich der auslösenden Komponente hineinbewegt Der Mauszeiger wurde aus dem Client-Bereich der auslösenden Komponente herausbewegt. Abb.: Übersicht zu den Methoden von MouseListener Die Ermittlung der Position des Mauszeigers kann über public int getX(); // liefert die X-Koordinate public int getY(); // liefert die Y-Koordinate public Point getPoint(); /* liefert X- und Y-Koordinate des Punkts, an dem sich der Mauszeiger beim Auftreten des Ereignisses befindet */ ermittelt werden. Koordinatenwerte werden relativ zum Ursprung der auslösenden Komponente angegeben. Weiterhin gibt es in MouseEvent die Methode public boolean isPopupTrigger(). Darüber kann abgefragt werden, ob das Klickereignis den Ausruf eines Popup-Menüs anzeigen soll. Die Methode public int getClickCount() liefert die Anzahl der Mausklicks. Für die Beabeitung von Mouse-Events stehen außerdem die aus InputEvent 184 Programmieren in Java 146geerbten public public public public Methoden boolean boolean boolean boolean isShiftDown(); isControlDown(); isMetaDown(); isAltDown(); zur Verfügung. MouseMotionEvents Sie geben Auskunft über die Bewegung des Mauszeigers. Ein Empfänger für "MouseMotionEvents" muß das Interface MouseMotionListener implementieren. Es wird mit public void addMouseMotionListener(MouseListener l) registriert. Die Methode steht allen Objekten der Klasse Component oder daraus abgeleiteteter Klassen zur Verfügung. Die Methoden von MouseMotionListener bekommen Events des Typs MouseEvent übergeben. Damit stehen diesselben Methoden wie bei MouseEvent zur Verfügung. Das Interface MouseMotionListener definiert public abstract void mouseMoved(MouseEvent e); // Aufruf bei Bewegung einer Maus ohne Drücken der Maustaste public abstract void mouseDragged(MouseEvent e); /* Aufruf bei Bewegung der Maus und gedrückter rechter Maustaste */ oder linker Fokus-Events Der Fokus zeigt an, welches Fenster Tastatureingaben erhält. Sind mehrere Fenster gleichzeitig geöffnet, so kann immer nur eines von ihnen den Fokus beanspruchen. Sind auf einem aktiven Fenster mehrere Dialogelemente aktiv, so kann ebenfalls nur eines davon den Fokus erhalten, denn jedes Dialogelement wird ebenfalls durch ein (meist unsichtbares) Fenster dargestellt. Ein Empfänger für Fokus-Events muß das Interface FocusListener implementieren und bekommt Events des Typs FocusEvent übergeben. FocusEvent erweitert ComponentEvent und stellt neben getID(), getSource() die Methode public boolean isTemporary() bereit. Sie zeigt an, ob der Fokuswechsel temporär oder permanent ist. Die Registrierung von Focus-Events erfolgt über public void addFocusListener(FocusListener l), die allen Objekten des Typs Component oder daraus abgeleiteten Objekten zur Verfügung steht, Das Interface FocusListener enthält zwei unterschiedliche Methoden: public abstract // Aufruf, wenn public abstract // Aufruf, wenn void focusGained(FocusEvent e) die Komponente den Fokus erhält void focusLost(FocusEvent e) die Komponente den Fokus abgibt Über die Methode public void requestFocus() kann eine Komponente den Fokus für sich selbst beanspruchen bzw. ihn einer anderen Komponenten zuweisen. 146 InputEvent ist Basisklasse von MouseEvent und KeyEvent. Sie stellt Methoden bereit, die allgemeine Informationen über den Zustand der Umschalttasten STRG, ALT, UMSCHALT oder META zum Zeitpunkt des Ereignisses liefern 185 Programmieren in Java Key-Events Ein Empfänger für Key-Events muß das Interface KeyListener implementieren und bekommt Events des Typs KeyEvent übergeben. KeyEvent erweitert die Klasse InputEvent, die aus ComponentEvent abgeleitet ist, und stellt neben getID, getSource eine Reihe von Methoden zur Erkennung und Berabeitung von Tastaturcodes zur Verfügung. Die Registrierung erfolgt mit der Methode public void addKeyListener (KeyListener l), die auf allen Objekten des Typs Component oder daraus abgeleiteter Klassen zur Verfügung steht. Das Interface KeyListener definiert drei unterschiedliche Methoden: public abstract void keyTyped(KeyEvent e); public abstract void keyPressed(KeyEvent e); public abstract void keyReleased(KeyEvent e); Die Taste, die gedrückt wurde, erhält man über die folgenden Methoden der Klasse KeyEvent bereitgestellt: public int getKeyCode() liefert virtuelle Tastencodes, die in KeyEvent als symbolische Konstanten definiert wurden.Hier wird beim Drücken der Taste A immer der Code VK_A geliefert, unabhängig davon, ob UMSCHALT gedrückt wurde oder nicht. Symbolischer Name VK_0..VK_9 VK_A..VK_Z VK_ENTER VK_SPACE VK_TAB VK_ESCAPE VK_BACK_SPACE VK_F1..VK_F12 VK_HOME, VK_END VK_PAGE_UP, VK_PAGE_DOWN VK_DOWN, VK_UP VK_LEFT, VK_RIGHT VK_INSERT, VK_DELETE Bedeutung 0..9 A..Z Enter Leertaste Tabulator Escape Rückschritt Die Funktionstasten F1 .. F12 Home, End Bild hoch, Bild runter Cursor hoch, Cursor runter Cursor links, Cursor rechts Einfg, Entf Abb.: Tabelle der virtuellen Key-Codes public char getKeyChar() liefert das Zeichen, das der gedrückten Zeichentste entspricht, z.B. "a", wenn Taste A gedrückt wurde, und "A", wenn die Tastenkombination UMSCHALT + A gedrückt wurde. Funktionstasten werden nicht übertragen. Der Rückgabewert ist hier KeyEvent.CHAR_UNDEFINED. keyTyped147 keyPressed150 getKeyCode() Zeichentaste148: VK_UNDEFINED Funktionstaste149: Zeichentaste: VK_... Funktionstaste: VK_... 147 getKeyChar() Zeichentaste: Taste als char Funktionstaste: Zeichentaste: Taste als char Funktionstaste: CHAR_UNDEFINED zeigt das Verhalten beim Aufruf der Listener-Methode keyTyped Tasten, mit denen Buchstaben, Ziffern oder sonst. Unicode-Zeichen eingegeben werden, wie z.B. a, A, 1, 2, % aber auch ESC, SPACE, TAB 149 Dazu gehören bspw. F1, F2, Pos 1 aber auch die Umschalttasten: STRG, ALT, UMSCHALT 150 zeigt das Verhalten beim Aufruf von keyPressed 148 186 Programmieren in Java Rückgabecode bei Tastatur-Ereignissen Zusätzlich stehen fogende aus InputEvent geerbten Methoden zur Verfügung: public public public public boolean boolean boolean boolean isShiftDown(); isControlDown(); isMetaDown(); isAltDown(); Bsp.: Das folgende Programm151 schreibt alle möglichen Ereignisse zu Low-LevelEvents auf. import java.awt.*; import java.awt.event.*; public class PR14166 extends Frame { PR14166() { addComponentListener(new CL()); addFocusListener(new FL()); addKeyListener(new KL()); addMouseListener(new ML()); addMouseMotionListener(new MML()); } class CL implements ComponentListener { public void componentMoved(ComponentEvent e) { System.out.println(e.toString()); } public void componentResized(ComponentEvent e) { System.out.println(e.toString()); } public void componentHidden(ComponentEvent e) { System.out.println(e.toString()); } public void componentShown(ComponentEvent e) { System.out.println(e.toString()); } } class FL implements FocusListener { public void focusGained(FocusEvent e) { System.out.println(e.toString()); } public void focusLost(FocusEvent e) { System.out.println(e.toString()); } } class KL implements KeyListener { public void keyPressed(KeyEvent e) { System.out.println(e.toString()); } public void keyReleased(KeyEvent e) { 151 vgl. pr14160 187 Programmieren in Java System.out.println(e.toString()); } public void keyTyped(KeyEvent e) { System.out.println(e.toString()); } } class ML implements MouseListener { public void mouseClicked(MouseEvent e) { // requestFocus(); System.out.println(e.toString()); } public void mousePressed(MouseEvent e) { System.out.println(e.toString()); } public void mouseReleased(MouseEvent e) { System.out.println(e.toString()); } public void mouseEntered(MouseEvent e) { System.out.println(e.toString()); } public void mouseExited(MouseEvent e) { System.out.println(e.toString()); } } class MML implements MouseMotionListener { public void mouseDragged(MouseEvent e) { System.out.println(e.toString()); } public void mouseMoved(MouseEvent e) { System.out.println(e.toString()); } } public static void main(String[] args) { PR14166 f = new PR14166(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(200,200); f.setVisible(true); } } 188 Programmieren in Java 3.1.6 Der Weg eines Ereignisses Jede Ereignisquelle besitzt eine Reihe von Methoden, die für das Aufbereiten und Verteilen der Nachricht zuständig sind. Beim Weiterreichen einer Nachricht wird innerhalb der Nachrichtenquelle die Methode processEvent aufgerufen. Diese verteilt die Nachricht anhand ihres Typs an spezialisierte Methoden, deren Name sich nach dem Typ der zugehörigen Ereignisquelle richtet: protected void processComponentEvent(ComponentEvent e) protected void processFocusEvent(FocusEvent e) protected void processKeyEvent(KeyEvent e) protected void processMouseEvent(MouseEvent e) protected void processMouseMotionEvent(MouseEvent e) protected void processActionEvent(ActionEvent e) Abb.: Spezialisierte Methoden für das Event-Handling "processEvent" bzw. die spezialisierten Methoden für das Event-Handling werden nur aufgerufen, wenn der entsprechende Ereignistyp für diese Ereignisquelle aktiviert wurde. Dies geschieht in folgenden Fällen: - Ein passender Ereignisempfänger wurde über die zugehörige addEventListener-Methode registriert. Ein Ereignistyp wurde explizit durch Aufruf der Methode protected final void enableEvents(long eventsToEnable) aktiviert. Die Methode erwartet eine Maske, die durch eine bitweise Oder-Verknüpfung passender Konstanten aus der Klasse AWTEvent zusammengesetzt werden kann. Folgende Bitmasken akzeptiert diese Methode: ACTION_EVENT_MASK ADJUSTMENT_EVENT_MASK COMPONENT_EVENT_MASK CONTAINER_EVENT_MASK FOCUS_EVENT_MASK ITEM_EVENT_MASK KEY_EVENT_MASK MOUSE_EVENT_MASK MOUSE_MOTION_EVENT_MASK TEXT_EVENT_MASK WINDOW_EVENT_MASK Die Namen dieser Bitmasken verweisen auf die Namen der Ereignisklassen, für deren Objekte sie den Ereignisfilter durchlässig schalten. Sollen mehrere aktiviert werden, dann ist diese Kombination durch Addition bzw. bitweises Oder möglich, z.B.: enableEvents(ACTION_EVENT_MASK + WINDOW_EVENT_MASK) bzw. enableEvents(ACTION_EVENT_MASK | WINDOW_EVENT_MASK) Sobald ein soches Ereignis erzeugt wird, sieht sein Weg dann folgendermaßen aus: 1. Die Methode processEvent der eigenen Klasse erhält das Ereignis 2. Nach der Ereignisverarbeitung wird die Methode der Subklasse aufgerufen 3. Diese prüft den Ereignistyp und ruft dann typspezifische process...Event-Methoden auf, z.B. processActionEvent, processFocusEvent. 4. Diese prüfen, ob Listener registriert sind, und rufen sie gegebenenfalls auf. 5. Nach diesen Aufrufen wird die Methode processEvent der nächsten Superklasse aufgerufen. 189 Programmieren in Java 6. Sie verfährt wieder so, wie unter den Ziffern 3 bis 5 beschrieben. Das wiederholt sich so lange, bis processEvent von ComponentEvent durchlaufen wird. Bsp.: Das folgende Programm schreibt alle möglichen Ereignisse zu Low-LevelEvents auf. import java.awt.*; import java.awt.event.*; public class PR14168 extends Frame { PR14168() { // Bekanntmachen der im Programm zu bearbeitenden Ereignisse enableEvents( AWTEvent.COMPONENT_EVENT_MASK | AWTEvent.FOCUS_EVENT_MASK | AWTEvent.KEY_EVENT_MASK | AWTEvent.MOUSE_EVENT_MASK | AWTEvent.MOUSE_MOTION_EVENT_MASK | AWTEvent.CONTAINER_EVENT_MASK); } public void processEvent(AWTEvent e) { System.out.println(e.toString()); super.processEvent(e); } public static void main(String[] args) { PR14168 f = new PR14168(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(200,200); f.setVisible(true); } } 190 Programmieren in Java 3.2 Kommunikation Anwender – Programm über Dialoge bzw. Menüs 3.2.1 Dialoge Der Anwender verkehrt im Rahmen grafischer Benutzeroberflächen über vom Programm vorgegebene Dialoge. Diese bestehen aus einem Fenster und einer Reihe von Dialogelementen (z.B. Textfelder, Buttons, Listboxen) zur Darstellung und Erfassung programmspezifischer Daten. Das Design der Dialoge wird von Layoutmangern unterstützt, die sich um Größe und Anordnung der einzelnen Dialogelemente kümmern. Erstellen eines Dialogs Dafür sind 4 Schritte nötig: - Anlegen eines Fensters Zuordnen eine Layoutmanagers Einfügen von Dialogelementen Anzeigen des Fensters Anlegen eines Fensters. Ein Dialogfenster kann wahlweise aus der Klasse Frame oder Dialog abgeleitet werden. "Dialog" erlaubt "modale Dialoge152" und verhindert das Verändern der Fenstergröße durch den Anwender. Im Gegensatz zum Frame kann ein "Dialog"-Fenster keine Menüleiste erzeugen und dem Fenster kein Icon153 zuordnen. Zuordnen eines Layoutmanagers. Es wird über die Methode "public void setLayout(LayoutManager mgr)" der Klasse Container realisiert. Java stellt fünf bereit: FlowLayout BorderLayout, GridLayout Layoutmanager154 GridBagLayout und CardLayout. Neben den Fähigkeiten eines Layoutmanagers bestimmt in der Regel die Reihenfolge der Aufrufe von der "add"-Methode des Fensters die tatsächliche Anordnung der Komponenten auf dem Bildschirm. Schachteln von Layoutmanagern. Dazu wird an die Stelle, die das Sublayout erhalten soll, einfach ein Objekt der Klasse Panel eingefügt, das einen eigenen Layoutmanager erhält. Dieses Panel kann mit Dialogelementen bestückt werden, die entsprechend dem zugeordneten Sublayout formatiert werden, z.B.: import java.awt.*; import java.awt.event.*; public class PR14171 extends Frame { public PR14171() { 152 Modale Dialaloge verhindern die Interaktion des Anwenders mit anderen Fenstern der Anwendung bis zum Schließen des Dialogfensters. 153 Falls ein Fenster (unter Windows) minimiert wird, zeigt es ein Icon an. Mit Hilfe eines Doppelklicks auf das Icon kann eine ursprüngliche Größe des Fensters wiederhergestellt werden. Mit Hlife der Methode "public void setIconImage(Image bild)" kann einem Fenster ein Icon zugeordnet werden, das beim Minimieren angezeigt wird. 154 Vgl. 5.4.2 191 Programmieren in Java addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { setVisible(false); dispose(); System.exit(0); } }); // Layout festelegen und Komponenten hinzufuegen int i = 0; Panel p1 = new Panel(); p1.setLayout(new GridLayout(3,1)); p1.add(new Button("Schaltflaeche " + ++i)); p1.add(new Button("Schaltflaeche " + ++i)); p1.add(new Button("Schaltflaeche " + ++i)); Panel p2 = new Panel(); p2.setLayout(new BorderLayout()); p2.add("North",new Button("Schaltflaeche " + ++i)); p2.add("South",new Button("Schaltflaeche " + ++i)); p2.add("West",new Button("Schaltflaeche " + ++i)); p2.add("East",new Button("Schaltflaeche " + ++i)); p2.add("Center",new Button("Schaltflaeche " + ++i)); // Hauptfenster setLayout(new GridLayout(1,2)); add(p1); add(p2); pack(); } public static void main(String args[]) { PR14171 f = new PR14171(); f.setVisible(true); } } Das vorliegende Programm zeigt nach dem Aufruf das folgende Fenster: Abb.: Einfügen von Dialogelementen. Es erfolgt über public Component add(Component komponente) public Component add(Component komponente, int pos) public Component add (String name, Component komponente) // erwartet einen String-Parameter, der bei bestimmten Layout-Managern // (z.B. BorderLayout) Informationen zur Positionierung der Elemente ("bei // BorderLayout: "South", "East", "West", "North", "Center") angibt. der Klasse Container. Mit "public void remove(Component komponente)" können bereits an das Fenster übergebene Komponenten gelöscht werden. Anzeigen eines Dialogfensters. Es erfolgt durch einen Aufruf von "setVisible". Zweckmäßig sollte zuvor "public void pack()" der Klasse Window zur 192 Programmieren in Java Anpassung der Fenstergröße an den für die Darstellung der Dialogelemente erforderlichen Platz ausgeführt werden. Popup-Fenster der Klasse Dialog Die Klasse Dialog155 stellt ein Popup-Fenster bereit und ermöglicht die Erzeugung "modaler" bzw. "nicht modaler" Dialoge. "Modal" bedeutet: Das Dialogfeld blockiert andere Fenster, während es angezeigt wird, z.B.156: import java.awt.*; import java.awt.event.*; public class InfoDialog extends Dialog { protected Button schalter; protected Label label; public InfoDialog(Frame eltern, String titel, String nachricht) { super(eltern,titel,false); this.setLayout(new BorderLayout(15,15)); label = new Label(nachricht,Label.CENTER); this.add("Center",label); schalter = new Button("Okay"); schalter.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { dispose(); setVisible(false); } }); Panel p = new Panel(); p.setLayout(new FlowLayout(FlowLayout.CENTER,15,15)); p.add(schalter); this.add("South",p); this.pack(); } public static void main(String args[]) { Frame f = new Frame("InfoDialog-Test"); f.setSize(300,100); f.setVisible(true); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { // dispose(); // setVisible(false); System.exit(0); } }); InfoDialog dlg = new InfoDialog(f,"Dialog-Demo", "Diese Demo wurde nach einer Vorlage von" + "David Flanagan geschrieben"); dlg.setVisible(true); } } 155 156 vgl. 5.3.4 vgl. pr14170 193 Programmieren in Java Dialogfenster sind Übergangsfenster. Sie dienen dazu, den Benutzer über Ereignisse zu informieren oder Eingabe vom Benutzer anzufordern. Im Gegensatz zu Frames haben Dialogfelder im allg. keine Titelleiste oder Schaltfläche zum Schließen des Fensters. Ein Dialogfenster ist, wie ein Frame, ein Panel, in dem Komponenten der Benutzeroberfläche angeordnet, gezeichnet sowie Grafikoperationen ausgeführt werden können. Mit der Klasse FileDialog kann ein plattformspezifischer Dateidialog erzeugt werden. Dialog FileDialog public static final int LOAD public static final int SAVE << Konstruktor >> public FileDialog(Frame eltern) // erzeugt Datei-Dialog ohne Titel zum Öffnen einer Datei public FileDialog(Frame eltern, String titel) //erzeugt Dialog zum Öffnen einer Datei mit einem Titel im // Fensterrahmen public FileDialog(Frame eltern, String titel, int mode) // erzeugt Dialog mit einem Titel im Fensterrahmen. Da der // Dialog sowohl für Sichern und Laden ausgelegt ist, sind // weder LOAD oder SAVE mit anzugeben. << Methoden >> public String getDirectory() // liefert das Verzeichnis des Dialogs public synchronized void setDirectory(String dir) // setzt das Verzeichnis, das zu Beginn ausgegeben werden soll public String getFile() // liefert die ausgewählte datei des Dialogs public synchronized void setFile(String file) // Datei, die zu Beginn angezeigt wird public FilenameFilter getFilenameFilter() // liefert FilenameFilter.-Objekt zurück public synchronized void setFilenameFilter(FilenameFilter filter) // setzt den FilenameFilter Abb.: Die Klasse FileDialog Vererbungskette. FileDialog leitet sich von Dialog ab. Dialog ist eine Ableitung von Window. Window ist ein Container und dieser wiederum ein Component. Bsp.: Laden und Speichern von Dateien mit Unterstützung durch ein Dateidialogfeld157. import java.awt.*; import java.awt.event.*; public class DateiDialog extends Frame { TextField dateiName = new TextField(); 157 pr14160 194 Programmieren in Java TextField verzeichnis = new TextField(); Button oeffnen = new Button("Oeffnen"); Button sichern = new Button("Sichern"); public DateiDialog() { setTitle("Dateidialog-Test"); Panel p = new Panel(); p.setLayout(new FlowLayout()); oeffnen.addActionListener(new OeffnenL()); p.add(oeffnen); sichern.addActionListener(new SichernL()); p.add(sichern); add(p,BorderLayout.SOUTH); verzeichnis.setEditable(false); dateiName.setEditable(false); p = new Panel(); p.setLayout(new GridLayout(2,1)); p.add(dateiName); p.add(verzeichnis); add(p,BorderLayout.NORTH); } class OeffnenL implements ActionListener { public void actionPerformed(ActionEvent e) { FileDialog d = new FileDialog(DateiDialog.this, "Welche Datei soll geoeffnet werden?"); d.setFile("*.java"); d.setDirectory("."); // Aktuelles Verzeichnis d.show(); String datei = "*.*"; if ((datei = d.getFile()) != null) { dateiName.setText(datei); verzeichnis.setText(d.getDirectory()); } else { dateiName.setText("Cancel wurde gedrueckt!"); verzeichnis.setText(""); } } } class SichernL implements ActionListener { public void actionPerformed(ActionEvent e) { FileDialog d = new FileDialog(DateiDialog.this, "Welche Datei soll gesichert werden?", FileDialog.SAVE); d.setFile("*.java"); d.setDirectory("."); // Aktuelles Verzeichnis d.show(); String datei = "*.*"; if ((datei = d.getFile()) != null) { dateiName.setText(datei); verzeichnis.setText(d.getDirectory()); } else { dateiName.setText("Cancel wurde gedrueckt!"); verzeichnis.setText(""); } } } public static void main(String args[]) { Frame f = new DateiDialog(); 195 Programmieren in Java f.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(250,110); f.setVisible(true); } } Bei Applets ist es vom Browser abhängig, ob Instanzen von FileDialog eingesetzt werden können. Die meisten Browser erzeugen lediglich einen Fehler. Vordefinierte Dialogelemente Jedes Dialogelement wird in Java durch eine eigene Klasse repräsentiert. Zur Aufnahme eines Dialogelements in einen Dialog wird eine neue Instanz der gewünschten Klasse angelegt und das resultierende Element mit "add" in den Dialog eingefügt. Alle Dialogelemente sind aus der Klasse Component abgeleitet. Sie verfügen über die grundlegenden Eigenschaften eines Fensters, besitzen Größe und Position und sind in der Lage, Nachrichten zu empfangen und zu bearbeiten. Component Button TextComponent TextField Container TextArea Menu Panel Applet MenuComponent MenuBar Checkbox MenuItem Window Frame Dialog Abb.: Komponenten des AWT Vordefinierte Dialogelemente unter den Komponenten des AWT sind: Labels158, Schaltflächen(Buttons)159, Kontrollkästchen und Optionsfelder160, Auswahlmenüs161, Listenfelder162, Textbereiche und Textfelder163, Schieberegler164. Das Dialogelement „Canvas“ 158 vgl. 5.2.2 vgl. 5.2.1 160 vgl. 5.2.3 161 vgl. 5.2.4 162 vgl. 5.2.5 163 vgl. 5.2.6 164 vgl. 5.2.7 159 196 Programmieren in Java Die meisten AWT-Komponenten bieten Möglichkeiten für die Ausführung von Zeichenoperationen. Zeichenbereiche sind Komponenten, die nur speziell zum Zeichnen ausgerichtet sind. Zeichenbereiche können keine anderen Komponenten enthalten, akzeptieren aber Ereignisse. Außerdem können sie Animationen und Bilder anzeigen. Zum Erstellen eines Zeichenbereichs wird die Klasse Canvas benutzt. Das eigentliche Zeichnen erfolgt in der Methode paint(Graphics g). Diese Methode wird immer dann aufgerufen, wenn die Zeichenfäche neu gezeichnet werden muß. Typische Situationen, in denen paint(Graphics g) aufgerufen wird, sind: - Der die Zeichenfläche umfassende Rahmen (Frame) wird ertmals erstellt - Die Methode repaint() wird explizit aufgerufen - Die Zeichenfläche wurde teilweise oder ganz von anderen Fenstern überdeckt - Die Größe der Zeichenfläche hat sich verändert Durch Überlagerung von public void paint(Graphics g) sorgt eine CanvasKomponente für die Darstellung auf dem Bildschirm. Der Punkt (0,0) des übergebenen Graphics-Objekts entspricht dabei der linken oberen Ecke des Ausgabebereichs. Bsp.165: import java.awt.*; public class KreuzHaar extends java.applet.Applet { GridLayout g = new GridLayout(1,1); MeinCanvas can = new MeinCanvas(); public void init() { setLayout(g); add(can); } } class MeinCanvas extends java.awt.Canvas { public void paint(Graphics g) { int x = getSize().width / 2; int y = getSize().height / 2; g.setColor(Color.black); g.drawLine(x-10,y,x-2,y); g.drawLine(x+10,y,x+2,y); g.drawLine(x,y-10,x,y-2); g.drawLine(x,y+10,x,y+2); } } Da die Klasse Canvas aus Component abgeleitet ist, bekommt ein Canvas-Objekt alle Ereignisse zugestellt, die auch an eine Komponente gehen. Hierzu zählen: Tastatur-, Maus-, Mausbewegungs-, Fokus- und Komponentenereignisse. 165 pr52900 197 Programmieren in Java 3.2.2 Menüs Jedes Fenster / Frame kann eine eigene Menüleiste besitzen. Jede Menüleiste kann mehrere Menüs enthalten und jedes Menü beliebige Einträge166. Menüs dienen zur Auswahl von Funktionen und Optionen in Anwendungen. Menüs können entweder in der Form von Menubars an der Oberseite von Fenstern oder als Popupmenüs an einer beliebigen Stelle auftreten. Ein Menü ist entweder in einer Menüleiste eines Frame-Objekts eingebettet (Klasse MenuBar) oder erscheint auf Abruf als Kontextmenü (Klasse PopupMenu). Damit sind entweder Untermenüs (Klasse Menu) oder Menüeinträge (Klasse MenuItem) enthalten. Jedes Menü besitzt ein „label“ und ein „aktionKommando“ (ein String, der das Menü beim ActionEvent kennzeichnet und der ausgelöst wird, wenn der Eintrag angeklickt wird). Für Menüs können Shortcuts, also Tastaturkürzel, angegeben werden. Ein MenuItem ist ein Eintrag in einem Menü, der sich bei Selektion einer Option ändert (CheckboxMenuItem) oder ein Kommando absetzt. 166 vgl. 5.3.3 198 Programmieren in Java MenuComponent { abstract } public String getName() public void setName(String name) public MenuContainer getParent() public Font getFont() public void setFont(Font f) public final void dispatchEvent(AWTEvent e) protected void processEvent(AWTEvent e) public String toString() MenuItem MenuBar << Konstruktoren >> public MenuItem() public MenuItem(String label) public MenuItem(String label, MenuShortcut s) << Methoden >> public void addNotify() public String getLabel() public void setLabel(String label) public boolean isEnabled() public void setEnabled(boolean b) public MenuShortcut getShortcut() public void setShortcut(MenuShortcut s) protected final void enableEvents(long eventsToEnable) protected final void disableEvents(long eventsToEnable) public void setActionCommand(String kommando) public String getActionCommand() public void addActionListener(ActionListener l) public void removeActionListener(ActionListener l) protected void processEvent(AWTEvent e) protected processActionEvent(ActionEvent e) ... CheckboxMenuItem << Konstruktor >> public MenuBar() << Methoden >> public void addNotify() public void removeNotify() public Menu getHelpMenu() public void setHelpMenu(Menu m) public Menu add(Menu m) public void remove (MenuComponent m) public int getMenuCount() public Menu getMenu(int i) public Enumeration shortcuts() public MenuItem getShortcutMenuItem (MenuShortcut s) << interface >> MenuContainer Menu << Konstruktoren >> public CheckboxMenuItem() public CheckboxMenuItem(String label) public CheckboxMenuItem(String label, boolean state) << Methoden >> public void addItemListener(ItemListener l) public void addNotify() public boolean getState() protected void processEvent(AWTEvent e) ... << Konstruktoren >> public Menu() public Menu(String label) << Methoden >> public MenuItem add(MenuItem m) public void add(String label) public void addNotify() public MenuItem getItem(int index) public void insert(MenuItem mi, int index) public void insert(String label,int index) ... 199 Programmieren in Java 3.2.3 Popup-Menüs Popup-Menüs sind nicht wie normale Menüs an eine bestimmte Position gebunden, sondern tretem meistens dort auf, wo der Benutzer mit der rechten Maustaste geklickt hat. Für Popup-Menüs ist die Klasse Popup-Menu zuständig. Menüeinträge können mit der Klasse Menu erzeugt werden. Menu PopupMenu << Konstruktor >> public PopupMenu() // erzeugt ein Popup-Menu public PopupMenu(String label) << Methoden >> public void addNotify() public void show(Component origin, int x, int y) // läßt das Popup-Menu auf der Komponenten // origin an der Position x, y aufspringen. Abb.: Die AWT-Menüklasse PopupMenu Bsp.: 167 import java.awt.*; import java.awt.event.*; public class PopMenDemo extends Frame { PopupMenu popmen = new PopupMenu(); public PopMenDemo() { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); popmen.add(new MenuItem("Eintrag 1")); popmen.addSeparator(); popmen.add(new MenuItem("Eintrag 2")); popmen.add(new MenuItem("Eintrag 3")); popmen.add(new MenuItem("Eintrag 4")); add(popmen); popmen.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { System.out.println(e.getActionCommand() + " wurde gedrueckt"); } }); addMouseListener(new MouseAdapter() 167 pr53330, PopMenDemo.java 200 Programmieren in Java { public void mouseClicked(MouseEvent m) { if (m.getModifiers() == m.BUTTON3_MASK) popmen.show(PopMenDemo.this,m.getX(),m.getY()); } }); setSize(300,300); setVisible(true); } public static void main(String args[]) { new PopMenDemo().show(); } } 201 Programmieren in Java 3.3 Grundlagen der Applet-Erstellung 3.3.1 HTML-Grundlagen Die Einbindung von Java-Applets in HTML-Seiten Java-Applets werden innerhalb von HTML-Seiten über Referenzen eingebunden. Referenzen können Überschriften, Texte, Kapitel, Unterkapitel, Absätze, Grafiken bzw. Links zu anderen Seiten sein. HTML ist eine Dokumentationsbeschreibungssprache mit der logische Strukturen eines Dokuments beschrieben werden. Über Klartext (ASCII-Text) gibt ein Dokumentenformat Empfehlungen an eine Darstellungssoftware (Browser) zur Darstellung der Dokumentstruktur. Außerdem wird beschrieben, welche Funktionalität wie auszuführen ist, damit sie dem geplanten Layout und der vorgegebenen Funktionalität entspricht. Es gibt keine verbindliche Darstellungsvorschrift, deshalb kann die Ausführung von HTML-Seiten in verschiedenen Browsern oft unterschiedlich anfallen. Die Basis von HTML Die Sprache HTML baut auf der Sprache SGML auf. HTML ist die Abkürzung für Hypertext Markup Language und wurde aus der in der ISO-Norm 87779:1986 festgeschriebenen Spache SGML (Structured Generalized Markup Language) entwickelt. Ein in HTML geschiebenes Dokument kann außer Text, Grafiken und multimediale Elemente (Sound, Video, usw.) enthalten (Referenz auf Grafik- oder Multimedia-Dateien). Weiterhin können mit gewissen Einschränkungen über HTML Datenbankabfragen formuliert, Resultate optisch aufbereitet und Menüstrukturen aufgebaut werden. Die Normung der HTML realisiert und kontrolliert das World Wide Web Consortium (W3C) mit Sitz in Genf. Ende 1997 kam es zu zur offiziellen Verabschiedung des Standards. HTML-Steueranweisungen Tags. Alle HTML-Steueranweisungen werden in sog. Tags geschrieben, die von spitzen Klammern (< >) begrenzt sind. Man unterscheidet zwischen Einleitungs- und Abschluß-Tags. Die Abschluß-Tags sind beinahe identisch mit dem Einleitungs-Tag. Sie besitzen lediglich zusätzlich einen Slash (/) nach dem „<“-Zeichen. Groß- und Kleinschreibung spielt bei HTML-Steueranweisungen keine Rolle. HTML-Seiten haben zwingend die Extension .htm oder .html. 202 Programmieren in Java Das Grundgerüst einer HTML-Seite Eine HTML-Seite wird immer in die Anweisung <html> am Anfang und </html> am Ende eingeschlossen. Konkrete Referenzierung eines Java Applet Die konkrete Referenzierung eines Java-Applet wird mit dem Applet-Tag <applet ...> eingeleitet. Zwischen dem einleitenden Tag und dem abschließenden AppletTag (</applet>) können benötigte Parameter oder beliebiger Text eingegeben werden. Die einfachste Form der Applet-Referenz. Ohne irgendwelche Parameter wird ein Java-Applet eingebunden mit <APPLET CODE = "klassenelement" WIDTH = Wert HEIGHT = Wert> </APPLET> klassenelement: Applet-Klasse WIDTH = Wert: Breite des Applet in Pixel HEIGHT = Wert: Höhe des Applet in Pixel Das <APPLET>-Tag erzeugt keinen Absatz, deshalb sollte es in einem allgemeinen Text-Tag stehen, z.B. <P> oder in einem Überschriften-Tag (<H1>, <H2> usw.). Optionale Parameter des <Applet>-Tag Parameter CODEBASE ARCHIVE OBJECT ALT NAME ALIGN VSPACE HSPACE Bedeutung Hier kann ein alternatives Verzeichnis168 für das Laden von Klassendateien angegeben werden. Fehlt diese Angabe, wird das Dokumentenverzeichnis genommen. Angabe des JAR-Archivs, aus dem die Klassendateien und sonstige Resourcen des Applet genommen werden Name der Datei, die den serialisierten Inhalt des Applet enthält. Alternativer Text für Browser, die das Applet verstehen, aber Java nicht unterstützen Eindeutiger Name für das Applet. Er kann zur Unterscheidung mehrerer kommunizierender Applets auf einer Web-Seite verwendet werden. Vertikale Anordnung des Applets in einer Textzeile. Hier kann einer der Werte left, right, top, texttop, middle, absmiddle, baseline, bottom, absbottom angegeben werden. Rand über und unter dem Applet Rand links oder rechts vom Applet Neben den Parametern des „Applet-Tag“ können auch Parameter an das Applet selbst übergeben werden. Jeder Parameter kann durch ein <PARAM>-Tag über zwei Attribute für Name (name) und Wert (value) festgelegt werden. Das <PARAM>-Tag steht zwischen einem öffnenden und schließenden <APPLET>-Tag. Die Parameter werden beim Laden an das Applet weitergereicht. Innerhalb des Applets können sie mit der Methode public String getParameter(String name) abgefragt werden. 168 Pfadname, in dem sich die Klassen befinden 203 Programmieren in Java 3.3.2 Die interne Arbeitsweise eines Applets Ein Java-Applet besitzt im Gegensatz zu einer Java-Anwendung keine main()Methode, die beim Laden gestartet wird und das Programm solange am Leben hält, bis es der Benutzer beendet. In einem Applet sorgen vier Methoden dafür, daß sich das Applet in seiner Umgebung korrekt verhält. Panel Applet << Konstruktor >> public Applet() << Methoden >> public void destroy() public AppletContext getAppletContext() public String getAppletInfo() public AudioClip getAudioClip(URL url) public AudioClip getAudioClip(URL url,String name) public URL getCodeBase() public URL getDocumentBase() public Image getImage(URL url) public Image getImage(URL url, String name) public Locale getLocale() public String getParameter(String name) public String[][] getParameterInfo() public void init() public boolean isActive() public void play(URL url) public void play(URL url, String name) public void resize(Dimension d) public void resize(int width, int height) public void setStub(AppletStub stub) public void showStatus(String msg) public void start() public void stop() ….. Abb. Die Klasse Applet 204 Programmieren in Java 1. Das Erstellen eines Applets Zum Erstellen eines Applet muß immer eine „Subklasse“ der Klasse Applet169 erzeugt werden. Java setzt voraus, daß eine Applet-Subklasse public deklariert wurde. Erkennt Java ein Applet auf einer Web-Seite, dann wird die AppletAusgangsklasse und die Hilfsklasse, die diese erste Klasse evtl. benutzt, über das Netz geladen. Java erstellt eine Instanz dieser Klasse, alle systembezogenenen Methoden werden an diese Instanz geschickt. Mehrere Applets auf der gleichen oder auf unterschiedlichen Seiten verwenden andere Instanzen, so daß sich jedes Applet auf dem gleichen System evtl. anders verhält. 2. Applet-Methoden Applets können zahlreiche, unterschiedliche Aktivitäten umfassen, die verschiedenen wichtigen Ereignissen im Lebenszyklus eines Applet entsprechen, z.B. Initialisieren, Zeichnen, Mausereignisse. Jeder Aktivität ist eine entsprechende Methode zugeordnet, d.h.: Falls eine Ereignis stattfindet, ruft der Browser (oder ein Java ähnliches Werkzeug) diese spezifische Methode auf. Zum Reagieren auf solche Ereignisse sind bestimmte Verhaltensweisen vorzusehen. Das geschieht durch Überschreiben der jeweiligen Methode in der Applet-Subklasse. Unterschiedliche Applet-Verhalten bedeutet: Jeweils andere Methoden müssen überschieben werden. Die folgenden Methoden bestimmen den Lebenszyklus eines Applet: Rückkehr zur HTML-Seite init() start() stop() destroy() Verlassen der HTML-Seite Abb.: Methoden im Lebenszklus eines Applet Die Methode init() wird nach dem Laden des Applet ausgeführt. Sie dient zur Initialisierung. Die Methode start() wird automatisch nach Aufruf der Methode init() aufgerufen bzw. dann, wenn das Applet in den Zustand „aktiv“ versetzt wird. Applets können in zwei Zuständen sein: aktiv und inaktiv. Nach dem Laden eines Applets ist dieses zunächst inaktiv. Das Applet wechselt in den Zustand aktiv, wenn es erstmalig auf dem Bildschirm erscheint. Von dort aus wechselt es seinen Zustand zwischen aktiv und inaktiv. Wodurch dieser Zustandswechsel genau ausgelöst wird, ist abhängig vom Kontext des Applel, d.h. in der Regel vom verwendetet Web-Browser. Die Methode stop() wird aufgerufen, wenn die HTML-Seite, in der das Applet eingebunden ist, verlassen wird bzw. das Applet in den Zustand inaktiv versetzt wird. 169 Alle Applets müssen java.applet.Applet in die Datei mit der Definition der Applet-Klasse importieren. java.applet.* vollzieht das Einbunden von java.applet.Applet automatisch. Fast alle Applets (die mit grafischen Schnittstellen) benötigen auch java.awt.* 205 Programmieren in Java Die Methode destroy() zerstört das Applet, nachdem es gestoppt wurde und der Kontext des Applet sich für eine Zerstörung entscheidet. Die Methode destroy() sorgt dafür, daß alle vom Applet belegte Ressourcen wieder freigegeben werden. Vorhandene, vom Applet erzeugte Threads werden ebenfalls zerstört. Die paint()-Methode wird in Java immer aufgerufen, wenn ein Applet gezeichnet werden muß, z.B. bein erstmaligen Zeichnen des Applet, beim Verschieben des Applet-Fenster, beim Überlagern des Applet-Fenster durch ein anderes Fenster. Die paint()-Methode hat folgende Gestalt: public void paint(Graphics g) { .... } paint() besitzt ein Argument: eine Instanz der Klasse Graphics. Dieses Objekt wird vom Browser erstellt und an paint() abgegeben. Es muß sichergestellt sein, daß die Graphics-Klasse170 in den Code der Applet-Subklasse importiert171 wird. Bsp.: „Aller Anfang ist schwer!. Dieser Spruch soll mit Hilfe eines Applet gezeigt werden.. Die zugehörige Quellcodedatei „AllerAnfangApplet.java“ umfaßt172: import java.applet.*; import java.awt.*; public class AllerAnfangApplet extends Applet { Font f; String spruch; // public void init() { f = new Font("Helvetica",Font.BOLD,24); this.spruch = "Aller Anfang ist schwer!"; } // public void paint(Graphics g) { // Oval mit Farbe yellow g.setColor(Color.yellow); g.fillOval(10,10,330,100); // Roter Rahmen; da Java keine Linienbreite kennt, // wird die Linienbreite durrch 4 Ovale, (die sich // um Pixelbreite unterscheiden,) simuliert g.setColor(Color.red); g.drawOval(10,10,330,100); g.drawOval( 9, 9,332,102); g.drawOval( 8, 8,334,104); g.drawOval( 7, 7,336,106); g.setColor(Color.black); g.setFont(f); g.drawString(this.spruch,40,70); } } Die zugehörige HTML-Datei AllerAnfangApplet.html umfaßt: 170 Teil des Pakets java.awt Normalerweise geschieht dies über: import java.awt.Graphics 172 vgl. pr32101 171 206 Programmieren in Java <HTML> <HEAD> <TITEL>Hallo!</TITLE> <BODY> <CENTER> <APPLET CODE="AllerAnfangApplet.class" WIDTH=350 HEIGHT=125> </APPLET> </CENTER> </BODY> </HEAD> </HTML> In einem Browser führt das zu der folgenden Darstellung: Java-Applets zeichnen sich durch Überschreiben der „paint“-Methode selbst. Wie wird die „paint“-Methode aufgerufen? Es gibt drei verschiedene Methoden zum Neuzeichnen eines Applet: public void paint(Graphics g) Sie zeichnet tatsächlich die Grafik des Applets in den Zeichenbereich. Sie wird immer aufgerufen, wenn ein Applet neu gezeichnet173 werden muß. Das in der „paint“-Methode abgebene GraphicsObjekt enthält den Grafikstatus, d.h. die aktuellen Merkmale der Zeichnungsoberfläche. public void repaint() Sie kann jederzeit aufgerufen werden, wann auch immer das Applet neu gezeichnet werden muß. Sie ist der Auslöser, die „paint“-Methode sobald wie möglich aufzurufen und das Applet neu zu zeichnen. Sollten die repaint()-Anweisungen schneller ablaufen, als Java diese verarbeiten kann, werden evtl. einige übersprungen. In vielen Fällen ist die Verzögerung zwischen dem Aufruf von repaint() und der eigentlichen Aktualisierung des Fensters vernachlässigbar. public void update(Graphics g) Sie wird von repaint() aufgerufen. Die „update“-Methode löscht den vollständigen Zeichenbereich174 und ruft anschließend die „paint“-Methode auf, die dann das Applet vollständig neu zeichnet. Der Aufruf der „paint“-Methode erfolgt also nicht direkt über die „repaint“-Methode, sondern indirekt über die „update“-Methode. Applets werden aus Sicherheitsgründen gewissen Einschränkungen unterworfen: 173 Dies ist immer beim ersten Aufruf des Applets der Fall, aber auch jedesmal dann, wenn das Applet-Fenster verschoben oder zwischenzeitlich von einem anderen Fenster überlagert wurde. 174 Das ruft oft den unangenehmen Flimmereffekt bei schnellen Bildsequenzen hervor. 207 Programmieren in Java - - Applets können das Dateisystem des Bernutzers nicht lesen und nicht beschreiben, abgesehen von bestimmten Verzeichnissen (die vom Benutzer durch eine Zugriffskontrolliste, die standardäßig leer ist, bestimmt werden). Einige Browser lassen keinerlei Schreib- und Leseaktion des Applets auf dem Client zu. Applets können auf dem Client keinerlei Programme ausführen. Applets können normalerweise nur mit dem System kommunizieren, auf denen sie gespeichert sind. Applets können keine keine nativen Programme der lokalten Plattform laden, auch keine gemeinsame Bibliotheken (wie DLL’s). 3. Methoden zur Ereignisbehandlung in Applets Ein Applet kann auch auf Ereignisse wie Mausbewegungen reagieren. Für soche Ereignisse (z.B. Drücken der Maustaste) stellt Java Ereignisbehandlungs-Methoden des JDK 1.0 bzw. JDK 1.1 zur Verfügung. Bsp.175: Ein Applet zum Zeichnen von Punkten an den Stellen, an denen eine Maustaste gedrückt wurde. // Import der Pakete import java.awt.*; import java.awt.event.*; // Top-Level Klassen-Deklaration des Applets public class MausDownPunktApplet extends java.applet.Applet { // Variablen-Deklarationen private int mausX, mausY; // private boolean mausKlick = false; // Methoden, die ueberschrieben werden public void init() { setBackground(Color.yellow); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { mausX = e.getX(); mausY = e.getY(); repaint(); } }); } /* public boolean mouseDown(Event e, int x, int y) { mausX = x; mausY = y; mausKlick = true; repaint(); return true; } */ // Ausgabemethode public void paint(Graphics g) { g.setColor(Color.blue); // if (mausKlick) // { g.fillOval(mausX,mausY,20,20); // mausKlick = false; // } 175 pr32305 208 Programmieren in Java } } Nach jedem Mausklick wird ein blauer Punkt in die Zeichenfläche gebracht. Das Bild wird neu gezeichnet. Die repaint()-Methode ruft vor der Ausführung der paint()-Methode die update()-Methode auf, die anschließend paint() aufruft. „update()“ leert in Originalform den Anzeigebereich des Applets. Wird update() überschrieben, z.B. durch public void update(Graphics g) { paint(g); } entfällt das Leeren des Anzeigebereichs. // Import der Pakete import java.awt.*; import java.awt.event.*; // Top-Level Klassen-Deklaration des Applets public class MausDownPunkteApplet extends java.applet.Applet { // Variablen-Deklarationen private int mausX, mausY; // private boolean mausKlick = false; // Methoden, die ueberschrieben werden public void init() { setBackground(Color.yellow); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent e) { mausX = e.getX(); mausY = e.getY(); repaint(); } }); } /* public boolean mouseDown(Event e, int x, int y) { mausX = x; mausY = y; mausKlick = true; repaint(); return true; } */ // Ausgabemethode public void paint(Graphics g) { g.setColor(Color.blue); // if (mausKlick) // { g.fillOval(mausX,mausY,20,20); // mausKlick = false; // } } public void update(Graphics g) { paint(g); } } 209 Programmieren in Java Eine überschriebene update()-Methode kann den Flimmereffekt erheblich senken. 3.3.3 „Multithreading“-fähige Applets Mit Threads können in Java Applets so erstellt werden, daß alle oder auch einzelnen Codeteile in ihrem eigenen Thread laufen, ohne andere Teile des Systems zu beeinflussen. Ein Applet kann im wesentlichen über vier Schritte Multithreading-fähig gemacht werden: 1. Erweitern der Unterschrift des Applets um implements Runnable 2. Hinzufügen einer Instanzvariablen, die den Thread des Applet enthält 3. Reduktion der start()-Methode, so daß sie außer dem Start des Threads keine weiteren Threads enthält 4. Hinzufügen der run()-Methode, die den eigentlichen Code enthält, den das Applet ausführen soll. Bsp.: Ein Applet zur Anzeige von Datum und Uhrzeit, jede Sekunde wird aktualisiert176. Nach den bisher vorliegenden Erkenntnissen müßte das zugehörige Applet folgende Gestalt haben: import java.awt.Graphics; import java.awt.Font; import java.util.Date; // // Top Level Deklaration des Applets // public class DigitalUhr extends java.applet.Applet { // Variablen-Deklaration Font einFont = new Font("TimesRoman",Font.BOLD,24); Date datum; // Eigene Methoden // Methoden, die ueberschrieben werden public void start() { // Ausfuehrung des Applet while (true) { datum = new Date(); repaint(); // Aufruf der repaint()-Methode try {Thread.sleep(1000); } // Pause von 1000 Millisekunden catch(InterruptedException e) {} } } // Optional, aber sehr wahrscheinlich – die Ausgabemethode public void paint(Graphics g) { g.setFont(einFont); // Setzen des aktuellen Font g.drawString(datum.toString(),10,50); // Ausgabe Datum // Da paint() wiederholt mit jeweils dem aktuellen Wert von // „datum“ aufgerufen wird, wird die Zeichenkette jede Sekunde //zur Ausgabe des neuen Datums aufgerufen } } In der start()-Methode nimmt die while-Schleife alle Systemressourcen für sich in Anspruch (einschl. der Anzeige am Bildschirm). Deshalb funktioniert die 176 vgl. pr42001 210 Programmieren in Java digitale Uhr nicht. Außerdem kann das Applet nicht gestoppt werden, da die stop()-Methode nicht aufgerufen werden kann. Die Lösung des Problems liegt im erneuten Schreiben des Applets mit Threads. Das Applet muß dazu mit den vorgegebenen vier Arbeitsschritten erweitert werden. import import import // // Top // public java.awt.Graphics; java.awt.Font; java.util.Date; Level Deklaration des Applets class DigitalThreadUhr extends java.applet.Applet implements Runnable { // Variablen-Deklaration Font einFont = new Font("TimesRoman",Font.BOLD,24); Date datum; Thread faden; // Eigene Methoden // Methoden, die ueberschrieben werden public void start() { if (faden == null) { faden = new Thread(this); faden.start(); } } public void stop() { if (faden != null) { faden.stop(); faden = null; } } public void run() { // Ausfuehrung des Applet, hier findet die Animation statt while (true) { datum = new Date(); repaint(); // Aufruf der repaint()-Methode try {Thread.sleep(1000); } // Pause von 1000 Millisekunden catch(InterruptedException e) {} } } // Optional, aber sehr wahrscheinlich – die Ausgabemethode public void paint(Graphics g) { g.setFont(einFont); // Setzen des aktuellen Font g.drawString(datum.toString(),10,50); // Ausgabe Datum // Da paint() wiederholt mit jeweils dem aktuellen Wert von // "datum" aufgerufen wird, wird die Zeichenkette jede Sekunde // zur Ausgabe des neuen Datums aufgerufen } } Das folgende Gerüst umfaßt ein Muster für „multithreading“-fähige Applets: // Name der Klasse: // Beschreibung: 211 Programmieren in Java // // // // Import import import import der Pakete java.lang.*; java.applet.*; java.awt.*; // Top-Level-Klassen-Deklaration bzw. Definition des Applets public Klassenname extends java.applet.Applet { // Variablen-Deklarationen bzw. Definitionen // ... // Eigene Methoden // ... // Methoden, die ueberschrieben werden // public void init() { // ... } public void start() { // ... } public void stop() { // ... } public void destroy() { // ... } // Optional: die Ausgabemethode public void paint(Graphics g) { // .. } // Bei Multithreading: Verwendung der run-Methode public void run() { // ... } } 212 Programmieren in Java 3.3.4 Animation in Applets Animationsschritte Eine Animation umfaßt in Java zwei Schritte: 1. 2. Aufbau und Ausgabe eines Animationsrahmens (-fenster). Entsprechende häufige Wiederholung der Zeichnung, um den Eindruck von Bewegung zu vermitteln (Abspielen einer Animation). Aufbau eines Animationsrahmens Dazu gehört alles das, was die Animation vorbereitet, z.B.: - Ermitteln der Größe des Ausgabebereichs Positionieren der Animation Erstellen oder Laden der einzelnen Animationsbilder Aufbau von einzelnen Animationssequenzen Abspielen einer Animation Die paint()-Methode wird von Java aufgerufen, wenn ein Applet gezeichnet werden muß. Java kann aber auch aufgefordert werden, ein Bild zu einem bestimmten Zeitpunkt nachzuzeichnen. Tut man das wiederholt und schnell genug mit der repaint()-Methode, dann entsteht eine Animation. repaint() ist eine Anfrage an Java, das Applet so schnell wie möglich zu zeichnen. import java.awt.*; import java.util.*; public class PendelAppl1 extends java.applet.Applet implements Runnable { // Instanzvariable // Position vom Zentrum des schwingenden Pendels int x, y; // double thetaMax = (double) 0.35; double thetaMin = (double) –0.35; // Die initiale Position vom Pendel double theta = (double) 0.; // double wechsel = (double) 0.01; // int xStart = 150, yStart = 20; // Radius des Pendels double r = (double) 200; // Durchmesser des Balls int d = 20; Thread faden; // Methoden public void init() { setBackground(Color.yellow); } public void start() { 213 Programmieren in Java if (faden == null) { faden = new Thread(this); faden.start(); } } public void stop() { if (faden != null) { faden.stop(); faden = null; } } public void run() { while (true) { x = xStart + (int)(r * Math.sin(theta)); y = yStart + (int)(r * Math.cos(theta)); if ((theta >= thetaMax) | (theta <= thetaMin)) wechsel = -wechsel; theta += wechsel; repaint(); try { Thread.sleep(10); } catch(InterruptedException e) {} } } public void paint(Graphics g) { g.setColor(Color.blue); g.drawLine(xStart,yStart,x,y); g.setColor(Color.red); g.fillOval(x-d/2,y-d/2,d,d); } } Reduktion von Flimmereffekten in Animationen Die Methode update() ist die Ursache für das Flimmer-Problem. Da das AppletFenster zwischen den Einzelbildern gelöscht wird, springen die Bereiche des AppletFenster, die sich ändern, kurz zwischen dem Zustand Löschen und Neuzeichnen hin und her, d.h. sie flimmern. Zwei Verfahrensweisen können das Flimmern von JavaApplets einschränken: - Überschreiben der update-Methode so, daß sie entweder den Bildschirm nicht löscht oder nur Teile löscht, die geändert wurden. Überschreiben der Methoden update() und paint(), Verwenden doppelter Pufferung. 1. Überschreiben der update()-Methode177 update() löscht den Bildschirm durch Füllen mit der aktuellen Hintergrundfarbe, setzt die aktuellen Farbe auf die Vordergrundfarbe und ruft anschließend paint() auf. Das Überschreiben von update() muß sicherstellen, daß so etwas Ähnliches geschieht. import java.awt.*; public class PendelAppl2 extends java.applet.Applet implements Runnable { 177 vgl. pr32212 214 Programmieren in Java // Instanzvariable // Position vom Zentrum des schwingenden Pendels int x, y; // double thetaMax = (double) 0.35; double thetaMin = (double) –0.35; // Die initiale Position vom Pendel double theta = (double) 0.; // double wechsel = (double) 0.01; // int xStart = 150, yStart = 20; // Radius des Pendels double r = (double) 200; // Durchmesser des Balls int d = 20; Thread faden; int xAlt, yAlt; // Methoden public void init() { setBackground(Color.white); } public void start() { if (faden == null) { faden = new Thread(this); faden.start(); } } public void stop() { if (faden != null) { faden.stop(); faden = null; } } public void run() { while (true) { x = xStart + (int)(r * Math.sin(theta)); y = yStart + (int)(r * Math.cos(theta)); if ((theta >= thetaMax) | (theta <= thetaMin)) wechsel = -wechsel; theta += wechsel; repaint(); try { Thread.sleep(10); } catch(InterruptedException e) {} } } public void update(Graphics g) { g.setColor(Color.Yellow); g.drawLine(xStart,yStart,xAlt,yAlt); g.fillOval(xAlt-d/2,yAlt-d/2,d,d); g.setColor(Color.blue); g.drawLine(xStart,yStart,x,y); g.setColor(Color.red); g.fillOval(x-d/2,y-d/2,d,d); paint(g); } 215 Programmieren in Java public void paint(Graphics g) { xAlt = x; yAlt = y; } } 2. Double Buffering Mit „double buffering“ wird eine zweite Oberfläche geschaffen, in der alles vorgezeichnet und dann auf einmal in die Zeichnungsoberfläche des Applet ausgegeben wird. Bsp.178: import java.awt.*; public class Pendel extends java.applet.Applet implements Runnable { // Instanzvariable // Position vom Zentrum des schwingenden Pendels int x, y; // double thetaMax = (double) 0.35; double thetaMin = (double) –0.35; // Die initiale Position vom Pendel double theta = (double) 0.; // double wechsel = (double) 0.01; // int xStart = 150, yStart = 20; // Radius des Pendels double r = (double) 200; // Durchmesser des Balls int d = 20; Thread faden; int xAlt, yAlt; Image backgroundImage; Graphics backgroundGraphics; // Methoden public void init() { setBackground(Color.white); backgroundImage = createImage(this.size().width, this.size().height); backgroundGraphics = backgroundImage.getGraphics(); } public void start() { if (faden == null) { faden = new Thread(this); faden.start(); } } public void stop() { if (faden != null) { faden.stop(); faden = null; } } public void run() { 178 pr32213, Pendel.java 216 Programmieren in Java while (true) { x = xStart + (int)(r * Math.sin(theta)); y = yStart + (int)(r * Math.cos(theta)); if ((theta >= thetaMax) | (theta <= thetaMin)) wechsel = -wechsel; theta += wechsel; repaint(); try { Thread.sleep(10); } catch(InterruptedException e) {} } } public void update(Graphics g) { paint(g); } public void paint(Graphics g) { backgroundGraphics.setColor(Color.white); backgroundGraphics.drawLine(xStart,yStart,xAlt,yAlt); backgroundGraphics.fillOval(xAlt-d/2,yAlt-d/2,d,d); backgroundGraphics.setColor(Color.blue); backgroundGraphics.drawLine(xStart,yStart,x,y); backgroundGraphics.setColor(Color.red); backgroundGraphics.fillOval(x-d/2,y-d/2,d,d); g.drawImage(backgroundImage,0,0,this); xAlt = x; yAlt = y; } } 217 Programmieren in Java 3.3.5 Das Laden und Anzeigen von Bildern Den Umgang mit Bildern ermöglicht die Klasse Image des Pakets java.awt. In einem Applet können Methoden der Klassen Applet und Graphics zum Laden und Anzeigen von Bildern herangezogen werden. Bilder werden als seperate Dateien außerhalb der .class-Dateien von Java gespeichert. Falls die Image-Klasse verwendet wird, muß das Bild im Format .GIF oder .JPG vorliegen. Laden von Bildern. Es erfolgt mit der Methode getImage()aus der Applet-Klasse, die mit einem oder zwei Argumenten aufgerufen werden kann: - Aufruf von getImage mit einem Argument (ein Objekt vom Typ URL179) Aufruf mit zwei Argumenten (Basis URL des Bilds (URL-Objekt)) und ein String, der den relativen Pfad oder den Dateinamen des aktuellen Bilds angibt. Die Klasse Applet besitzt zwei Methoden zum Erzeugen einer Basis-URL ohne Angaben fester Adressen im Programm: - die Methode getDocumentBase() gibt ein URL-Objekt zurück, das den Ordner (das Verzeichnis) repräsentiert, die die Webseite mit dem Applet enthält. die Methode getCodeBase() gibt ein Verzeichnis (Ordner) zurück, das das Verzeichnis repräsentiert, in dem sich die .class-Datei der Hauptklasse des Applet befindet. Ausgabe von Bildern. Mit der Methode drawImage() der Graphics-Klasse kann ein Bild, das in ein Image-Objekt geladen wurde, angezeigt werden. drawImage() hat 4 Argumente: - das Image-Objekt, das angezeigt werden soll die x- und y-Koordinate das Schlüsselwort this Mit paint() kann das Bild zur Anzeige gebracht werden: public paint(Graphigs g) { g.drawImage(imageObjekt, xKoord, yKoord, this); } Bsp.180: import java.awt.*; import java.applet.*; public class ZeichneBild extends Applet { private Image bild; public void init() { bild = getImage(getDocumentBase(),"B04240900.jpg"); resize(250, 200); } public void paint(Graphics g) { int xPos = 10; g.drawImage(bild,xPos,10,this); } } 179 Adressen im World Wide Web werden durch URL-Objekte repräsentiert. Die Klasse URL (Uniform Resource Locator) ist Teil des Pakets java.net 180 vgl. pr33501 218 Programmieren in Java 3.3.6 Die Ausgabe von Sound Das JDK bietet Möglichkeiten zur Ausgabe von Sound181 an. Die Ausgabe von Sound kann über zwei Methoden der Klasse Applet erfolgen: public void play(URL url) public void play(URL url, String name) hier kann entweder die URL einer Sound-Datei oder die Kombination von Verzeichnis-URL und Dateinamen angegeben werden. Die Übergabe der URLs geschieht über die Applet-Methoden: public URL getCodeBase() public URL getDocumentbase() Die Methoden liefern eine URL des Verzeichnisses, aus dem das Applet gestartet wurde bzw. in dem die aktuelle HTML-Seite liegt. Der nachteil dieser Vorgehensweise ist, daß die Sound-datei bei jedem Aufruf neu geladen werden muß. public getAudioClip(URL url, String name) Hier wird ein Objekt der Klasse AudioClip beschafft, das dann abgespielt werden kann. AudioClip stellt drei Methoden zur Verfügung: public void play() startet die zuvor geladene Sound-Datei und spielt sie genau einmal ab. public void loop() startet die zuvor geladene Sound-Datei und spielt den Sound in einer Endlosschleife immer wieder ab. public void stop() Darüber kann die zuvor mit loop() iniziierte Schleife beendet werden. Bsp.182: import java.net.*; import java.applet.*; public class PR33602 { public static void main(String args[]) { if (args.length >= 1) { try { URL url = new URL(args[0]); AudioClip clip = Applet.newAudioClip(url); clip.play(); try { Thread.sleep(10000); } catch (InterruptedException e) {} } 181 Das JDK 1.2 ermöglicht die Soundausgabe in Applets und Applikationen. Frühere Versionen gestatten Soundausgabe nur für Applets. Die Ausgabe war auf Sound beschränkt, die im AU-Format (stammt aus der Sun-Welt und legt ein Sample im Format 8 Bit Mono, Sampling-Rate 8 kHz, µ -lawKompression ab) vorliegen mußte. Seit dem JDK 1.2 werden auch die Sample-Formate WAV und AIFF sowie die Midi-Formate Typ 0 und Typ 1 und RMF unterstützt. Zudem gibt es einige Shareware- oder Freeware-Tools, die zwischen verschieden Formaten konvertieren können (z.B. CoolEdit oder Gold-Wave). 182 Vgl. pr33602, Aufruf z.B.: java PR33602 file:///c:/winnt\media\ringin.wav 219 Programmieren in Java catch (MalformedURLException e) { System.out.println(e.toString()); } } } } 3.3.7 Die Klasse JApplet Diese Klasse befindet sich im Paket javax.swing, gehört aber logisch zu den Applets. Sie erweitert die Applets um typische Swing-Eigenschaften. java.awt.Panel java.applet.Applet javax.swing.JApplet << Konstruktor >> public JApplet() << Methoden >> public void setJMenuBar(JMenuBar menuBar) public JMenuBar getJMenuBar() public void setLayout(LayoutManager manager) public Container getContentPane() public void setContentPane(Container contentPane) .... Abb. Die Klasse JApplet 220 Programmieren in Java 3.3.8 Das Interface AppletContext Die Referenz vom Typ des Interface AppletContext dient vorwiegend zur Kommunikation zwischen Applet und dem Webbrowser bzw. anderen Applets. << interface >> AppletContext public Applet getApplet(String name) public Enumeration getApplets() public AudioClip getAudioClip(URL url) public Image getImage(URL url) public void showDocument(URL url) public void showDocument(URL url, String target) public void showStatus(String status) Abb.: Das Interface AppletContext Mit Hilfe von z.B. getAppletContext().getApplets() kann auf andere Applets Bezug genommen werden. Die Methode showDocument() lädt eine Webseite in den Browser, auf dem das Applet läuft. 221 Programmieren in Java 3.4 Grafische Benutzeroberfläche mit Observable-Observer Das Zusammenwirken und die Kommunikation zwischen Awendung und grafischer Benutzeroberfläche sollte folgendermaßen gestaltet sein: - Anwendung und grafische Benutzeroberfläche sollen möglichst unabhängig voneinander sein, in jedem Fall aber sauber voneinander getrennt sein (Anwendungsklasse, GUI-Klasse). Jedes GUI-Objekt muß seinen Anwendungsfall kennen, um desssen Methoden aufrufen zu können Eigentlich müßte umgekehrt jedes Anwendungsobjekt „sein GUI-Objekt“ kennen, z.B. zur Anzeige der Anfangswerte Das Hauptprogramm „degeneriert“ zur Erzeugung eines Anwendungs- und GUI-Objekts. Die Klasse Observable In Java muß das Interesse an einem Objekt durch eine eigene Klasse ausgedrückt werden. Die eigene Klasse muß von der Klasse Observable abgeleitet sein. Die Observable-Klasse erlaubt es einem Objekt, andere Objekte zu informieren, wenn es eine Änderung erfährt. public class java.util.Observable { Observable(); public void addObserver(Observer o); public void deleteObserver(Observer o); public void deleteObservers(); public int countObservers); protected void setChanged(); portected void clearChanged(); public boolean hasChanged(); public void notifyObservers(); public void notifyObservers(Object arg); } Methoden: Die wichtigsten Methoden beim Erzeugen einer Subklasse von Observable sind „setChanged“ und „notifyObservers“. Die „setChanged“Methode markiert, daß Observable verändert wurde. Beim Aufruf von „notifyObservers“ werden die Observer benachrichtigt. protected void setChanged() setzt ein internes Flag für die Modifikation (wird von „notifyObservers“ verwendet). Es wird automatisch gelöscht, wenn „notifyObservers“ aufgerufen wird, kann aber auch manuell mit der „clearChanged“-Methode gelöscht werden. protected void clearChanged() public void notifyObservers() prüft, ob das changed-Flag gesetzt wurde. Ist dies nicht der Fall, kann keine Mitteilung über eine Änderung versendet werden. public void notifyObservers(Object arg) Das Argument kann zur Übergabe zusätzlicher Information über die Modifikation dienen. Ohne Parameter entspricht der Aufruf einem Aufruf mit dem Argument Null. public boolean hasChanged() stellt fest, ob ein Objekt von Interesse modifiziert wurde. public void addObserver(Observer obs) 222 Programmieren in Java Mit dieser Methode können Observer ihr Interesse an einem beliebigen Observable bekunden. public void deleteObserver (Observer obs) Mit dieser Methode kann ein Observer die Überwachung der entsprechenden Observables beenden public void deleteObservers() löscht die Liste aller Observer public int countObservers() liefert die Anzahl der für in Observable registrierten Observer Das Interface Observer Jede Klasse, die Mitteilungen über Modifikationen eines Observable bekommen möchte, muß das Observer-Interface implementieren. Dieses Interface besteht aus einer einzigen Methode: public abstract void update(Observable obs, Object arg) Sie wird bei einer Objektmodifikation aufgerufen. „obs“ ist das Observable, das soeben geändert wurde. Wurde „notifyObservers“ ohne Argument aufgerufen, dann ist arg Null. Observable-Observer: Kopplung von Anwendung und GUI Die Klasse Observable183 erlaubt einem Objekt, Informationen an andere Objekte zu geben, wenn es eine Änderung erfährt. Beim Erstellen von Benutzeroberflächen kann es verschiedene Möglichkeiten zur Änderung eines Datenobjekts geben. Eine solche Änderung kann wiederum die Aktualisierung verschiedener Teile einer Anzeige zur Folge haben. Bsp.:184 import java.awt.*; import java.awt.event.*; import java.util.*; class BoxObservable extends Observable { public void notifyObservers(Object b) { setChanged(); super.notifyObservers(b); } } public class BoxObserver extends Frame { Observable notifier = new BoxObservable(); public BoxObserver(int grid) { setTitle("Demonstration Observer"); setLayout(new GridLayout(grid,grid)); for (int x = 0; x < grid; x++) for (int y = 0; y < grid; y++) add(new OCBox(x, y, notifier)); } public static void main(String args[]) 183 Das Konzept für diese Klasse ist eine Leihgabe von Smalltalk und unter dem namen Model-View-Controller (MVC) bekannt. 184 pr34100 223 Programmieren in Java { int grid = 8; if (args.length > 0) grid = Integer.parseInt(args[0]); Frame f = new BoxObserver(grid); f.setSize(500,400); f.setVisible(true); f.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); } } class OCBox extends Canvas implements Observer { Observable notifier; int x, y; Color fFarbe = neueFarbe(); static final Color[] farben = { Color.black, Color.blue, Color.cyan, Color.darkGray, Color.gray, Color.green, Color.lightGray, Color.magenta, Color.orange, Color.pink, Color.red, Color.white, Color.yellow }; OCBox(int x, int y, Observable notifier) { this.x = x; this.y = y; notifier.addObserver(this); this.notifier = notifier; addMouseListener(new ML()); } static final Color neueFarbe() { return farben[(int) (Math.random() * farben.length)]; } public void paint(Graphics g) { g.setColor(fFarbe); Dimension d = getSize(); g.fillRect(0, 0, d.width, d.height); } class ML extends MouseAdapter { public void mousePressed(MouseEvent e) { notifier.notifyObservers(OCBox.this); } } public void update(Observable o, Object arg) { OCBox clicked = (OCBox) arg; if (nextTo(clicked)) { fFarbe = clicked.fFarbe; repaint(); } } private final boolean nextTo(OCBox b) { return Math.abs(x - b.x) <= 1 && Math.abs(y - b.y) <= 1; } } 224 Programmieren in Java 3.5 Swing Die grundlegende GUI-Programmierung in Java wird durch die Klassenbibliothek AWT (Abstrach Window Toolkit) ermöglicht. Dabei werden in Wirklichkeit die graphischen Elemente nicht durch die AWT komplett realisiert, sondern die Erzeugung wird im wesentlichen an die jeweilige Plattform (Windows, Solaris, Mac) delegiert. Das im Hintergrund erzeugte und arbeitende Element wird dann als „Peer“Komponente bezeichnet. Die Klassenbibliothek kapselt den Zugriff auf diese Elemente und erleichtert damit auch teilweise die Handhabung einzelner Objekte. 1996 erstellte Netscape eine GUI-Bibliothek „Internet Foundation Classes“ mit komplett anderem Einsatz. Die Peer-Funktionalität wird auf ein Minimum reduziert: Ein Fenster zu erstellen, darauf zu zeichnen und dieses wieder zu schliessen. Dadurch wird eine gemeinsame Basis für alle Elemente der Benutzeroberfläche geschaffen, auf der aufbauend alle GUI-Elemente wie Schaltflächen, Listen, Menüs usw. gezeichnet werden konnten. Die komplette Funktionalität muß dann für alle Komponenten „nachgebaut“ werden, mit dem Vorteil einer einheitlchen, plattformübergreifenden Lösung. Diese Lösung wurde gemeinsam von Netscape und Sun ausgebaut und verbessert, wodurch eine nicht peerbasierte GUI-Bibliothek als Teil der Java Foundation Classes (JFC) für die Benutzeroberfläche mit dem Namen Swing entstanden ist. Swing185 ist eine Erweiterung des Abstract Windowing Toolkit, vollständig in Java 2 integriert und bietet eine wesentlich verbesserte Funktionalität. Alle Elemente von Swing sind Bestandteile des Pakets javax.swing, die über import javax.swing.* in Anwendungen einbezogen werden kann. Die grafische Benutzeroberfläche für Java Programme "Swing" umfasst die folgenden drei wesentlichen Konzepte: - Komponenten (Schnittstellenelemente wie z.B. Buttons, Menüs, ...). Swing-Komponenten werden auf gleiche Weise verwendet wie die Komponenten des AWT: Erzeugen einer Komponente über den Konstruktor, Aufruf der Methoden der Komponente. - Container (Behälter, die mit Komponenten gefüllt werden, z.B. Fenster, Dialoge, Panels,...). - Events (Nachrichten über Benutzeraktionen mit Maus, Tastatur bzw. Veränderung von Komponenten oder Systemstatus (Zeitintervall vergangen, Netzwerkanfragen, ...). 185 vgl. 5.6 225 Programmieren in Java 3.6 Java Beans 3.6.1 Wiederverwendbare Softwarekomponenten Unter einer Software-Komponente186 versteht man: Eine Komponente ist ein Stück Software, das klein genug ist, um es in einem Stück zu erzeugen und pflegen zu können, groß genug ist, um eine sinnvoll einsetzbare Funktionalität zu bieten und eine individuelle Unterstützung zu rechtfertigen, sowie mit standardisierten Schnittstelle ausgestattet ist, um mit anderen Komponenten zusammen zu arbeiten. Zu den grundlegenden Eigenschaften einer Softwarekomponente zählen: - eine Softwarekomponente ist ein ausführbares Programm - sie realisiert eine klar abgegrenzte Programmfunktionalität (z.B. ein GUI-Control oder ein Berechnungsmodul) - ihre Implementierung ist nach außen nicht transparent (black-box-Modell) - sie verfügt über öffentliche Schnittstellen, über die auf die Funktionalität der Komponente zugegriffen werden kann (Eigenschaften, Methoden, Ereignisse). - eine Softwarekomponente lässt sich in umfangreichere Programme integrieren - im Unterschied zu Objekten sind Komponenten auf eine abstrakten Ebene angesiedelt und nur lose miteinandet verkoppelt. Das soll vor allem die einfache Wiederverwendbarkeit von Komponenten gewährleisten Bisher gibt es keine einheitliche Entwicklungsmethode für component ware. Es existieren eine Reihe konkurrierender Komponentenarchitekturen, u.a. - Microsoft ActiveX/COM/DCOM als Weiterentwicklung von OLE(object linking and embedding) - Java Beans - CORBA Business Objects Sie sind z.T. untereinander interoperable, d.h. ein Java Beans kann als CORBAfähiges Objekt ausgestattet sein oder im Rahmen von ActiveX eingesetzt werden (über eine ActiveX-Bridge, die das Bean als Actve-X-Komponente verpackt). Für die Komponentenentwicklung von einzelnen Beans stehen folgende Funktionalitäten bereit: - Eigenschaften (properties) und Anpassung (customization) von Komponenten - Ereignisverarbeitung, um unterschiedliche Komponenten miteinander zu verknüpfen und sie kommunizieren zu lassen - Introspektion (introspection), so dass ein Entwicklungswerkzeug erkennen kann, wie ein Bean arbeitet. - Persistenz und Verpacken von Komponenten, so dass in einem Entwicklungswerkzeug angepasstes bzw. modifiziertes Bean in einem neuen Zustand gespeichert werden kann. Die wichtigsten Merkmale eines Beans sind: - seine Eigenschaften (properties) - seine Methoden, die von anderen Komponenten aufgerufen werden können. - Ereignisse / Nachrichen, die es auslöst. 186 Die Definition wurde zitiert nach: Griffel, Frank: Componentenware – Konzepte und Techniken eines Softwareparadigma, dpunkt-Verlag, 1998 226 Programmieren in Java 3.6.2 Komponenten für Java: Beans JavaBeans ist das Komponentenmodell von Java. Diese Komponenten haben eine genauere und für Programmierwerkzeuge verständlichere Schnittstellendefintion als Java-Klassen. Die zur Beschreibung und Analyse benötigten Interfaces und Klassen stehen im Package java.beans. Sie arbeiten sehr eng mit den Klassen zur Laufzeitanalyse von Java-Klassen im Paket java.lang.reflect zusammen. Die Schnittstellen von JavaBeans bezeichnet man als Features. Sie besteht aus drei Teilen: JavaBeans Properties Methoden Ereignisse Abb.: Die JavaBean-Features 1. Properties sind die öffentlichen, mit Namen versehenen Attribute oder Eigenschaften. Über diese Properties sind Beans an die Vorstellungen des Programmierers anpassbar, ohne daß er er den Quellcode ändern und darauf irgendein Zugriff haben muß. Die Implementierung von Properties erfolgt durch entsprechende lesende und schreibende Methoden, die einer Namenskonvention („Design-Pattern“187) gehorchen. Die Java-Beans-Architektur unterscheidet zwischen sog. Indexed, Bound und Constraint Properties. 2. Die öffentlichen Methoden, die eine Bean ausführen kann 3. Ein JavaBean erzeugt Ereignisse, z.B. dann, wenn sich ihre Properties ändern. Diese drei Arten von Features sind im Interface BeanInfo dokumentiert. Das Paket java.beans enthält folgende Klassen, Schnittstellen und Ausnahmen: AppletInitalizer BeanInfo Customizer DesignMode PropertyChangeListener PropertyEditor VetoableChangeListener Visibility Methoden für die Initialisierung von Beans, die auch Applets sind Methoden für die Beschreibung eines Beans, z.B. getBeanDescriptor() Schnittstelle für Klassen, die der Anpassung von Beans dienen (Bean-Editoren, Bean-Customizer) Methoden, die feststellen, ob sich ein Bean in einer Design- oder Lauffzeitumgebung befindet Lauscherschnittstelle, die Eigenschaftsänderungsnachrichten abfängt Methoden für Klassen, die das Editieren von Beans bzw. ihrer Eigenschaften erlauben. Lauscherschnittstelle für sog. "constraint properties", d.h. Eigenschaften deren Änderung von "externen Faktoren" abhängt, d.h. gegen deren Änderung von anderen Komponenten ein Veto eingelegt werden kann. Methoden zur Feststellung, ob ein Bean in einem GUI ausgeführt wird. Abb.: Schnittstellen in java.beans 187 - Unter Design Pattern sind einfache Muster auf der Basis von Methodennamen und Signaturen gemeint, z.B. Ein Property mit dem Namen Color, für das sowohl schreibende als auch lesende Zugriffe erlaubt sind wird durch Implementierung der Methoden „public Color getColor()“ und „public void setColor()“ dargestellt. Das Entwurfsmuster für das Verbinden eines Event-Handlers mit einer Event-Source besteht aus einer Methode, die mit „set“ beginnt und mit „Listener“ endet und ein Argument besitzt. Außerdem muß eine Vererbung vom EventListener-Interface vorhanden sein. 227 Programmieren in Java BeanDescriptor Beans EventSetDescriptor FeatureDescriptor Introspector MethodDescriptor ParameterDescriptor PropertyChangeEvent PropertyChangeSupport PropertyDescriptor PropertyEditorManager PropertyEditorSupport SimpleBeanInfo VetoableChangeSupport Klasse für den Zugriff auf allgemeine Eigenschaften einer Komponente Hilfsklasse mit nützlichen Methoden um Informationen über ein Bean zu erhalten (isDesignTime(), isGuiAvailable(), etc.) beschreibt Ereignistypen, die von einem Bean ausgelöst werden. Superklasse aller Deskriptor-Klassen für Beans mit generischen Informationsmethoden (z.B. getNames(), attributeNames(), getValues(), etc.). Zugriff auf Beaneigenschaften zur Design-Zeit durch Methoden wie z.B. getBeanInfo(). beschrebt eine Mrethode eines Java Bean, die von außen zugänglich ist zusätzliche Information über Parameter (d.h. mehr als das, was man über die reflection-Methoden ermitteln kann Nachrichtenklasse für die Verarbeitung von Eigenschaftsänderungsachrichten Hilfsklasse für die Verarbeitung von Eigenschaftsänderungsnachrichten beschreibt die Eigenschaft eines Bean, die von außen zugänglich ist. ist beim Aufbau eines geeigneten Editor für ein Bean behilflich Hilfsklasse für den Aufbau von Editoren Hilfsklasse für die Generierung bon BeanInfo-Objekten Hilfsklasse für die Verarbeitung von Eigenschaften, gegen die ein Veto möglich ist. Abb.: Klassen in java.beans IntrospectionException PropertyVetoException tritt bei Fehlern während der Introspektion eines Beans auf tritt auf, falls eine Eigenschaft eines Bean auf einen nicht akzeptablen Wert geändert werden soll Abb.: Ausnahmen in java.beans 3.6.3 Enterprise Java Beans „Java Beans“ wurde als ein lokales Komponentenmodell188 entworfen. Das JavaBeans-Modell sieht aber auch die Nutzung entfernter Ressourcen vor: Enterprise Java Beans. Damit werden Anbindungen zur folgenden Application Interfaces möglich: - Java Database Connectivity -API: Es gestattet den Zugriff auf relationale Datenbanken. - Remote Method Invocation: RMI stellt ein API zur Verfügung, mit dessen Hilfe die Kommunikation zweier Komponenten über Adreß- oder Maschinenräume hinweg möglich ist. RMI ist eine Art objektorientierter RPC-Mechanismus (Remote Procedure Call), der speziell für Java entwickelt wurde. - CORBA 188D.h.: Die Kommunikation zwischen Komponenten findet auf einer Maschine statt. 228 Programmieren in Java 4. Grafik und Bildverarbeitung Mit einer Instanz von Graphics kann gezeichnet werden, z.B.: Graphics meineGrafik; meineGrafik = getGraphics(); meineGrafik.drawString("mache irgendwas",20,40); Die in der Klasse Component als public Graphics getGraphics() definierte Methode gibt den „Graphics“-Kontext von der Komponente zurück bzw. Null, wenn die Komponente keinen aktuellen Grafikbezug hat. Bei jeder Zeichenoperation muß der grafische Kontext angegeben werden, denn dieses Objekt verwaltet: - die Komponente, auf der zu zeichnen ist. - Koordinaten des Bildbereichs und des Clipping-Bereichs. - der aktuelle Clip-Bereich und Font, die aktuelle Farbe - die Pixeloperation (XOR oder Paint) - Composite (Überschreibmodus/Alphakanal) Die meisten Zeichenvorgänge werden in der paint()-Methode durchgeführt. JavaApplets zeichnen sich selbst neu nach Überschreiben der paint()-Methode. Es gibt drei verschiedene Methoden189 zum Neuzeichnen: public void paint(Graphics g) public void repaint() public void update(Graphics g) Ein Graphics-Objekt erzeugt nicht der Entwickler, sondern er bekommt den Kontext gestellt, auf dem er zeichnen möchte. Die Klasse Graphics hat keinen öffentlichen Konstruktor. 189 vgl. 3.2.2 229 Programmieren in Java Graphics { abstract } ......... << Konstruktor >> protected Graphics() << Methoden >> public abstract Graphics create() public Graphics create(int x, int y, int width, int height) public abstract void translate(int x, int y) public abstract Color getColor() public abstract void setColor(Color farbe) public abstract void setXORMode(Color farbe) public abstract Font getFont() public abstract void setFont(Font f) public FontMetrics getFontMetrics() public abstract Rectangle getClipBounds() public abstract void clipRect(int x, int y, int width, int height) public abstract void setClip(int x, int y, int width, int height) public abstract Shape getClip() public abstract void drawLine(int x1, int y1, int x2, int y2) public abstract fillRect(int x, int y, int width, int height) public void drawRect(int x, int y, int width, int height) public abstract void clearRect(int x, int y, int width, int height) public abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) public abstract fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight) public abstract void drawOval(int x, int y, int width, int height) public abstract void fillOval(int x, int y, int width, int height) public abstract void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle) public abstract void drawString(String str, int x, int y) public abstract boolean drawImage(Image bild, int x, int y, ImageObserver observer) Abb.: Die Klasse Graphics 4.1 Allgemeine Zeichenvorgänge 4.1.1 Punkte, Linien, Kreise, Bögen, Polygone Das Koordinatensystem. Der Ausgangspunkt (0,0) des Java-Koordinatensystems ist die obere linke Ecke. Von dieser Stelle führen positive x-Werte nach rechts und positive y-Werte nach unten. Die Angaben der Koordinaten erfolgen in Pixel. Alle Pixelwerte sind Ganzzahlen. Punkte. Ein Punkt ist durch zwei oder mehr Koordinaten gekennzeichnet. In Java gibt es keine Funktion, mit der Punkte gezeichnet werden. Diese können nur durch Liniebefehle erzeugt werden. Zeichnen einer Linie. Es geschieht mit der Methode: public abstract void drawLine(int x1, int y1, int x2, int y2). (x1,y1) bestimmt den Anfangspunkt, (x2,y2) bestimmt den Endpunkt der Linie. 230 Programmieren in Java Bsp.: Ein Applet mit zufällig verteilten Linien190 import java.awt.*; // Top Level Deklaration des Applets public class LinienApplet extends java.applet.Applet implements Runnable { // Variablen-Deklaration int x1 = 0; int x2 = 0; int y1 = 0; int y2 = 0; float rot, gruen, blau; Color linienFarbe; // Eigene Methoden // Methoden, die ueberschrieben werden public void init() { setBackground(Color.lightGray); } public void start() { if (faden == null) { faden = new Thread(this); faden.start(); } } public void stop() { if (faden != null) { faden.stop(); faden = null; } } public void run() { // Ausfuehrung des Applet while (true) { x1 = (int) (Math.random() * this.size().width); x2 = (int) (Math.random() * this.size().width); y1 = (int) (Math.random() * this.size().height); y2 = (int) (Math.random() * this.size().height); rot = (float) Math.random(); gruen = (float) Math.random(); blau = (float) Math.random(); linienFarbe = new Color(rot,gruen,blau); repaint(); // Aufruf der repaint()-Methode try {faden.sleep(1000); } // Pause von 1000 Millisekunden catch(InterruptedException e) {} } } public void paint(Graphics g) { g.setColor(linienFarbe); g.drawLine(x1,y1,x2,y2); } } Die Methode drawLine zeichnet Linien mit einer Dicke von einem Pixel. 190 vgl. pr42005 231 Programmieren in Java Zeichnen eines Rechtecks. Dafür gibt es die Methode: public void drawRect(int x, int y, int width, int height). (x1,y1) bestimmt die obere linke Ecke eines Rechtecks, (width, height) legen Breite und Höhe des Rechtecks fest. Zeichnen eines gefüllten Rechtecks. Es wird ermöglicht durch die Methode: public abstract void fillRect(int x, int y, int width, int height). Die Farbe, mit der das Rechteck gefüllt werden soll, kann mit der folgenden Methode gesetzt werden: public abstract void setColor(Color c). Bsp.: Ein Applet mit zufällig verteilten Rechtecken191 // zeichne Rechtecke import java.applet.*; import java.awt.*; public class RechteckeAppl3 extends Applet { // Instanzvariable int appletHoehe; int appletBreite; int rechteckHoehe; int rechteckBreite; int rechteckTop; int rechteckLinks; Color rechteckFarbe; int anzRechtecke = 100; // Methoden public void init() { Dimension d = size(); appletHoehe = d.height; appletBreite = d.width; repaint(); } public void paint(Graphics g) { setBackground(Color.white); g.setColor(Color.black); g.drawRect(0,0,appletBreite – 1,appletHoehe – 1); for (int i = 0; i < anzRechtecke; i++) { rechteckTop = bestimmeZufallszahl(appletHoehe); rechteckLinks = bestimmeZufallszahl(appletBreite); rechteckHoehe = bestimmeZufallszahl( appletHoehe - rechteckTop); rechteckBreite = bestimmeZufallszahl( appletBreite – rechteckLinks); rechteckFarbe = new Color(bestimmeZufallszahl(255), bestimmeZufallszahl(255), bestimmeZufallszahl(255)); g.setColor(rechteckFarbe); g.fillRect(rechteckLinks,rechteckTop,rechteckBreite-1, rechteckHoehe – 1); } } private int bestimmeZufallszahl(int bereich) { double ausgangsgroesse; ausgangsgroesse = Math.random(); return (int) (ausgangsgroesse * bereich); } } 191 vgl. pr41103 232 Programmieren in Java Löschen eines Rechtecks. Das übernimmt die Methode public abstract void clearRect(int x, int y, int width, int height). Kopieren eines Rechtecks. Dafür gibt es die Methode public abstract void copyArea(int x, int y, int width, int height, int dx, int dy). Zeichnen eines 3D-Rechtecks. Es erfolgt mit Hilfe der Methode public void draw3DRect(int x, int y, int width, int height, boolean raised). Zeichnen eines gefüllten 3D-Rechtecks. Zeichnen abgerundeter Rechtecke. Sie können gezeichnet werden mit public abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight). „arcWidth“ bestimmt den Winkel der Abrundung auf der horizontalen, „arcHeight“ den Winkel auf der vertikalen Ebene. Je größer die Winkel sind, desto stärker gerundet erscheint das Rechteck. Zeichnen abgerundeter, gefüllter Rechtecke. Dafür existiert die Methode public abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight). Zeichnen von Polygonen. Hierfür kann die Methode public abstract void drawPolygon(int[] xPunkte, int[] yPunkte, int nPunkte) herangezogen werden. Es gibt zwei Möglichkeiten beim Zeichnen von Polygonen: - Weitergabe der beiden Datenbereiche (Arrays) mit den x- und y-Koordinaten der Punkte, z.B.192: import java.awt.*; public class ZeichnePolyAppl1 extends java.applet.Applet { // Instanzvariable // Definition des Felds mit den x-Koordinaten int xKoord[] = {20,50,70,40,20,20}; // Definition des Felds mit den y-Koordinaten int yKoord[] = {30,10,20,70,50,30}; // Methoden public void init() { setBackground(Color.yellow); } public void paint(Graphics g) { // Zeichne ein 5-Eck g.setColor(Color.red); g.drawPolygon(xKoord,yKoord,6); } } - Weitergabe einer Instanz der Polygon-Klasse, z.B.193: import java.awt.*; public class ZeichnePolyAppl2 extends java.applet.Applet { // Instanzvariable // Definition des Felds mit den x-Koordinaten int xKoord[] = {20,50,70,40,20,20}; // Definition des Felds mit den y-Koordinaten int yKoord[] = {30,10,20,70,50,30}; // Anzahl Ecken int anzEcken = xKoord.length; // Methoden public void init() { setBackground(Color.yellow); } public void paint(Graphics g) 192 193 pr41105 pr41105 233 Programmieren in Java { // Zeichne ein 5-Eck g.setColor(Color.red); Polygon poly = new Polygon(xKoord,yKoord, anzEcken); g.drawPolygon(poly); } } Das Zeichnen von gefüllten Polygonen. Dazu dient die Methode public abstract void fillPolygon(int[] xPunkte, int[] yPunkte, int nPunkte). Bsp.194: import java.awt.*; public class ZeichnePolyAppl3 extends java.applet.Applet { // Instanzvariable // Definition des Felds mit den x-Koordinaten int xKoord[] = {20,50,70,40,20}; // Definition des Felds mit den y-Koordinaten int yKoord[] = {30,10,20,70,50}; // Methoden public void init() { setBackground(Color.yellow); } public void paint(Graphics g) { // Zeichne ein 5-Eck g.setColor(Color.red); g.fillPolygon(xKoord,yKoord,5); } } Zeichnen eines Linienzuges mit der Methode abstract void drawPolygon(int xPunkte[], int yPunkte[], int nPunkte) durch die gegebenen Koordinaten in der Vordergrundfarbe. Die Figur wird dabei nicht geschlossen, wenn nicht Strat- und Endkoordinaten gleich sind. Mit nPunkte werden die Anzahl der gezeichneten Linien kontrolliert. Die Klasse Polygon. Sie ist eine Erweiterung des Interface Shape . Ein PolygonObjekt verwaltet eigenständig seine Koordinaten, von außen können Elemente aufgenommen werden. 194 pr41105 234 Programmieren in Java java.awt.Polygon protected Rectangle bounds public int npoints public int[] xpoints public int[] ypoints << Konstruktor >> public Polygon() public Polygon(int[] xpoints, int[] ypoints, int npoints) << Methoden >> public void addPoint(int x, int y) public boolean contains(double x, double y) public boolean contains(double x, double y, double w, double h) public boolean contains(int x, int y) ... public Rectangle getBounds(); ... public boolean inside(int x, int y) ... public void translate(int deltaX, int deltaY) Abb.: Die Klasse Polygon Das Zeichnen von Kreisen und Ellipsen. Es erfolgt über die Methode public abstract void drawOval(int x, int y, int width, int height). (x,y) gibt die Koordinaten der oberen linken Ecke des umschreibenden Rechtecks an. Das Zeichnen von gefüllten Kreisen und Ellipsen. Es erfolgt über die Methode public abstract void fillOval(int x, int y, int width, int height) Das Zeichnen von Bögen. Java stellt die folgende Methode dafür zur Verfügung: public abstract void drawArc(int x, int y, int breite, int hoehe, int startWinkel, int bogenWinkel). „startWinkel“ bestimmt den Anfangswinkel von einer (gedachten) horizontalen Mittellinie aus gesehen, ab dem der Bogen gezeichnet werden soll. „bogenWinkel“ legt fest, wie weit der Bogen ab dem Startpunkt gezeichnet wird und in welche Richtung er geht. Die positive Richtung in Java ist entgegen dem Uhrzeigersinn. Das Zeichnen von gefüllten Bögen. Es erfolgt über die Methode public abstract void fillArc(int x, int y, int breite, int hoehe, int startWinkel, int bogenWinkel) Das Zeichnen von Zeichenketten erfolgt mit Hilfe der Methoden public abstract void drawString(String str, int x, int y) public void drawChars(char[] daten, int offset, int length, int x, int y) public void drawBytes(byte[] daten, int offset, int length, int x, int y) 235 Programmieren in Java 4.1.2 Farbangaben Java setzt Farben aus sog. Primärfarben (Rot, Grün, Blau) des Lichts zusammen (RGB-Modell). Eine Farbe im RGB-Modell wird durch die Angabe, wieviel rotes, grünes und blaues Licht in der Farbe enthalten sind, bestimmt. Dies kann entweder mit einer Ganzzahl zwischen 0 und 255 oder einer Gleitpunktzahl zwischen 0.0 und 1.0 geschehen. Farbe Weiß (white) Hellgrau (lightGray) Grau (gray) Dunkelgrau (darkGray) Schwarz (black) Rot (rot) Grün (green) Blau (blue) Gelb (yellow) Orange (orange) Pink (pink) Magenta (magenta) Cyan (cyan) Rot-Anteil 255 192 128 64 0 255 0 0 255 255 255 255 0 Grün-Anteil 255 192 128 64 0 0 255 0 255 200 175 0 255 Blau-Anteil 255 192 128 64 0 0 0 255 0 0 175 255 255 Abb.: Gebräuchliche Farbwerte RGB-Werte) Neben dem RGB-Farbmodell unterstützt Java auch das HSB-Farbmodell. Dieses stellt eine Farbe durch die drei Parameter Farbton, Intensität und Helligkeit dar. Die Farbmodelle können die gleichen Farben beschreiben und umgerechnet werden. Die Color-Klasse. Auf drei Arten kann eine Farbe erzeugt werden: public Color(int red, int green, int blue) Damit wird eine Farbe mit Rot-, Grün- und Blau-Werten zwischen 0 und 255 erzeugt. public Color(int rgb) erzeugt ein Color-Objekt aus dem rgb-Wert, der die Farben rot, grün, blau kodiert. Der Rot-Anteil befindet sich in den Bits 16 bis 23, der Grünanteil in 8 bis 15 und auch der Blauanteil in 0 bis 7. Jede Farbe ist durch ein Byte repräsentiert. public Color(float red, float green, float blue) Eine gängige Farbe kann schneller über die Standardfarbobjekte der in der Color-Klasse definierten verschiedenen Klassenvariablen gewonnen werden, z.B.: public public public public public public public public public public public public public static static static static static static static static static static static static static Color Color Color Color Color Color Color Color Color Color Color Color Color white; lightGray; gray; darkGray; black; red; blue; green; yellow; magenta; cyan; orange; pink; Ermitteln der RGB-Werte von einem bestimmten Farbobjekt. Es erfolgt über public int getRed(); public int getGreen(); 236 Programmieren in Java public int getBlue(); Setzen von Farben. Es wird möglich durch die Methode: public abstract void setColor(Color c). Der Parameter bestimmt das gewünschte Farbobjekt. Setzen von Hintergrundfarben. Normalerweise ist die Hintergrundfarbe eines Applets weiß oder dunkelgrau (je nach Container). Individuell kann die Hintergrundfarbe eines Applets gesetzt werden durch: public void setBackground(Color c). Parameter ist das gewünschte Farbobjekt. Setzen von Vordergrundfarben. Falls die Farbe für alle Zeichenobjekte innerhalb eines Applets pauschal festgesetzt werden soll, dann kann die Methode public void setForeground(Color c) verwendet werden. 4.1.3 Textausgabe über den Zeichen-Modus Die Graphics-Klasse enthält auch Methoden zum Zeichnen von Textzeichen und Zeichenketten (z.B. die drawString() Methode). Zusätzlich spielen die FontKlasse195 und die Fontmetrics-Klasse196 beim Textzeichnen eine Rolle. Font FontMetrics { abstract } public static final int PLAIN public static final int BOLD public static final int ITALIC protected String name protected int style protected int size public Font getFont() public int getLeading() public int getAscent() public int getDescent() public int getHeight() public int getMaxAscent() public int getMaxDescent() public int getMaxAdvance() public int charWidth(int zeichen) public int charWidth(char zeichen) public int stringWidth(String str) public int[] getWidths() public String toString() << Konstruktoren >> public Font(String name, int style, int size) << Methoden >> public String getName() public int getStyle() public int getSize() public boolean isPlain() public boolean isBold() public boolean isItalic() public static Font getFont(String nm) public static Font getFont(String nm, Font font) public static Font decode(String str) public int hashCode() public boolean equals(Object obj) public String toString() Abb.: Die Klassen Font und FontMetrics 195 Die Font-Klasse stellt bestimmte Fonts dar (Name, Stil, Fontgröße) Die Fontmetrics-Klasse enthält Informationen über den Font wie tatsächliche Höhe und Breite eines bestimmten Zeichens. 196 237 Programmieren in Java Die Klasse Font Texte werden in einem standarmäßig bereitgestellten Font ausgegeben. Soll ein anderer Font zur Textausgabe verwendet werden, so muß ein Objekt der Klasse Font erzeugt und dem verwendeten Graphics-Objekt zugewiesen werden. Das Erzeugen neuer Font-Objekte wird über die Parameter name, style und size des Konstruktors der Klasse Font gesteuert: public Font(String name, int style, int size) name: Name des gewünschten Font. In allen Java-Systemen sollen „SansSerif“, „Serif“, und „Monospaced“ unterstützt werden. Unter Windows werden die Standardnamen auf die „True-TypeFonts“ „Arial“, „TimesNewRoman“ und „CourierNew“ abgebildet. style: Auswahl der Ausprägung (fett, kursiv) Name Font.PLAIN Font.BOLD Font.ITALIC Wert 0 1 2 Bedeutung Standard-Font fett197 kursiv size: Angabe der Größe der gewünschten Schriftart in Pixel (Punktgrößen) public void setFont(Font font) wird zum Eintragen des Font-Objekts in den Grafikkontext verwendet. public Font getFont() ermittelt den aktuellen Font. Die Ermittlung, welche Zeichensätze auf einem System installiert sind, kann über Toolkit.getDefaultToolkit().getFontList() erfolgen. String[] getFontList() gibt die Namen der verfügbaren Zeichensätze zurück, z.B.:198 import java.awt.*; class ListFont { public static void main(String args[]) { // herkoemmliche Methode zur Ermittlung der // Schriftarten in einem Java-System String fonts[] = Toolkit.getDefaultToolkit().getFontList(); for (int i = 0; i < fonts.length; i++) System.out.println(fonts[i]); } } Die Klasse FontMetrics Diese Klasse bietet Methoden zur Bestimmung der Größe der angezeigten Zeichen in der festgelegten Schrift an. Begriffe für Schriften und Text. Baseline (Grundlinie): Damit ist die imaginäre Linie gemeint, auf der der Text steht. Descent (Unterstand): Damit ist gemeint, wie weit ein Buchstabe unter die Grundlinie geht. Ascent (Überstand): Damit ist gemeint, wie weit ein Buchstabe über die Grundlinie geht. 197 BOLD und ITALIC können auch gemeinsam verwendet werden, indem beide Konstanten mit „+“ zusammengefügt werden. 198 pr41300 238 Programmieren in Java Leading (Zeileabstand): Damit ist der Raum zwischen dem Descent eines Buchstabens und dem Ascent der nächsten Zeile gemeint. Methoden. Methode stringWidth() charWidth() getAscent() Aktion Gibt die volle Breite einer Zeichenmkette in Pixel aus Gibt die Breite eines bestimmten zeichens aus Gibt die Entfernung zwischen der Grundlinie und der oberen Grenze der Buchstaben aus getDescent() Gibt die Entfernung zwischen der Grundlinie und der unteren Grenze der Buchstaben aus (z.B. p und g) getLeading() Gibt den Abstand zwischen dem Überstand einer Zeile und dem Überstand der nächsten Zeile aus getHeight() gibt die Gesamthöhe der Schrift aus, d.h. die Summe von Überstand, Unterstand und Zeilenabstand Abb.: Fontmetrics-Methoden Bsp.199: import java.applet.*; import java.awt.*; public class AllAnfAppl extends Applet { private static final String spruch = "Aller Anfang ist schwer!"; private Font font; private FontMetrics fontMetrics; private int spruchBreite; private int spruchAscent; private int spruchDescent; private int spruchX; private int spruchY; // Methoden public void init() { setBackground(Color.white); font = new Font("Helvetica",Font.BOLD,24); fontMetrics = null; } // public void paint(Graphics g) { // Oval mit Farbe yellow g.setColor(Color.yellow); g.fillOval(0,0,getSize().width,getSize().height); // Roter Rahmen; da Java keine Linienbreite kennt, // wird die Linienbreite durrch 4 Ovale, (die sich // um Pixelbreite unterscheiden,) simuliert g.setColor(Color.red); g.drawOval( 3, 3,getSize().width-6,getSize().height-6); g.drawOval( 2, 2,getSize().width-4,getSize().height-4); g.drawOval( 1, 1,getSize().width-2,getSize().height-2); g.drawOval( 0, 0,getSize().width,getSize().height); g.setColor(Color.black); g.setFont(font); if (fontMetrics == null) { fontMetrics = g.getFontMetrics(); spruchBreite = fontMetrics.stringWidth(spruch); spruchAscent = fontMetrics.getAscent(); spruchDescent = fontMetrics.getDescent(); 199 pr41301 239 Programmieren in Java } int breite = getSize().width; int hoehe = getSize().height; spruchX = (breite - spruchBreite) / 2; spruchY = (hoehe + spruchAscent - spruchDescent) / 2; g.drawString(spruch,spruchX,spruchY); } } 4.1.4 Grössenangaben Für die meisten Größenangaben werden int-Werte benutzt. Darüber hinaus gibt es auch alterantive Formulierungsmöglichkeiten mit den Klassen Dimension, Point und Rectangle. << interface >> Shape Point public int x public int y Rectangle public int y public int y public int width public int height << Konstruktoren >> public Rectangle(Rectangle r) public Rectangle(int x, int y, int width, int height) public Rectangle(int width, int height) public Rectangle(Point p, Dimension d) public Rectangle(Point p) public Rectangle(Dimension d) << Methoden >> public Rectangle getBounds() public void setBounds(int x,int y, int width, int height) public void setBounds(Rectangle r) public Point getLocation() public void setLocation(Point p) public void setLocation((int x, int y) public void translate(int x, int y) public Dimension getSize() public void setSize(int width, int height) public boolean contains(Point p) public boolean contains(int x, int y) public boolean isEmpty() public int hashCode() public boolean equals(Object obj) public String toString() << Konstruktoren >> public Point() public Point(Point p) public Point(int x, int y) << Methoden >> public Point getLocation() public void setLocation(Point p) public void setLocation(int x, int y) public void move(int x, int y) public void translate(int x, int y) public int hashCode() public boolean equals(Object obj) public String toString() Dimension public int width public int height << Konstruktoren >> public Dimension() public Dimension(Dimension d) public Dimension(int width, int height) << Methoden >> public Dimension getSize() public void setSize(Dimension d) public boolean equals(Object obj) public String toString() Abb.: Die Klassen Dimension und Rectangle Das Interface Shape wird von den bisher bekannten geometrischen Formen (z.B. Rectangle, Polygon) implementiert und außerdem von den neuen geometrischen Formen im Paket java.awt.geom. 240 Programmieren in Java 4.1.5 Clipping-Operationen Jeder Grafikkontext hat eine zugeordnete Clipping-Region, die dazu dient, die Ausgabe auf einen bestimmten Bereich einzugrenzen. Clipping ist eine Eigenschaft des aktuellen „Graphic“ Objekts. Mit der Methode clipRect(int x, int y, int breite, int hoehe) läßt sich der Bereich einschränken, dann erfolgen alle Operationen in diesem Bereich. Mit public abstract void setClip(int x, int y, int width, int height) public abstract void setClip(Shape clip) kann die Clipping-Region auf einen beliebigen Bereich innerhalb des aktuelle Fensters ausgedehnt werden. Die zweite Version von setClip() erlaubt die Übergabe eines Objekts, die das Shape-Interface implementiert. 4.2 Bildverarbeitung Bilder sind neben Text das wichtigste visuelle Gestaltungsmerkmal. In Java können Grafiken an verschiedenen Stellen eingebunden werden, z.B. als Grafiken in Zeichenflächen (Canvas) oder als Icons in Buttons. Über Java können GIF-Bilder200 und JPEG-Bilder geladen werden. Jede Grafik wird als Exemplar der Klasse Image erzeugt. Die Image Klasse ist eine abstrakte Klasse, die zur Implementierung folgender konkreter Klassen dient: - ImageProducer für die Erzeugung von Bildern auf der Grundlage von Bildpunkten. - ImageConsumer für die Darstellung von Bildern als Interpretation der gespeicherten Bildpunkte. - ImageFilter für die Filterung des Bildes insbesondere hinsichtlich vorgebbarer Farbmodelle und Bildeigenschaften (mit der speziellen Subklasse RGBImageFilter). - ImageObserver bspw. für die Beobachtung, ob ein Bild vollständig geladen ist u.ä.m. Zur vollständigen Kontrolle des Ladens eines Bilds stellt java.awt. die Klasse MediaTracker bereit. 200 Das GIF-Format (Graphics Interchange Format) ist ein komprimierendes Verfahren, das 1987 von CompuServe-Betreibern zum Austausch von Bildern entwickelt wurde. GIF-Bilder können bis zu 1600 mal 1600 Punkte umfassen. Die Komprimierung (nach dem LZW-Verfahren) ist verlustfrei. Jedes GIF-Bild kann aus maximal 256 Farben bestehen (bei einer Palette von 16,7 Millionen Farben). 241 Programmieren in Java 4.2.1 Klassen zur Bildverarbeitung Toolkit { abstract } public abstract Image getImage(String dateiName) public abstract Image getImage(Url url) public abstract Image createImage(ImageProducer erzeuger) public abstract boolean prepareImage(Image bild, int breite, int hoehe,ImageObserver observer) public abstract int checkImage(Image bild, int breite, int hoehe, ImageObserver observer) .... Image { abstract } public abstract int getHeight(ImageObserver obs) public abstract int getWidth(ImageObserver obs) public abstract ImageProducer getSource() public abstract Graphics getGraphics() public abstract void flush() public abstract Object getProperty(String attribut, ImageObserver obs) public Image getScaledInstance(int breite, int hoehe, int modus) << interface >> ImageObserver public static final int ALLBITS public static final int SOMEBITS public static final int ABORT public static final int ERROR public static final int PROPERTIES public abstract boolean imageUpdate(Image bild, int infoFlags, int x, int y, int breite, int hoehe) MediaTracker public static final int ABORTED public static final int COMPLETE public static final int ERRORED public static final int LOADING << Konstruktor >> public MediaTracker(Component comp) << Methoden >> public void addImage(Image bild, int id) public void addImage(Image bild, int id, int breite, int hoehe) public boolean checkAll() public void waitForAll() throws InterruptedException public boolean checkID(int id) public void waitForID(int id) throws InterruptedException ... Abb.: Image-Klassen 242 Programmieren in Java Die Klassen zur Bildverarbeitung befinden sich im Package java.awt.image. Mit Hilfe der Klasse java.awt.Toolkit kann ein Bild aus einer angegebenen Quelle mit getImage() geladen werden. Dabei wird ein Objekt vom Typ java.awt.Image zurückgegeben. Der Ladezustand eines Objekts (z.B eines Bilds) kann überwacht werden, wenn die zum Objekt zugehörige Klasse das Interface java.awt.image.ImageObserver implementiert. Das tun alle AWT- und SwingKontrollelemente. Zur vereinfachten Benutzung beim Laden mehrerer Bilder gibt es die Klasse java.awt.MediaTracker. Die weiteren Klassen im Paket java.util.image ermöglichen Skalierung, Rotation, Filtern und andere Veränderungen der Bilder. Als Bildformate werden GIF und JPG unterstützt. In Java 2 kamen Klassen zum pixelorientierten Erzeugen und Bearbeiten von Bildern im Paket java.awt.image und dem neuen java.awt.image.renderable hinzu. Bsp.: Ein Bildbetrachter201 import java.awt.*; import java.awt.event.*; public class ImageViewer extends Frame implements ActionListener { private Image image; private Frame frame; public ImageViewer() { setTitle("Bildbetrachter"); // konstruiere die Menuezeile MenuBar mbar = new MenuBar(); Menu menue = new Menu("Datei"); MenuItem menueItem = new MenuItem("Oeffnen", new MenuShortcut((int) 'O')); menueItem.addActionListener(this); menue.add(menueItem); mbar.add(menue); setMenuBar(mbar); // Schliessen des Fenster mit X frame = this; addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); setSize(600,400); } public void paint(Graphics g) { if (image != null) { g.drawImage(image,0,0,this); setSize(image.getWidth(this),image.getHeight(this)); } } public void actionPerformed(ActionEvent e) { FileDialog d = new FileDialog(frame,"Oeffne Grafikdatei", FileDialog.LOAD); d.setFile("*.jpg;*gif"); d.show(); String file = d.getDirectory() + d.getFile(); 201 pr42300 243 Programmieren in Java image = Toolkit.getDefaultToolkit().getImage(file); if (image != null) repaint(); } public static void main(String args[]) { new ImageViewer().show(); } } Mit der Klasse MediaTracker kann festgestellt werden, ob ein Bild für die Anzeige bereit ist. Ein MediaTracker, z.B. für ein Applet, kann erzeugt werden über MediaTracker meinTracker = new MediaTracker(this); „this“ bezieht sich auf das Applet. Ein Bild wird angezeigt über die Methode getImage(), z.B. über Image bild = getImage(getDocumentBase(),“B......jpg“). Der Mediatracker wird angewiesen, dieses Bild über meinTracker.addImage(bild,0) zu beobachten. Mit waitForID() kann gewartet werden, bis das Bild fertig geladen wirde, z.B. meinTracker.waitForID(0). Mit waitForAll() kann gewartet werden, bis alle Bilder geladen sind. Falls nicht die ganze Zeit gewartet werden soll, bis das Bild geladen ist, kann der Ladevorgang mit der Methode statusID() überwacht werden. Beim Aufruf von statusID() wird eine Identifikation (ID) übergeben, für die ein Status angelegt werden soll, und ein boolscher Operator mit einer Angabe, ob der Ladevorgang für das Bild gestartet werden soll oder nicht, z.B.: meinTracker.statusID(0,true) Soll der Status aller Bilder überprüft werden, dann erfolgt der Aufruf meinTracker.statusAll(true). Die Methoden statusID() bzw. statusAll() geben eine ganze Zahl zurück, die durch folgende Flags symbolisiert wird: MediaTracker.ABORTED MediaTracker.COMPLETE MediaTracker.LOADING MediaTracker.ERRORED // // // // Das Das Das Das Laden von Bildern wurde abgebrochen Bilder wurden komplett geladen Bilder werden noch geladen Laden von Bildern ist fehlerhaft Mit checkID() bzw. checkALL() kann überprüft werden, ob ein Bild bzw. alle Bilder geladen wurde(n): public public public public boolean boolean boolean boolean checkAll() checkAll(boolean startLoading) checkID(int id) checkID(int id,boolean startLoading) Falls „startLoading“ true ist, wird der Ladevorgang für alle Bilder, die noch nicht geladen sind, gestartet. 244 Programmieren in Java 4.2.2 Bildproduzenten und Bildkonsumenten Bildproduzenten sind die Quellen für Bilddateien. Bildkonsumenten sind Objekte, die Bilder verwenden202. In Java ist die Aufgabe von Bildproduzenten bzw. Bildkonsumenten durch die Schnittstelle ImageProducer und ImageConsumer abgebildet. Das Interface ImageProducer beschreibt Methoden zum Bereitstellen von Pixeln eines Bildes. Klassen, die die Schnittstelle implementieren, stellen Bildinformationen einer speziellen Quelle dar. So ist die Klasse MemoryImageSource eine vorgefertigte Klasse, die ImageProducer implementiert. Sie produziert Bildinformationen aus einem Array von Pixeln, die im Speicher gehalten werden. Die Schnittstelle ImageConsumer beschreibt Methoden, die einem Objekt den Zugriff auf die Bilddatei des Produzenten erlauben. Das Produzenten/Konsumenten-Modell verwendet eine ColorModel-Klasse. Bilder, die zwischen Produzenten und Konsumenten ausgetauscht werden, werden aus Datenfeldern von Ganzzahlen erstellt. Jede Ganzzahl steht für die Farbe eines Pixels. Die ColorModel-Klasse verfügt über Methoden zum Herausziehen von roten, grünen, blauen und „alpha“-Komponenten. Die „alpha“-Komponente steht für die Transparenz einer Farbe. Ein „alpha“-Wert von 255 bedeutet, daß die Farbe vollkommen lichtdurchlässig ist. Ein „alpha“-Wert von 0 zeigt an, daß die Farbe vollständig transparent ist. Das Standard-Farbmodell ist das RGB-Default-Modell mit dem die 4 Farbkomponenten in eine Form „0xaarrggbb“ gebracht werden. Die 8 Bit ganz links sind der alpha-Wert, die nächsten 8 Bit sind die „Rot“-Komponente, danach kommt die „Grün“-Komponente und zum Schluß kommen 8 Bits für Blau. Bsp.: Eine Farbe von „0x12345678“ würde eine „alpha“-Komponente von 0x12 (ziemlich transparent) haben, eine Rotkomponente von 0x34, eine Grünkomponente von 0x56 und eine Blaukomponente von 0x78. 202 typischerweise einfache Zeichenroutinen, die das Bild auf dem Bildschirm anzeigen. 245 Programmieren in Java 4.2.3 Bildfilter ImageFilter liegen zwischen Produzenten und Bildinformationen und nehmen Einfluß auf die Größe. Konsumenten, verändern Subklassen der Klasse ImageFilter: BufferedImageFilter. Mit diesem Filter läßt sich ein Objekt vom Typ BufferedImageOp übergeben, mit dem unterschiedliche Manipulationen ermöglicht werden. BufferedImageOp ist eine Schnittstelle, die von AffineTransformOp, ConvolveOp, LookupOp implementiert wird. CropImageFilter. Bildteile werden herausgeschnitten. ReplicateScaleFilter. Zum Vergrößern / Verkleinern von Bildern. RGBImageFilter. Dieser allgemeine Filter ist für eine eigene, um RGBFilter erweiterte Filterklasse gedacht. Bilder skalieren mit getScaledInstance() Die Methode public Image getScaledInstance(int width,int height,int hints) liefert ein skaliertes Bild mit den neuen Ausmaßen width und height, hints gibt einen Skalierungsfaktor als Konstante an. getScaledInstance() greift auf die Filterklassen AreaAveragingScaleFilter und ReplicateScaleFilter zu. Sie berechnen jeweils das neue Bild über den Bildproduzenten. Bei einer Vergrößerung werden die Pixel einer Zeile und Spalte einfach verdoppelt. Bei einer Verkleinerung werden einfach Reihen und Spalten weggelassen203. Beim Vergrößern oder Verkleinern kommt es zu Pixelfehlern und die Frage, wie Pixel vergrößert werden, beeinflusst das Endergebnis und die Geschwindigkeit.. getScaledInstance() verlangt nicht nur Breite und Höhe, sondern auch eine Konstante für die Art der Skalierung. Der Skalierungsparameter bestimmt den Algorithmus. Skalierungsparameter SCALE_DEFAULT SCALE_FAST SCALE_SMOOTH SCALE_REPLICATE SCALE_AREA_AVERAGING verwendet einen Standard-Skalierungs-Algorithmus verwendet einen Skalierungs-Algorithmus, der mehr Wert auf Geschwindigkeit als auf Glätte des Bildes legt verwendet einen Algorithmus mit guter Bildqualität und legt wenig Wert auf Geschwindigkeit benutzt für den Skalierungs-Algorithmus den ReplicateScaleFilter verwendet den AreaAveragingScaleFilter Abb.: Skalierungsparameter für getScaledInstance() Bsp.204: import import import import import public { java.awt.*; java.awt.event.*; java.awt.image.*; java.applet.*; javax.swing.*; class ScaledImage extends Applet 203 Mit einem AreaAveragingScaleFilter erhält man die besseren Resultate, da Pixel nicht einfach kopiert werden, sondern eingefügte Pixel aus der Mittelwertberechnung bestimmt werden. Der Algorithmus heißt auch nearest neighbor algorithm. 204 pr42300 246 Programmieren in Java private Image originalImage; private Image scaledImage; public void init() { originalImage = new ImageIcon("B01000800.jpg").getImage(); int prozent = 50; scaledImage = originalImage.getScaledInstance( (originalImage.getWidth(this) * prozent)/100, (originalImage.getHeight(this)*prozent)/100, Image.SCALE_SMOOTH ); } public void paint(Graphics g) { g.drawImage(scaledImage ,5,0,this); } } Bildteile ausschneiden mit dem CropImageFilter Für die Anwendung eines Filters wird die Klasse FilteredImageSource herangezogen. Im Konstruktor von FilteredImageSource wird das Bild und der Filter angegeben. Bsp.:205 Das folgende Applet nimmt das Bild aus der vorstehenden Abbildung und setzt ihm ein CropImageFilter auf, damit es nur einen Teil des Bildes anzeigt. import import import import import public { 205 java.awt.*; java.awt.event.*; java.awt.image.*; java.applet.*; javax.swing.*; class ScaledCropImage extends Applet pr42300 247 Programmieren in Java private Image originalImage; private Image scaledImage; private Image croppedImage; private ImageFilter cropFilter; public void init() { originalImage = new ImageIcon("B01000800.jpg").getImage(); int prozent = 50; scaledImage = originalImage.getScaledInstance( (originalImage.getWidth(this) * prozent)/100, (originalImage.getHeight(this)*prozent)/100, Image.SCALE_SMOOTH ); cropFilter = new CropImageFilter(0,0,240,200); croppedImage = createImage(new FilteredImageSource(scaledImage.getSource(),cropFilter)); } public void paint(Graphics g) { g.drawImage(croppedImage ,3,0,this); } } Manipulation über Farbfilter Die folgende Filterklasse ist abgeleitet von RGBImageFilter. Sie umfaßt filterRGB() und zwingt einem Bild einen Grauschleier auf. class GrayFilter extends RGBImageFilter { public GrayFilter() { canFilterIndexColorModel = true; } public int filterRGB(int x, int int a = rgb & 0xff000000; int r = (((rgb & 0xff0000) + int g = (((rgb & 0x00ff00) + int b = (((rgb & 0x0000ff) + return a | r | g | b; } y, int rgb) { 0x180000)/3) & 0xff0000; 0x018000)/3) & 0x00ff00; 0x000180)/3) & 0x0000ff; } 248 Programmieren in Java Bsp.206: Das folgende Applet benutzt das Bild der vorstehenden Abbildung und färbt es mit Hilfe der vorstehenden Filterklasse GrayFilter grau ein. import java.awt.*; import java.awt.event.*; import java.awt.image.*; import java.applet.*; import javax.swing.*; public class ScaledCropGrayImage extends Applet { private Image originalImage; private Image scaledImage; private Image croppedImage; private Image grauImage; private ImageFilter cropFilter; private ImageFilter grauFilter; public void init() { originalImage = new ImageIcon("B01000800.jpg").getImage(); int prozent = 50; scaledImage = originalImage.getScaledInstance( (originalImage.getWidth(this) * prozent)/100, (originalImage.getHeight(this)*prozent)/100, Image.SCALE_SMOOTH ); cropFilter = new CropImageFilter(0,0,240,200); croppedImage = createImage(new FilteredImageSource(scaledImage.getSource(),cropFilter)); grauFilter = new GrayFilter(); ImageProducer erzeuger = new FilteredImageSource(croppedImage.getSource(),grauFilter); grauImage = this.createImage(erzeuger); } public void paint(Graphics g) { g.drawImage(grauImage,3,0,this); g.drawImage(croppedImage ,250,0,this); } } 206 vgl. pr42300 249 Programmieren in Java 4.2.4 Kopieren von Speicher in ein Bild bzw. von Bildern in einen Speicher 4.2.4.1 Kopieren von Speicher in ein Bild Ein möglicher Typ für das Produzieren von Bildern ist ein Datenfeld mit ganzen Zahlen, die für die Farbe eines jeden Pixels stehen. Möglich wird das durch die Klasse MemoryImageSource. Bsp.: Das folgende Applet erzeugt ein Speicherbild, eine MemoryImageSource und zeichnet das Bild im Zeichenbereich. import java.applet.*; import java.awt.*; import java.awt.image.*; /* das Applet zeichnet ein Image und benutzt dazu einen Array mit Pixeln */ public class SpeicherBild extends Applet { private final static int b = Color.blue.getRGB(); private final static int r = Color.red.getRGB(); private final static int g = Color.green.getRGB(); int pixels[] = { b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, g, g, g, g, g, g, b, b, b, b, g, g, g, g, g, g, b, b, b, b, g, g, r, r, g, g, b, b, b, b, g, g, r, r, g, g, b, b, b, b, g, g, g, g, g, g, b, b, b, b, g, g, g, g, g, g, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b, b }; Image meinBild; public void init() { /* Erzeigen des Bilds aus dem Array pixels. Die Pixels werden zeilenweise von Postion 0 aus dem Array gelesen. eine Zeile umfasst 10 Postionen. */ meinBild = createImage( new MemoryImageSource(10,10,pixels,0,10)); } public void paint(Graphics g) { // Zeichen das Bild. // Breite und Hoehe des Bilds werden 10fach vergroessert g.drawImage(meinBild,0,0,100,100,this); } } 250 Programmieren in Java Abb.: Darstellung von SpeicherBild 4.2.4.2 Kopieren von Bildern in einen Speicher Die PixelGrabber-Klasse nimmt ein Bild und macht aus diesem ein Datenfeld mit ganzen Zahlen. Ein PixelGrabber wird auf ein Image-Objekt aufgesetzt und füllt ein Ganzzahl-Feld mit den Farbwerten, die die Anteile der Farben rot, grün und blau enthalten. Der PixelGrabber ist für Modifikationen bereits existierender Bilder nützlich. class java.awt.image.PixelGrabber implements ImageConsumer Konstruktor. public PixelGrabber(Image img, int x, int y, int breite, int hoehe, int feld[], int verschiebung, int scanSize) erzeugt ein PixelGrabber-Objekt, das ein Rechteck von RGB-Farben aus dem Feld holt. Das Rechteck ist durch die Maße x, y, breite, hoehe beschrieben. Die Farbe für einen Punkt (i,j) sind im Feld an der Position (j-y)*scanSize+(i-x)+verschiebung. Mit der Umwandlung wird noch nicht begonnen. Sie muß mit der Funktion grabPixels() angeregt werden. Methoden. public boolean grabPixels() throws InterruptedException Die Werte von einem Image oder ImageProducer werden geholt. Die Funktion kann von außen unterbrochen werden. public int getHeight() liefert die Höhe des Pixelfelds. Ist die Höhe nicht verfügbar, dann ist das Ergebnis –1. public int getWidth() liefert die Breite des Pixelfelds. Bsp.: Das nachfolgende Bild lädt ein Bild und gibt die Farbinformationen auf der Konsole aus. import import import import javax.swing.*; java.awt.*; java.awt.event.*; java.awt.image.*; public class PR42400 extends Frame { Image bild; int breite, hoehe; int pixels[]; public PR42400() { 251 Programmieren in Java /* bild = Toolkit.getDefaultToolkit().getImage( "d:\\jdk1.3.1\\pgj\\progr\\pr42400\\B01000800.jpg"); */ bild = new ImageIcon("B01000800.jpg").getImage(); if (bild == null) System.out.println("Kein Bild"); breite = bild.getWidth(this); System.out.println(breite); hoehe = bild.getHeight(this); pixels = new int [breite * hoehe]; PixelGrabber grabber = new PixelGrabber(bild,0,0,breite,hoehe, pixels,0,breite); try { grabber.grabPixels(); } catch(InterruptedException e) { System.err.println("Fehler beim Holen der Pixel"); } setSize(breite,hoehe); repaint(); addMouseListener(new MouseAdapter() { public void mouseClicked(MouseEvent m) { int pixel = pixels[breite * m.getY() + m.getX()]; int alpha = (pixel >> 24) & 0xff; int rot = (pixel >> 16) & 0xff; int gruen = (pixel >> 8) & 0xff; int blau = (pixel) & 0xff; System.out.println("R=" +rot+ " G=" +gruen+ "B=" +blau); } }); } public void paint(Graphics g) { if (bild != null) g.drawImage(bild,0,0,this); } public static void main(String args[]) { Frame f = new PR42400(); f.show(); } } 252 Programmieren in Java 4.3 2D Java Java2D ist Bestandteil der Java Foundation Classes (JFC), ein Teil dessen, was Sun Swing nennt und sämtliche Techniken umfaßt, die die GUI-Fähigkeit von Java ausmachen. Das neue Java2D-API ist ein Satz von Klassen für erweiterte 2D-Grafik und die Bildverarbeitung. Die abstrakte Klasse Graphics2D erweitert die Funktionalität von Graphics um Transformationen, Linienstärke und –stil. Graphics { abstract } Graphics2D { abstract } Stroke stroke // Definition von Linienstiften Paint paint // Erweiterung des Farbmodells AffineTransform transform // erlaubt Veränderungen der Form Composite composite // gibt an, wie Objekte verschiedener Farben überlagert werden // sollen, erlaubt weiche Farbübergänge (Anti-Aliasing) << Konstruktor >> protected Graphics2D() << Methoden >> public abstract void addRenderingHints(Map hints) public abstract void clip(Shape s) public abstract draw(Shape s) public abstract void drawImage(Image img, AffineTransform xForm, ImageObserver obs) public abstract void fill(Shape s) public abstract GraphicsConfiguration getDeviceConfiguration() public abstract FontRenderContext getFontRenderContext() public abstract Paint getPaint() public abstract Object getRenderingHint(RenderingHints.Key hintKey) public abstract Stroke getStroke() public abstract AffineTransform getTransform() public abstract void rotate(double theta) public abstract void scale(double sx, double sy) public abstract void setComposite(Composite comp) public abstract setPaint(Paint paint) public abstract void setRenderingHint(RenderingHints.Key hintKey, Object hintValue) public abstract void setTransform(AffineTransform Tx) public abstract void transform(AffineTransform Tx) public abstract void translate(double tx, double ty) public abstract void translate(int x, int y) ... Abb.: Die Klasse Graphics2D 253 Programmieren in Java 4.3.1 Das Zeichnen unter Java2D API Zeichnen unter dem AWT Drawing Model Dieses Modell wurde bisher zum Zeichnen verwendet, z. B.: import java.awt.*; import java.applet.*; public class MaleGefRechteck extends Applet { // Zeichnen unter dem AWT Drawing Model public void paint(Graphics g) { g.setColor(Color.red); g.fillRect(300,300,200,100); } } Die Vorgehensweise beim Bearbeiten für jede Komponente kann aus diesem Bsp. unmittelbar abgeleitet werden: 1. Spezifikation der notwendigen Atribute für die zu zeichnende Form, z.B. die Farbe mit „setColor“. 2. Definition der zu zeichnenden Form 3. Festlegen des genauen Aussehens der Form, z.B. über eine passende Grafikmethode. Das AWT biete einfache grafische Funktionen an. Diese AWT-Funktionen besitzen aber - einen niedrigen Abstraktionsgrad und keine objektorientierte Repräsentation von grafischen Objekten. - diskrete Fenster- statt kontinuierlicher Weltkoordinaten - ein "schwaches" Modell zur Abbildung von Farben und Farbverläufen. Zeichnen mit Java2D Das Java2D API umfaßt zusätzliche unterstützende Features zur Spezifizierung von Zeichenstiften, komplexen Formen und diversen Zeichenprozessen. Zur Nutzung dieser Möglichkeit muß der Graphics-Parameter der paint()-Methode in ein Graphics2D-Objekt gebracht werden („gecastet“) werden, z.B.: import java.awt.*; import java.applet.*; import java.awt.geom.*; public class MaleGefRechteck extends Applet { public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; // Spezifikation der Attribute g2d.setColor(Color.red); // Definition der Form (Verwende Even-Odd-Regel) GeneralPath path = new GeneralPath(); path.moveTo(300.0f,400.0f); // untere linke Ecke path.lineTo(500.0f,400.0f); // untere rechte Ecke path.lineTo(500.0f,300.0f); // obere rechte Ecke path.lineTo(300.0f,300.0f); // obere linke Ecke 254 Programmieren in Java path.closePath(); // Fuellen der Form g2d.fill(path); } } // Schliessen des Rechtecks Eine Polygon-Klasse wie unter dem AWT gibt es in dem 2D-API nicht. Hier wird ein neuer Weg eingeschlagen, der über die Klasse GeneralPath geht. Damit lassen sich beliebige Formen bilden. Dem Pfad werden verschiedene Punkte hinzugefügt, die dann verbunden werden. Die Punkte müssen nicht zwingend, wie bei einem Polygon mit Linien verbunden werden, sondern lassen sich auch durch quadratische oder kubische Kurven verbinden. Da Graphics2D eine Unterklasse von Graphics ist, lassen sich natürlich alle AWT-Operatoren weiter verwenden. Bsp.: Aufbau eines "roten Polygons" aus einem Pfad207 import java.awt.*; import java.applet.*; import java.awt.geom.*; public class PolyRot extends Applet { public void paint(Graphics g) { // Erzeuegen eines Graphics2D-Objekt Graphics2D g2d = (Graphics2D) g; g2d.setColor(Color.blue); // Definition der Form GeneralPath einPfad = new GeneralPath(); // Startpunkt festlegen (Ursprung der sichtbaren // Zeichenflaeche des Fensters einPfad.moveTo(getInsets().left,getInsets().top); for (int i = 1, j = 10;i <= 10; i++, j--) { einPfad.lineTo(getSize().width/j,getSize().height/i); einPfad.lineTo(getSize().width/j,getSize().height/j); } // der Pfad wird geschlossen einPfad.closePath(); // Zeichnen (blau) des Umrisses vom PfadObjekt // und Fuellen mit rot g2d.setColor(Color.blue); g2d.draw(einPfad); g2d.setColor(Color.red); g2d.fill(einPfad); } } 207 vgl.PR43100 255 Programmieren in Java Abb.: Ein rotes Polygon Im einfachsten Fall erzeugt man einen Pfad, indem man ein Objekt der Klasse GeneralPath instanziert und festlegt, welche Punkte zu diesem Pfad gehören sollen. Dies kann durch einen Aufruf der Methoden von GeneralPath geschehen. Man verfolgt einen virtuellen Pfad in der Zeichenfläche und generiert dabei die Punkte, die zum Pfad gehören sollen. Verfahrensweise beim Zeichnen mit den Java 2D-API-Klassen 1. Spezifikation der notwendigen beschreibenden Attribute 2. Definition der Form, eines Textstrings oder eines Bilds. Das Java2D-API behandelt Positionsangeben (Pfade), Texte und Bilder gleichartig. Sie können rotiert, skaliert, verzerrt und mit diversen Methoden zusammengesetzt werden. Das „Shape“-Interface definiert einen ganzen Satz von Methoden zur Beschreibung von geometrischen PATH-Objekten. GeneralPath ist eine Implementation vom Shape-Interface, das zur Definition von beliebig komplexen Formen (zusammengestzt aus Linien- und Kurvensegmenten) verwendet werden kann. 256 Programmieren in Java << interface >> java.awt.Shape java.awt.Polygon java.awt.geom.Line2D java.awt.geom.GeneralPath java.awt.geom.Area java.awt.geom.RectangularShape java.awt.geom.Arc2D java.awt.geom.Ellipse2D java.awt.geom.Rectangle2D Abb.: das Interface Shape und implementierende Klassen draw() aus der Klasse Graphics2D nimmt ein Shape-Objekt und zeichnet es. Shape-Objekte sind Linien, Polygone oder auch Kurven. abstract public void draw(Shape s) zeichnet die Forrm im aktuellen Graphics2D-Kontext. Bsp.: Ein Kreis innerhalb von einem Kasten.208 import import import import javax.swing.*; java.awt.*; java.awt.event.*; java.awt.geom.*; public class ShapeBsp extends JPanel { private Ellipse2D.Double kreis = new Ellipse2D.Double(10,10,350,350); private Rectangle2D.Double quadrat = new Rectangle2D.Double(10,10,350,350); public void paintComponent(Graphics g) { clear(g); Graphics2D g2d = (Graphics2D) g; g2d.fill(kreis); g2d.draw(quadrat); } protected void clear(Graphics g) { super.paintComponent(g); // loescht die Pixelmap des Bildschirms // per Default gibt es Double Buffering } protected Ellipse2D.Double getCircle() { return (kreis); } public static void main(String[] args) { ShapeBsp jp = new ShapeBsp(); String titel = jp.getClass().toString(); if (titel.indexOf("class") != -1) 208 vgl. PR43100 257 Programmieren in Java titel = titel.substring(6); JFrame frame = new JFrame(titel); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.getContentPane().add(jp,BorderLayout.CENTER); frame.setSize(380,400); frame.setVisible(true); } } 3. Festlegen vom Aussehen der Form, des Textstrings oder des Bildes über passende Graphics2D-Methoden. Geometrische Objekte Java2D unterscheidet folgende geometrische Objekte: - Shapes: beliebige offene oder geschlossene Figuren, zusammengesetzt aus Folgen von Lininen und Kurvensegmenten. - Text: Text in vorhert festgelegtem Zeichensatz (Zeichen sind als Shapes definiert) - Image (rechteckiger Ausschnitt einer Bitmap). Jedem geometrischen Objekt (Punkt, Klasse, Rechteck) ist ein Java-Klasse zugeordnet. Das konkrete Aussehen des Objekts (Farbe, Umriss) wird erst bei der 258 Programmieren in Java Darstellung festgelegt. Die Darstellung erfolgt über einen Rasterisierer209, der Kompositionsvorschriften für kontinuierliche geometrische Objekte in Weltkoordinaten über ein diskretes Raster umsetzt. Allgemeine Vorgehensweise 1. Setzen von Graphikattributen (rendering attributes) wie Linientyp, Füllmuster (auch komplexe Gradienten). 2. Definition der Form, einer Zeichenkette (auszugebender Text) oder eines Bilds. 3. Anwendung einer Transformation (z.B. Bild verzerren oder Filtern, Text drehen etc.). 4. Ausgabe eines graphischen Elements (z.B. durch die Graphics2D-Methoden draw(), drawImage(), drawRectangle() oder fill(). Die eigentliche Ausgabe erfolgt in 4 Schritten: 1. Das Objekt wird in Grafikprimitive umgewandelt und auf den Koordinatenraum des Ausgabegerätes (device space) abgebildet. - Handelt es sich um eine Form (Shape), so werden die in der Form enthaltenen Graphikelemente bestimmt. - Handelt es sich um einen Text, wird die Form der Buchstaben (Glyphen) aus der Schriftinformation bestimmt und in einen Umriß (outline) umgewandelt, der sich als Shape-Objekt beschreiben lässt. - Bei Bildern (images) wird ihre Bounding Box in Device-Koordinaten umgewandelt (unter Verwendung der im Programm angegebenen Transformationen des Graphics2D-Kontextes) 2. Der aktuelle clipping path schränkt die Darstellungsoperation (rendering operation) ein. Ein clipping path kann jede Form annehmen, die sich durch ein Shape-Objekt beschreiben lässt. Normalerweise handelt es sich um den Bereich der gerade tatsächlich gezeichnet werden kann (z.B. sichtbare Fläche eines Fensters). 3. Die Ausgabefarbe wird bestimmt (aus den Bilddaten bei Bildern, aus dem aktuellen Paint- oder Color-Objekt in allen anderen Fällen. 4. Die Farbe wird aus das auszugebende Objekt angewandt. Java 2D API-Klassen210 Außer der Klasse java.awt.Graphics2D umfaßt Java 2D API Klassen in den Paketen java.awt.geom (geometrische Formen, Pfade und Transformationen), java.awt.font, java.awt.color (Farbräume), java.awt.image und java.awt.image.renderable (Bitmaps und Filter) sowie java.awt.print (Drucken). Die Klassen für geometrische Objekte, die sich von der Klasse Shape ableiten, sind Polygon, RectangularShape, Rectangle, Area, QuadCurve2D und CubicCurve2D211. Eine besondere Klasse, die auch von Shape abgeleitet ist, heißt GeneralPath. Damit lassen sich mehrere Objekte einer Figur zusammensetzten. Die Klasse Rectangle2D, RoundRectangle2D, Aread2D und Ellipse2D erben 209 Die kontinuierlichen Shapes (z.B. ein Kreis) werden vom Rasterisierer in diskrete Rasterbelegungen umgewandelt. Man unterscheidet Rasterisierung mit oder ohne Aliasing: 210 pr43210 211 Beschreibung quadratischer und kubischer Kurvensegmente. Das sind Kurven, die durch zwei Endpunkte und durch Kontrollpunkte dazwischen gegeben sind. Kubische Kurvensegmente werden auch Bézier-Kurven genannt. 259 Programmieren in Java von der Klasse RectangularShape und sind Objekte, die durch eine rechteckige Box umgeben sind. 260 Programmieren in Java 4.3.2 Eigenschaften geometrischer Objekte Dicke und Art der Linien bestimmen Die Methode setStroke() kann definieren - die Dicke (width) - die Eigenschaft wie ein Liniensegment beginnt und abschließt - die Art, wie sich die Linien verbinden - ein Linien-Pattern (dash attributes) Unterstützt wird setStroke() durch die Schnittstelle Stroke, die konkret durch BasicStroke implementiert wird, z.B. public void paint(Graphics g) { Graphics2D g2 = (Graphics2D) g; BasicStroke pen = new BasicStroke(2.0f,BasicStroke.CAP_BUTT,BasicStroke.JOIN_ROUND) g2.setStroke(pen); } Für BasicStroke gibt es 9 Konstruktoren. Der hier verwendete Konstruktor von BasicStroke har drei Argumente: - einen float-Wert, der die Linienstärke angibt - einen int-Wert, der die Art des Linienendes festlegt - einen int-Wert, der den Stil des Verbindungsstücks zwischen zwei Liniensegmenten festlegt Besonders bei breiten Linie ist es interessant, wie die Linie endet. Hier läßt sich aus CAP-BUTT (keine Abschlußpunkte), CAP_ROUND (Anzeige von Kreisen an beiden Enden) und CAP_SQUARE auswählen. Die möglichen Stile für Verbindungselemente sind JOIN_MITER (Abschluß von Linien so, daß sie senkrecht aufeinander stehen), JOIN_ROUND (Abschluß der Linien durch abgerundete Ecken) und JOIN-BEVEL (Eine Linie wird zwischen den äußeren Eckpunkten gezogen). Bsp212.: Das folgende Applet zeigt Verbindungselemente zwischen Linien import java.awt.*; import java.applet.*; import java.awt.geom.*; public class StrokeDemo extends Applet { public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; BasicStroke pen1 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); g2d.setStroke(pen1); g2d.draw(new Line2D.Float(10f,10f,210f,10f)); BasicStroke pen2 = new BasicStroke(10.0f,BasicStroke.CAP_ROUND, BasicStroke.JOIN_MITER); g2d.setStroke(pen2); 212 pr43100 261 Programmieren in Java g2d.draw(new Line2D.Float(10f,30f,210f,30f)); BasicStroke pen3 = new BasicStroke(10.0f,BasicStroke.CAP_SQUARE, BasicStroke.JOIN_MITER); g2d.setStroke(pen3); g2d.draw(new Line2D.Float(10f,50f,210f,50f)); BasicStroke pen4 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); g2d.setStroke(pen4); g2d.draw(new Line2D.Float(10f,70f,100f,70f)); g2d.draw(new Line2D.Float(100f,70f,100f,120f)); g2d.draw(new Line2D.Float(100f,120f,10f,120f)); g2d.draw(new Line2D.Float(10f,120f,10f,70f)); BasicStroke pen5 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); g2d.setStroke(pen5); g2d.draw(new Line2D.Float(120f,70f,210f,70f)); g2d.draw(new Line2D.Float(210f,70f,210f,120f)); g2d.draw(new Line2D.Float(210f,120f,120f,120f)); g2d.draw(new Line2D.Float(120f,120f,120f,70f)); BasicStroke pen6 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); g2d.setStroke(pen6); g2d.draw(new Line2D.Float(30f,140f,10f,160f)); g2d.draw(new Line2D.Float(30f,140f,50f,160f)); BasicStroke pen7 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); g2d.setStroke(pen7); g2d.draw(new Line2D.Float(90f,140f,70f,160f)); g2d.draw(new Line2D.Float(90f,140f,110f,160f)); BasicStroke pen8 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); g2d.setStroke(pen8); g2d.draw(new Line2D.Float(150f,140f,130f,160f)); g2d.draw(new Line2D.Float(150f,140f,170f,160f)); float dash[] = {10, 2}; BasicStroke pen9 = new BasicStroke(2.0f,BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER, 1, dash,0); g2d.setStroke(pen9); g2d.draw(new Rectangle2D.Float(10f,180f,50f,50f)); BasicStroke pen10 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT, BasicStroke.JOIN_ROUND); g2d.setStroke(pen10); g2d.draw(new Rectangle2D.Float(10f,250f,60f,70f)); BasicStroke pen11 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL); g2d.setStroke(pen11); g2d.draw(new Rectangle2D.Float(90f,250f,60f,70f)); BasicStroke pen12 = new BasicStroke(10.0f,BasicStroke.CAP_BUTT, BasicStroke.JOIN_MITER); g2d.setStroke(pen11); g2d.draw(new Rectangle2D.Float(170f,250f,60f,70f)); } } 262 Programmieren in Java Abb. 263 Programmieren in Java 4.3.3 Koordinatensysteme und Transformation Das Java 2D-API unterscheidet zwischen - dem user coordinate space (Koordinatenraum des Benutzers) und - dem device coordinate space (Koordinatensystem des Ausgabegeräts) Zu den möglichen Transformationen gehören - die Drehung (rotate(), setToRotation()) - die Skalierung (scale(), setToScale()) - die Verschiebung (translate(), setToTranslation()) - die Verzerrung (shear(), setToShear()) Das folgende Beispiel213 zeigt ein Rechteck, das zunächst im Ursprung des Koordinatensystems (Linke obere Ecke) gezeichnet wird. Anschließend wird der Koordinatenraum durch sukzessive Anwendung von Transformationen - verschoben - verschoben und gedreht - verschoben, gedreht und skaliert - verschoben, gedreht, skaliert und verzerrt import import import import javax.swing.*; java.awt.*; java.awt.event.*; java.awt.geom.*; public class TransformBsp extends JPanel { public void paintComponent(Graphics g) { clear(g); Graphics2D g2d = (Graphics2D) g; Rectangle einRechteck = new Rectangle(getInsets().left, getInsets().top,50,50); g2d.draw(einRechteck); // neues Transformationsmodell erzeugen AffineTransform dieTransformation = new AffineTransform(); // Koordinatenraum um 50 x-, 50 y-Einheiten verschieben dieTransformation.translate(50.0,50.0); g2d.transform(dieTransformation); g2d.setColor(Color.lightGray); g2d.fill(einRechteck); // Koordinatenraum um 30 Grad gegen der Uhrzeigersinn drehen dieTransformation.rotate(-Math.PI/12.0); g2d.transform(dieTransformation); g2d.setColor(g2d.getColor().darker()); g2d.fill(einRechteck); // Koordinatenraum um die Faktoren 1.33 * x, 0.66 * y skalieren dieTransformation.scale(1.33f,0.66f); g2d.transform(dieTransformation); g2d.setColor(Color.black); g2d.fill(einRechteck); // Koordinatensystem um die Faktoren 0.1 * x, 0.1 * y verzerren 213 vgl. pr43100 264 Programmieren in Java dieTransformation.shear(0.1f,0.1f); g2d.transform(dieTransformation); g2d.setColor(Color.black); g2d.fill(einRechteck); } protected void clear(Graphics g) { super.paintComponent(g); // loescht die Pixelmap des Bildschirms // per Default gibt es Double Buffering } public static void main(String[] args) { TransformBsp tbsp = new TransformBsp(); String titel = tbsp.getClass().toString(); if (titel.indexOf("class") != -1) titel = titel.substring(6); JFrame frame = new JFrame(titel); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.getContentPane().add(tbsp,BorderLayout.CENTER); frame.setSize(380,250); frame.setVisible(true); } } Abb.: Transformation eines Rechtecks. Nach jeder Transformation wird das Rechteck neu gezeichnet. Affine Transformationen. Sie können durch einfache Transformationsmatrizen beschrieben werden, z.B. Translation eines Punktes (x,y) um (dx,dy): ⎛ 1 0 dx ⎞ ⎛ x ⎞ ⎛ x + dx ⎞ ⎜ ⎟ ⎜ ⎟ ⎜ ⎟ ⎜ 0 1 dy ⎟ ⋅ ⎜ y ⎟ = ⎜ y + dy ⎟ ⎜0 0 1 ⎟ ⎜ 1⎟ ⎜ 1 ⎟ ⎝ ⎠ ⎝ ⎠ ⎝ ⎠ 265 Programmieren in Java Bsp.214: Das Haus des Nikolaus. Gegeben ist die folgende paint()- Methode public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; // Definition der Form (Verwende Even-Odd-Regel) GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); path.moveTo(-25,50); path.lineTo(-25.0f,0.0f); path.lineTo(0.0f,-30.0f); path.lineTo(25.0f,0.0f); path.lineTo(-25.0f,0.0f); path.lineTo(25.0f,50.0f); path.lineTo(-25.0f,50.0f); path.lineTo(25.0f,0.0f); path.lineTo(25.0f,50.0f); path.closePath(); // Schliessen g2d.draw(path); } Die Implementierung dieser paint()-Methode zeigt das folgende Fenster an: Eine Translation um (100,100) führt zu dem folgenden Fenster: Diese Form wurde durch Implementieren der folgende paint()-Methode erreicht: public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; // Definition der Form (Verwende Even-Odd-Regel) 214 pr43100 266 Programmieren in Java GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); path.moveTo(-25,50); path.lineTo(-25.0f,0.0f); path.lineTo(0.0f,-30.0f); path.lineTo(25.0f,0.0f); path.lineTo(-25.0f,0.0f); path.lineTo(25.0f,50.0f); path.lineTo(-25.0f,50.0f); path.lineTo(25.0f,0.0f); path.lineTo(25.0f,50.0f); path.closePath(); // Schliessen // Transformation AffineTransform at = new AffineTransform(); at.setToTranslation(100.0f,100.0f); g2d.transform(at); g2d.draw(path); } Zusätzlich zur Translation soll das Haus noch um 45 Grad im Uhrzeigersinn gedreht werden. Dabei muß unterschieden werden zwischen dem Setzen einer einer Affinen Transformation und dem Erweitern einer Affinen Transformation. public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; // Definition der Form (Verwende Even-Odd-Regel) GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); path.moveTo(-25,50); path.lineTo(-25.0f,0.0f); path.lineTo(0.0f,-30.0f); path.lineTo(25.0f,0.0f); path.lineTo(-25.0f,0.0f); path.lineTo(25.0f,50.0f); path.lineTo(-25.0f,50.0f); path.lineTo(25.0f,0.0f); path.lineTo(25.0f,50.0f); path.closePath(); // Schliessen // Transformation AffineTransform at = new AffineTransform(); // Setzen der affinen Transformation at.setToTranslation(100.0f,100.0f); // Erweitern der affinen Transformation at.rotate(Math.PI/4); g2d.transform(at); g2d.draw(path); } Die Geometrie vom Haus des Nikolaus soll vergrößert werden. public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; // Definition der Form (Verwende Even-Odd-Regel) GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); 267 Programmieren in Java path.moveTo(-25,50); path.lineTo(-25.0f,0.0f); path.lineTo(0.0f,-30.0f); path.lineTo(25.0f,0.0f); path.lineTo(-25.0f,0.0f); path.lineTo(25.0f,50.0f); path.lineTo(-25.0f,50.0f); path.lineTo(25.0f,0.0f); path.lineTo(25.0f,50.0f); path.closePath(); // Schliessen // Transformation AffineTransform at = new AffineTransform(); // Setzen der affinen Transformation at.setToTranslation(100.0f,100.0f); // Erweitern der affinen Transformation at.rotate(Math.PI/4); // Skalieren at.scale(1.5f,1.5f); g2d.transform(at); // Zeichnen g2d.draw(path); } Das Fenster zeigt: Die Skalierung in Java2D beeinflusst auch die Stroke- und Paint-Eigenschaften, d.h. bei einer Vergrößerung wird auch die Liniendicke größer. Die vollständige Transformation kann durch dir folgendeTransformationsmatrix beschrieben werden (Kompostion von Transformationen): ⎛ 1 0 dx ⎞ ⎛ cos α ⎜ ⎟ ⎜ ⎜ 0 1 dy ⎟ ⋅ ⎜ 0 ⎜0 0 1 ⎟ ⎜ 0 ⎝ ⎠ ⎝ 0 sin α 0 0 ⎞ ⎛ sx 0 0 ⎞ ⎟ ⎜ ⎟ 0 ⎟ ⋅ ⎜ 0 sy 0 ⎟ ⋅ geom _ objekt 1 ⎟⎠ ⎜⎝ 0 0 1 ⎟⎠ 268 Programmieren in Java 4.3.4 Java2D-Pipeline Java2D stellt geometische Objekte über eine "Render-"Pipeline dar: Transformation fill() Shapes Stroke draw() Rasterisierer Text drawStr() Font Hints Images Rendering Hints drawImage() Clipping Paint Composition Output Device Abb.: Java2D-Pipeline Geometrische Objekte können neben ihrer Form (Shape, z.B. Rechteck) durch drei weitere Parameter beschrieben werden: Translation, Rotation, Skalierung. Die Anwendung dieser Parameter auf ein geometrisches Objekt wird als Transformation bezeichnet. Kontinuierliche Shapes werden vom Rasterisierer in diskrete Rasterbelegungen umgewandelt. Man unterscheidet Rasterisierung mit oder ohne Aliasing: - Aliasing: Raster-Pixel wird als belegt gekennzeichnet, wenn das Objekt meht als x% des Pixels belegt - Antialiasing: dem Pixel wird der Belegungsfaktor x zugewiesen und als Transparenzwert interpretiert. Rendering Hints sind Hinweise oder Wünsche an Java2D, welche Qualität bzw. welche Performance man gerne erreichen würde. Java2D entscheidet dann selber, welche dieser Wünsche erfüllt werden (können). Die Ausgabe von Java2D erfolgt typischerweise in ein Fenster. Dieses Fenster zeigt nur einen Ausschnitt der potentiell unbegrenzten (virtuellen) Java2D-Zeichenfläche. Gemalt werden soll natürlich nur das, was auch tatsächlich in diesem sichtbaren Ausschnitt liegt. Dieses Zuschneiden auf einen Bereich wird als Clipping bezeichnet. Java2D erlaubt neben dem normalen Clipping auf einem rechteckigen (Fenster-) Bereich auch das Clipping auf beliebige Java2D-Flächen. Im Paint-Schritt werden Farbe und Transparenz der Pixel festgelegt. Dazu gehört u.a.: - die Ausprägung der Umrisslinie (durchgehend, gestrichelt, …) - die Art der Füllung (feste Farbe, Farbverlauf, Muster, Bitmap, …) Falls ein Image gemalt werden soll, entfällt dieser Schritt, da alle Informationen implizit im Image enthalten sind. 269 Programmieren in Java Java2D erweitert das Hinzufügen von Objekten um sog. Kompostionsregeln (bekannt als Porter-Duff-Regeln). Neben der Porter-Duff-Komposition, die ein Source-Objekt mit der gesamten Zeichenfläche verknüpft, gibt es noch die sog. CAG-Komposition (Constructive Area Geometry). CAG verknüpft jeweils 2 (CAG-) Objekte zu einem neuen CAG-Objekt. 4.3.5 Farbmanagement unter Java 2D Die Klasse Color mit dem Interface Paint Ein Objekt der Klasse Color stellt eine Farbe im RGB-Farbschema dar. Color implementiert das neue Interface Paint (Zeichenfarbe). << interface >> Paint Color GradientPaint public static Color black; public static Color blue; public static Color cyan; … public static Color white; public static Color yellow; << Konstruktoren >> public Color(float r, float g, float b) public Color(int rgb) public Color(int rgba,boolean hasalppha) public Color(int r,int g,int b) public Color(int r,intg,int b,int a) << Methoden >> public Color brighter() public Color darker() public boolean equals() public int getAlpha() public int getBlue() … public static Color getColor(String nm) public static getHSBColor(float h,float s, float b) public int getRGB() public int getTransparency() public static int HSBtoRGB(float hue, float saturation,float brightness) public static float RGBtoHSB(int r,int g,int b, float[] hsbwerte) public String toString() 270 TexturePaint Programmieren in Java Abb.: Die Klasse Color Farbübergänge Java 2D erlaubt die Verwendung von Farbübergängen (Gradienten) und Mustern zur Füllung beliebiger Formen. Dazu existieren zwei Klassen, die die Schnittstelle Paint implementieren: - GradientPaint (repäsentiert einen glatten Übergang von einer Farbe zu einer anderen) - TexturePaint für Füllmuster auf der Basis einer Bilddatei, die das Muster vorgibt. public class GradientPaint extends Object implements Paint Konstruktoren. public GradientPaint(float x1,float y1,Color c1,float x2,float y2,Color c2) public GradientPaint(float x1,float y1,Color c1,float x2,float y2, Color c2,boolean cyclic) // c1 ist die Farbe am ersten Punkt(x1,y1), c2 ist die Farbe am zweiten Punkt (x2,y2). cyclic ist true, // falls der Übergang zwischen den Farben wiederholt werden soll Der aktuelle Gradient wird über setPaint() des Graphics2D-Kontextes gesetzt. Bsp.215: Ein Kreis wird mit einem Farbübergang ausgestattet. import java.awt.*; import java.awt.event.*; import java.awt.geom.*; public class GradientPaintBsp extends JPanel { private Ellipse2D.Double kreis = new Ellipse2D.Double(10,10,350,350); private GradientPaint gradient = new GradientPaint(0,0,Color.red,175,175,Color.yellow,true); public void paintComponent(Graphics g) { clear(g); Graphics2D g2d = (Graphics2D) g; g2d.setPaint(gradient); g2d.fill(getCircle()); g2d.setPaint(Color.black); g2d.draw(getCircle()); } protected void clear(Graphics g) { super.paintComponent(g); // loescht die Pixelmap des Bildschirms // per Default gibt es Double Buffering } protected Ellipse2D.Double getCircle() { return (kreis); } public static void main(String[] args) { GradientPaintBsp gbsp = new GradientPaintBsp(); String titel = gbsp.getClass().toString(); if (titel.indexOf("class") != -1) 215 pr43100 271 Programmieren in Java titel = titel.substring(6); JFrame frame = new JFrame(titel); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.getContentPane().add(gbsp,BorderLayout.CENTER); frame.setSize(380,400); frame.setVisible(true); } } Abb.: Ausstatten einer Form mit einem Farbgradienten public class TexturePaint extends Object implements Paint Konstruktor. public TexturePaint(BufferedImage texture,Rectangle2D anchor) // ein Bild wird über eine Form gedeckt. Methoden. public PaintContext createContext(ColorModel cm, Rectangle deviceBounds, Rectangle2D userBounds, AffineTransform xForm, Rendering hints) public BufferedImage getImage() public int getTransparency() Bsp.216: Ein Kreis wird mit einem Füllmuster abgedeckt. 216 pr43100 272 Programmieren in Java import import import import import javax.swing.*; java.awt.*; java.awt.event.*; java.awt.geom.*; java.awt.image.*; public class TexturePaintBsp extends JPanel { private Ellipse2D.Double kreis = new Ellipse2D.Double(10,10,350,350); public void paintComponent(Graphics g) { clear(g); Graphics2D g2d = (Graphics2D) g; // g2d.fill(kreis); // g2d.draw(quadrat); BufferedImage buffImage = new BufferedImage(10,10,BufferedImage.TYPE_INT_RGB); Graphics2D gg = buffImage.createGraphics(); gg.setColor(Color.yellow); gg.fillRect(0,0,10,10); gg.setColor( Color.black ); // draw in black gg.drawRect(1,1,6,6); // draw a rectangle gg.setColor(Color.blue ); // draw in blue gg.fillRect(1,1,3,3); // draw a filled rectangle gg.setColor(Color.red ); // draw in red gg.fillRect(4,4,3,3); // draw a filled rectangle g2d.fill( new RoundRectangle2D.Double( 155, 30, 75, 100, 50, 50 )); // paint buffImage onto the Frame g2d.setPaint(new TexturePaint(buffImage, new Rectangle(10,10))); g2d.fill(getCircle()); g2d.setPaint(Color.black); g2d.draw(getCircle()); } protected void clear(Graphics g) { super.paintComponent(g); // loescht die Pixelmap des Bildschirms // per Default gibt es Double Buffering } protected Ellipse2D.Double getCircle() { return (kreis); } public static void main(String[] args) { TexturePaintBsp tbsp = new TexturePaintBsp(); String titel = tbsp.getClass().toString(); if (titel.indexOf("class") != -1) titel = titel.substring(6); JFrame frame = new JFrame(titel); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.getContentPane().add(tbsp,BorderLayout.CENTER); frame.setSize(380,400); frame.setVisible(true); } } 273 Programmieren in Java Abb.: Füllen einer Form mit einem Füllmuster aus einer Bilddatei Transparentes Zeichnen mit Hilfe der Klasse AlphaComposite Für jede geometrische Form kann ein Transparenzwert ("Alphakanal") angegeben werden, der angibt, wie stark "deckend" gezeichnet werden soll. Dazu instanziert man ein Objekt von AlphaComposite und setzt den entsprechenden Zeichenparameter mit der Methode setComposite() des Graphics2D-Kontextes. Bsp.217: Ein rotes, halbtransparentes Rechteck liegt über einem Muster. import java.awt.*; import java.applet.*; import java.awt.geom.*; public class PolyTransp extends Applet { public void paint(Graphics g) { // Erzeuegen eines Graphics2D-Objekt Graphics2D g2d = (Graphics2D) g; g2d.setColor(Color.blue); // Definition der Form GeneralPath einPfad = new GeneralPath(); // Startpunkt festlegen (Ursprung der sichtbaren // Zeichenflaeche des Fensters einPfad.moveTo(getInsets().left,getInsets().top); for (int i = 1, j = 10;i <= 10; i++, j--) { einPfad.lineTo(getSize().width/j,getSize().height/i); einPfad.lineTo(getSize().width/j,getSize().height/j); 217 vgl. pr43100 274 Programmieren in Java } // der Pfad wird geschlossen einPfad.closePath(); // Zeichnen (blau) des Umrisses vom PfadObjekt g2d.setColor(Color.blue); g2d.draw(einPfad); // neues Gradientenobjekt GradientPaint derFarbGradient = new GradientPaint(getInsets().left, // Startpunkt getInsets().top, // Gradient new Color(255,255,255), // Farbe getSize().width, // Endpunkt getSize().height, // Gradient new Color(0,0,0)); // Endfarbe // Gradient festlegen g2d.setPaint(derFarbGradient); // Pfad fuellen g2d.fill(einPfad); // Farbe festlegen (verdraengt den Gradienten) g2d.setColor(Color.red); // Instanz eines Alphakanals laden (Wert: 50 Prozent = 0.5) AlphaComposite transparenzModus = AlphaComposite.getInstance(AlphaComposite.SRC_OVER,0.5f); // Transparenzwert festlegen g2d.setComposite(transparenzModus); // Zeichnen eines Rechtecks (50 Pixel Rand an allen Seiten) g2d.fillRect(getInsets().left + 50, getInsets().top + 50, getSize().width - getInsets().right - 100, getSize().height - getInsets().bottom - 100); // g2d.setColor(Color.red); // g2d.fill(einPfad); } } Abb.: Transparente Überlagerung von Formen Wenn zwei einander überlappende Formen dargestellt werden sollen, muß geklärt werden, wie die Objekte sich zueinander verhalten sollen. Hierzu gibt es die Composite- und CompositeContext-Interfaces. Eine Implementierung dazu ist die AlphaComposite-Klasse, in der die SRC_OVER-Regel die meistgenutzte ist. 275 Programmieren in Java 4.3.6 Text und Fonts unter Java2D Character, Glyphs218 und Fonts Ein String lässt sich auf der Ebene der einzelnen Zeichen auf vielfältige Art und Weise manipulieren, z.B. durch - verschiedene Formatierungen - Ausrichtung - Position - Schriftart - Metrik - Größe - Umrisse Jedes Font-Objekt umfasst diese Attribute, die direkt über von der Font-Klasse zur Verfügung gestellte Methoden ansprechbar sind. Java2D enthält eine erweiterte Font-Klasse. die erheblich weitgehendere Möglichkeiten zur Kontrolle von Schriftarten bereitstellt. Bsp.219: Das folgende Programm listet die von Java2D bereitgestellten Fonts auf. import java.awt.*; /** Lists the names of all available fonts. */ public class ListFonts { public static void main(String[] args) { GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); String[] fontNames = env.getAvailableFontFamilyNames(); System.out.println("Available Fonts:"); for(int i = 0; i < fontNames.length; i++) System.out.println(" " + fontNames[i]); } } Transformations- und Zeichentechnik Die Transformations- und Zeichentechnik von Java2D kann auch auf Textstrings angewendet werden. Bsp.220: Rotation eines Textstrings import java.awt.*; import java.awt.geom.*; import java.awt.event.*; import javax.swing.*; public class RotationBsp extends JPanel { private Ellipse2D.Double kreis = new Ellipse2D.Double(10,10,350,350); private GradientPaint gradient = new GradientPaint(0,0,Color.red,175,175,Color.yellow,true); private Color[] farben = { Color.white, Color.black }; public RotationBsp() 218 Die Vereinigung von dem Zeichen selbst und seinem exakten Aussehen wird Glyph genannt pr43100 220 pr43100 219 276 Programmieren in Java { GraphicsEnvironment env = GraphicsEnvironment.getLocalGraphicsEnvironment(); env.getAvailableFontFamilyNames(); setFont(new Font("Book Antiqua", Font.PLAIN, 90)); } protected void clear(Graphics g) { super.paintComponent(g); // loescht die Pixelmap des Bildschirms // per Default gibt es Double Buffering } protected Ellipse2D.Double getCircle() { return (kreis); } public void paintComponent(Graphics g) { clear(g); Graphics2D g2d = (Graphics2D) g; g2d.setPaint(gradient); g2d.fill(getCircle()); g2d.setPaint(Color.black); g2d.draw(getCircle()); g2d.translate(185.0,185.0); for (int i = 0; i < 16; i++) { g2d.rotate(Math.PI/8.0); g2d.setPaint(farben[i%2]); g2d.drawString("Java",0,0); } } public static void main(String[] args) { RotationBsp rotationBsp = new RotationBsp(); String titel = rotationBsp.getClass().toString(); if (titel.indexOf("class") != -1) titel = titel.substring(6); JFrame frame = new JFrame(titel); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.getContentPane().add(rotationBsp,BorderLayout.CENTER); frame.setSize(380,400); frame.setVisible(true); } } 277 Programmieren in Java Abb.: 4.3.7 2D Bildverarbeitung Zeichnen eines Bilds. Mit Hilfe der Graphics2d-Methode public boolean drawImage(BufferedImage image, BufferedImageOp filter, int left, int top) kann ein BufferedImage ab der oberen linken Ecke (left,top) gezeichnet werden. Erstellen eines BufferedImage aus einem Image-File. public static BufferedImage getBufferedImage(String imageFile, Component c) { Image image = c.getToolkit().getImage(imageFile); MediaTracker tracker = new MediaTracker(c); // nutzt MediaTracker tracker.addImage(image,0); try { tracker.waitForAll(); } catch (InterruptedException ie) { } BufferedImage bufferedImage = new BufferedImage(image.getWidth(c), image.getHeight(c),BufferedImage.TYPE_INT_RGB); Graphics2D g2d = bufferedImage.createGraphics(); g2d.drawImage(image,0,0,c); return bufferedImage); } 278 Programmieren in Java BufferedImage-Objekte Ein BufferedImage-Objekt besteht aus einem Raster-Objekt und einem ColorModelObjekt. Der Raster kümmert sich um die Bildverwaltung, wobei der DataBuffer die Bilddaten enthält und das SampleModel angibt, wie die Bilddaten zu interpretien sind. Das ColorModel gibt an, wie die Pixeldaten in Bezug auf ihre Farben ausgewertet werden sollen. Ein BufferedImage kann als OffScreen-Puffer dienen. Damit kann man das Objekt schon im Speicher rendern, bevor man das Bild oder nur den benötigten Ausschnitt auf den Bildschirm kopiert. 4.3.8 Animationen mit Java2D Man kann mit Java2D einfache Animationen erstellen, z.B.: 279 Programmieren in Java 5. AWT 5.1 Bestandteile des AWT In Java wird die Kommunikation mit dem Anwender hauptsächlich über ein eigenes Konzept realisiert – dem Abstract Windowing Toolkit. Das AWT besitzt zur Kommunikation mit dem Anwender ein Application Programming Interface (API). Darüber können allgemeine Komponenten der Benutzeroberfläche, z.B. Schaltflächen oder Menüs, plattformunabhängig genutzt werden. 5.2 Die AWT-Komponenten Die AWT stellt Komponenten für den Anwender bereit. Die von Anfang an vorhandenen Komponenten221 des AWT sind: Schaltflächen (Buttons), Labels, Kontrollkästchen (Checkbuttons), Optionsfelder (Radiobuttons), Listen, Auswahlfelder, Textfelder, Menüs, Zeichenbereiche. Zusätzlich gibt es Container, in die die Komponenten zum Erstellen vollständiger und sinnvoller Anwendungsschnittstellen integriert sein müssen. Die Container im AWT sind Fenster, Panels, Frames, Dialoge. Ein weiterer Bestandteil der AWT sind Layout-Manager. Ein Layout-Manager ist in jedem Container enthalten. Er findet – angepaßt an die jeweilige Situation – automatisch heraus, an welche Stelle die Komponenten am besten passen. Der AWT stellt 5 verschiedene Typen von Layout-Managern zur Verfügung: Flow-, Border-, Grid-, Gridbag-Layout. Schließlich stellt AWT die Mittel zur Reaktion auf Ereignisse zur Verfügung, die vom Anwender über Komponenten ausgelöst werden können. Die Wurzel fast aller AWT-Komponenten ist die Klasse Component. Sie enthält die grundlegenden Anzeige- und Event-Handling-Funktionen. Component Canvas Container Panel Applet TextComponent Window Frame Button TextField Dialog Abb. AWT-Klassenhierarchie 221 Java 1.2 und das Swing-Konzept erweitern diese Komponenten noch einmal um einen satz von neuen und ergänzenden Komponenten. 280 Programmieren in Java 5.2.1 Schaltflächen (Buttons) Component Button << Konstruktoren >> public Button() public Button(String label) << Methoden >> public void addActionListener(ActionListener l) public void addNotify() public String getActionCommand() public String getLabel() protected String paramString() protected void processActionEvent(ActionEvent e) protected void processEvent(AWTEvent e) public void removeActionListener(ActionListener l) public void setActionCommand(String kommando) public void setLabel(String label) Abb. Die Klasse Button Erzeugen. Schaltflächen (Buttons) sind Elemente einer grafischen Benutzeroberfläche, die auf Knopfdruck Aktionen in der Fensterklasse auslösen. Mit den folgenden Konstruktoren kann eine Schaltfläche erstellt werden: - Button() erzeugt eine leere Schaltfläche ohne Beschriftung Button(String label) erzeugt eine Schaltfläche mit der durch das String-Objekt bezeichneten Beschriftung. In einem Applet222 reicht dafür aus: add(new Button("Auf los geht's los!"));. Beschriftungen einer Schaltfläche können dynamisch zur Laufzeit gesetzt und wieder verändert werden. Dazu dient die Methode: public void setLabel(String label). Welche Beschriftung die Schaltfläche angenommen hat, ermittelt die Methode public String getLabel();. Reaktionen auf die Betätigung von Schaltflächen (JDK 1.1). Falls ein Button gedrückt wird, sendet es ein Action-Event an seine Ereignisempänger. Diese müssen das Interface ActionListener implementieren und sich durch den Aufruf von „addActionListener“ registrieren: public void addActionListener(ActionListener l) public void removeActionListener(ActionListener l) Das Action-Event führt im Ereignisempfänger zum Aufruf der Methode public void actionPerformed(ActionEvent ae) ,die die Methode getActionCommand aufrufen kann, mit der die Beschriftung des Button abgefragt werden kann. Falls das Action-Kommando nicht mit der Beschriftung identisch ist, 222 Abgeleitet von der Panel-Klasse 281 Programmieren in Java kann es in der Button-Klasse durch „public void setActionCommand(String kommando)“ geändert werden. Bsp.: Setzen von Hintergrundfarben nach verschiedenen Buttonklicks223 import java.awt.*; import java.awt.event.*; public class SchaltApplet extends java.applet.Applet { private Button schalt1 = new Button("Rot"); private Button schalt2 = new Button("Blau"); private Button schalt3 = new Button("Gruen"); private Button schalt4 = new Button("Gelb"); private Button schalt5 = new Button("Schwarz"); public void init() { setBackground(Color.white); schalt1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setBackground(Color.red); } }); schalt2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setBackground(Color.blue); } }); schalt3.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setBackground(Color.green); } }); schalt4.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setBackground(Color.yellow); } }); schalt5.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { setBackground(Color.black); } }); this.setLayout(new BorderLayout(15,15)); Panel p = new Panel(); p.setLayout(new FlowLayout(FlowLayout.CENTER,15,15)); p.add(schalt1); p.add(schalt2); p.add(schalt3); p.add(schalt4); p.add(schalt5); this.add("South",p); // this.pack(); } } 223 pr52105 282 Programmieren in Java Reaktionen auf die Betätigung von Schaltflächen (JDK 1.0). Die Komponenten innerhalb der AWT-Oberfläche besitzen eine action()-Methode, die aufgerufen wird, wenn bei einer Komponente eine Aktion ausgführt wurde. Beim Betätigen (Auslösen) einer Schaltfläche wird die action()-Methode aufgerufen. Die action()-Methode gehört zu den Ereignisbehandlungsmethoden des Event Handling. Die Syntax der action()-Methode ist bei allen Komponenten identisch: public boolean action(Event ereignis, Object welcheAktion). Ereignis: Ereignis, das bei der Komponente aufgetreten ist welcheAktion: steht für das, was geschehen ist Bei Schaltflächen ist die Art der Aktion (welcheAktion) ganz einfach über das Label (Beschriftung) der Schaltfläche auszuwerten, die ausgelöst wurde. Der „Event“Parameter enthält spezifische Informationen über die Aktion, z.B.: event.target (Komponente, bei der die Aktion eingetreten ist) event.when (Zeitpunkt, zu dem die Aktion geschehen ist) Mit dem instanceof-Operator kann die event.target-Variable überprüft werden, ob die Aktion auch für das Objekt erfolgt, das gewünscht wird. Bsp.: Setzen von Hintergrundfarben nach verschiedenen Buttonklicks224 import java.awt.*; public class KnopfAktApplet extends java.applet.Applet { public void init() { setBackground(Color.white); this.setLayout(new BorderLayout(15,15)); Panel p = new Panel(); p.setLayout(new FlowLayout(FlowLayout.CENTER,15,15)); p.add(new Button("Rot")); p.add(new Button("Blau")); p.add(new Button("Gruen")); p.add(new Button("Gelb")); p.add(new Button("Schwarz")); this.add("South",p); } public boolean action(Event e, Object arg) { // Test auf Schaltflaechenaktion if (e.target instanceof Button) aendereFarbe((String) arg); return true; } void aendereFarbe(String knopfName) { // Aendern der Hintergrundfarbe if (knopfName.equals("Rot")) setBackground(Color.red); else if (knopfName.equals("Blau")) setBackground(Color.blue); else if (knopfName.equals("Gruen")) setBackground(Color.green); else if (knopfName.equals("Gelb")) setBackground(Color.yellow); else if (knopfName.equals("Schwarz")) setBackground(Color.black); else; } } 224 vgl. pr52105 283 Programmieren in Java 5.2.2 Labels Labels sind Zeichenketten zur Beschriftung anderer Komponenten der Benutzeroberfläche. Ein Label umfaßt eine Zeile Text, die auf dem Bildschirm angezeigt wird und vom Programm veränder werden kann. Konstruktoren. public Label() public Label(String text) public Label(String text, int ausrichten) Der Parameter „ausrichten“ bestimmt die Ausrichtung des Texts. Hier kann eine der Konstanten Label.LEFT, Label.RIGHT, Label.CENTER übergeben werden. Methoden. public void setText(String text) // Zugriff auf die Textzeile des Label public String getText() // Zugriff auf die Textzeile des Label public void setAlignment(int ausrichten) // Ausrichten der Textzeile public int getAlignment() // Ausrichten der Textzeiele 5.2.3 Kontrollkästchen und Optionsfelder Erzeugen von Kontrollkästchen. Ein Kontrollkästchen (Checkbutton / Checkbox) hat zwei Bestandteile: ein Label (Text, der neben dem Kontrollkästchen angelegt wird) und einem Zustand (boolesche Variable, die angibt, ob die Box eingeschaltet wurde oder nicht225). Zum Erzeugen von Kontrollkästchen gibt es 5 Konstruktoren - - Checkbox() (Kontrollkästchen ohne Label) Checkbox(String label) (Kontrollkästchen mit Beschriftung) Checkbox(String label, boolean zustand) Checkbox mit Vorgabe des Anfangszustands Checkbox(String label, CheckboxGroup cbg, boolean zustand) (Kontrollkästchen, das jenach gesetzter boolescher Zustandsvariable vorselektiert (true) ist oder nicht (false). Das mittlere Argument ist bei Kontrollkästchen immer auf Null gesetzt. Falls das nicht der Fall ist, dient der Parameter cbg zum Gruppieren von Radiobuttons. Checkbox(String label, boolean zustand, CheckboxGroup cbg) Plazieren von Kontrollkästchen in einem Container. Es erfolgt über die Methode add(), z.B.: add(new Checkbox("Java"));. Überprüfen und Setzen von Kontrollkästchen. Mit der Methode public boolean getState() kann überprüft werden, ob ein Kontrollkästchen angeglickt wurde. Die Methode public void setState(boolean zustand) setzt den Status (true für gesetzt). Die getlabel()-Methode fragt das Label des jeweiligen Kontrollkästchens ab. Reaktion auf Zustandsänderungen. Eine Checkbox generiert ein Item-Event, wenn die Markierung vom Anwender gesetzt oder zurückgenommen wird. Zur Reaktion auf dieses Event ist durch den Aufruf von addItemListener ein Objekt, das das Interface ItemListener implementiert, bei der Checkbox zu registrieren. In 225 Standardmäßig ist die Box ausgeschaltet (Wert ist false oder „off“). 284 Programmieren in Java diesem Fall wird ein Ereignisempfänger die Methode itemStateChanged mit einem Argument vom Typ ItemEvent aufgerufen: public abstract void itemStateChanged(ItemEvent e) Über das ItemEvent kann die Methode getItemSelectable aufgerufen werden, mit der ermittelt werden kann, durch welche Checkbox das Ereignis ausgelöst wurde: public ItemSelectable getItemSelectable(). Der Rückgabewert kann in ein Objekt des Typs Checkbox konvertiert werden. Durch Aufruf von getState() kann der aktuelle Zustand bestimmt werden. Bsp.: import java.awt.*; import java.awt.event.*; public class PR52301 extends Frame { Checkbox cb1 = new Checkbox("Checkbox 1"), cb2 = new Checkbox("Checkbox 2",true), cb3 = new Checkbox("Checkbox_3",false); public PR52301() { setLayout(new GridLayout(3,1)); setBackground(Color.lightGray); cb1.addItemListener(new CBL()); cb2.addItemListener(new CBL()); cb3.addItemListener(new CBL()); add(cb1); add(cb2); add(cb3); } public class CBL implements ItemListener { public void itemStateChanged(ItemEvent e) { Checkbox cb = (Checkbox) e.getItemSelectable(); System.out.println(cb.getLabel() + ": " + cb.getState()); } } public static void main(String args[]) { PR52301 f = new PR52301(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(100,150); f.setVisible(true); } } Defintion zur CheckboxGroup. Eine CheckboxGroup ist die Java-Variante einer Gruppe von Radiobuttons (Optionsfelder), von den genau immer einer aktiviert wird. Wird eine anderer Button aktiviert, so änder er seinen internen Status auf „true“ und der zuvor gesetzte wird „false“. Eine CheckboxGroup ist nichts Anderes als eine Checkbox, deren CheckboxGroup-Parameter gesetzt ist. Erzeugen von Optionsfeldern (Radiobuttons). Erforderlich ist zunächst eine Checkbox-Gruppe, die über die betreffenden Konstruktoren erstellt werden kann. Dieser Gruppe werden die einzelnen Optionsfelder hinzugefügt. 285 Programmieren in Java Plazieren von Optionsfeldern in einem Container. Auch Optionsfelder müssen wie alle Komponenten nach der Erzeugung in einen Container gebracht werden. Das geschieht über die Methode add(). Setzen und Überprüfen von Optionsfeldern. Die Methoden getState() und getLabel() funktionieren auch bei Optionsfeldern. Zum Zugriff auf die Gruppe eines Optionsfelds und zum Ändern gibt es die Methoden: public CheckboxGroup getCheckboxGroup() public void setCheckboxGroup(CheckboxGroup g) Zum Holen und Setzen des momentan ausgewählten Optionsfelds dienen public Checkbox getCurrent() public void setCurrent(Checkbox box) Bsp.: import java.awt.*; import java.awt.event.*; public class PR52302 extends Frame { CheckboxGroup cbg = new CheckboxGroup(); public PR52302() { setLayout(new GridLayout(3,1)); setBackground(Color.lightGray); Checkbox cb1 = new Checkbox("rot",cbg,false); Checkbox cb2 = new Checkbox("blau",cbg,false); Checkbox cb3 = new Checkbox("gruen",cbg,false); cb1.addItemListener(new CBL()); cb2.addItemListener(new CBL()); cb3.addItemListener(new CBL()); add(cb1); add(cb2); add(cb3); } public class CBL implements ItemListener { public void itemStateChanged(ItemEvent e) { Checkbox cb = (Checkbox) e.getItemSelectable(); if (cb.getLabel() == "rot") { if (cb.getState()) { System.out.println("rot"); } } if (cb.getLabel() == "blau") { if (cb.getState()) { System.out.println("blau"); } } if (cb.getLabel() == "gruen") { if (cb.getState()) { System.out.println("gruen"); } } } } public static void main(String args[]) { PR52302 f = new PR52302(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); 286 Programmieren in Java f.setSize(100,150); } } f.setVisible(true); Reaktionen auf Kontrollfelder und Radiobuttons im JDK1.0. Kontrollfelder und Radiobuttons verfügen wie alle Komponenten über die action()-Methode. Sie wird bei einer Auswahl aufgerufen, sobald ein Feld selektiert wird. Bsp.226: import java.awt.*; public class CheckAktApplet extends java.applet.Applet { CheckboxGroup meineCheckboxGruppe = new CheckboxGroup(); public void init() { add(new Checkbox("Blau",meineCheckboxGruppe,false)); add(new Checkbox("Rot",meineCheckboxGruppe,false)); add(new Checkbox("Gruen",meineCheckboxGruppe,false)); add(new Checkbox("Gelb",meineCheckboxGruppe,true)); // Hintergrundfarbe passend zur Voreinstellung setBackground(Color.yellow); } public boolean action(Event e, Object welcheAktion) { // Test auf Kontrollkaestchen oder Radiobutton if (!(e.target instanceof Checkbox)) return false; // Instanz der Ereignisse Checkbox welcheAuswahl = (Checkbox) e.target; // Status des Radiobuttons zur Kontrolle boolean CheckboxStatus = welcheAuswahl.getState(); // Auswahl Blau if (welcheAuswahl.getLabel() == "Blau") { if (CheckboxStatus) setBackground(Color.blue); return true; } // Auswahl Rot if (welcheAuswahl.getLabel() == "Rot") { if (CheckboxStatus) setBackground(Color.red); return true; } // Auswahl Gruen if (welcheAuswahl.getLabel() == "Gruen") { if (CheckboxStatus) setBackground(Color.green); return true; } if (welcheAuswahl.getLabel() == "Gelb") { if (CheckboxStatus) setBackground(Color.yellow); return true; } return false; // keine der Optionen wird aufgefangen } } 226 pr54305 287 Programmieren in Java 5.2.4 Auswahlmenüs Es handelt sich um Popup- bzw. Pulldown-Menüs, die sich beim Anklicken öffnen und mehrere auszuwählende Optionen anzeigen, von denen dann eine Option ausgewählt werden kann. Konstruktor: public Choice() // erstellt ein Choice-Objekt mit einer leeren Liste. Bearbeiten der Liste (Combobox) zum Auswahlmenü: public void addItem(String item) Jeder Aufruf von addItem hängt das übergebende Element „item“ an das Ende der Liste an. Die Elemente werden in der Reihenfolge der Aufrufe von addItem eingefügt. Zugriff auf die Elemente der Combobox: public int getSelectedIndex() liefert den Index des ausgewählten Elements. Durch Übergabe dieses Werts (an „getItem“ kann das aktuelle Element beschafft werden: public String getItem(int n). Über public String getSelectedItem() kann ohne Kenntnis der internen Nummer (Index) das ausgewählte Element beschafft werden. Programmseitige Auswahl eines Elements: public void select(int) // Auswahl über den Index public void select(String s) // Auswahl über den angegebenen Wert von String Ereignisbehandlung. Ebenso wie eine Checkbox sendet auch ein Choice-Element „Item“-Ereignisse, wenn ein Element ausgewählt wurde. Zur Reaktion auf dieses Element muß ein ItemListener durch Aufruf von public void addItemListener(ItemListener l) registriert sein. Ein „ItemEvent“ wird immer dann gesendet, wenn ein Element ausgewählt wurde. In diesem Fall wird ein Ereignisempfänger die Methode itemStateChanged mit einem Argument vom Typ ItemEvent aufrufen: public abstract void itemStateChanged(ItemEvent e). Das „ItemEvent“ stellt die Methode public ItemSelectable getItemSelectable() zur Verfügung, mit der ermittelt werden kann, durch welches Choice-Element das Ereignis ausgewählt wurde. Zusätzlich gibt es public Object getItem(), die das ausgewählte Element als String zur Verfügung stellt. Bsp.: import java.awt.*; import java.awt.event.*; public class PR14174 extends Frame { TextField t = new TextField(30); Choice auswahl = new Choice(); public PR14174() { auswahl.addItem("rot"); auswahl.addItem("blau"); auswahl.addItem("gruen"); auswahl.addItem("pink"); auswahl.addItem("orange"); auswahl.addItem("gelb"); auswahl.addItem("cyan"); add(auswahl); pack(); } public static void main(String args[]) { PR14174 f = new PR14174(); f.addWindowListener( 288 Programmieren in Java new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setBackground(Color.lightGray); f.setVisible(true); } } 5.2.5 Listenfelder Eine Instanz der Klasse List ist eine listenartige Darstellung von Werten, aus denen Anwender einen oder mehrere auswählen kann. Anders als ein Choice-Element ist ein Element der Klasse List ständig in voller Größe auf dem Bildschirm sichtbar. Erzeugen von Listenfeldern. public List() // legt eine leere Liste an, deren dargestellte Größe durch den // Layoutmanager begrenzt wird. public List(int groesse) // Der Parameter groesse legt die Anzahl der angezeigten Zeilen fest. public List(int groesse, boolean multiselect) // ist multiselect "true", kann der Anwender nicht nur ein Element, // sondern auch andere Elemente auswählen Mehrfachauswahl ausgewählter Elemente. „List“ bietet Funktionalität wie Choice an. Zusätzlich stehen zur Verfügung: nahezu dieselbe public int[] getSelectedIndexes() // liefert einen Array mit den Indexpositionen der ausgewählten // Elemente. public String[] getSelectedItems() // liefert eine Liste der Werte. Auswahl einzelner Elemente. public void select(int index) public void deselect(int index) Entfernen, Verändern von Elementen. public void delItem(int index) // löscht das Element an der Position index public void remove(int index) // löscht das Element an der Position index public void replaceItem(String neuerWert,int index) // ersetzt das Element an der Position index durch die Postion // neuerWert. Item- und Action-Ereignisse. Ein Listenelement sendet Item- und ActionEreignisse. Ein Action-Ereignis wird generiert, wenn ein Listenelement durch Doppelklick ausgewählt wurde. Ein Item-Ereignis wird ausgelöst, nachdem in der Liste ein Element ausgewählt wurde. Bsp.: import java.awt.*; import java.awt.event.*; public class PR14177 extends Frame { String[] farben = {"rot","blau","gruen","pink","orange", "gelb","cyan"}; List lst = new List(6,true); public PR14177() 289 Programmieren in Java { setLayout(new FlowLayout()); for (int i = 0; i < 7; i++) lst.addItem(farben[i]); add(lst); lst.addItemListener(new ILL()); lst.addActionListener(new ALL()); pack(); } public class ILL implements ItemListener { public void itemStateChanged(ItemEvent e) { List lst = (List) e.getItemSelectable(); String str = lst.getSelectedItem(); int pos = ((Integer) e.getItem()).intValue(); System.out.println("event.getSelectedItem: " + str + " " + pos); } } class ALL implements ActionListener { public void actionPerformed(ActionEvent e) { Object obj = e.getSource(); if (obj instanceof List) { System.out.println("Listenfeld-Aktion: " + e.getActionCommand()); } } } public static void main(String args[]) { PR14177 f = new PR14177(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { // f.setVisible(false); System.exit(0); } }); f.setBackground(Color.lightGray); // f.setSize(100,160); f.setVisible(true); } } 5.2.6 Textbereiche und Textfelder Das AWT verfügt über Klassen zur Eingabe von Text (Klasse TextField für Textfelder und Klasse TextArea für Textbereiche). Textfelder begrenzen die Menge des einzugebenden Textes und können nicht gescrollt werden. In Textbereichen können mehrere Zeilen verarbeitet werden, außerdem sind Textbereiche scrollbar. TextField Ein TextField dient zur Darstellung von Text. Anwender und Programm können den dargestellten Text einlesen und verändern. Erzeugen von Textfeldern. Es stehen folgende Konstruktoren zur Verfügung: 290 Programmieren in Java public TextField() erzeugt ein leeren Textfeld, in das der Anwender Text eingeben kann. public TextField(int anzSpalten) Erzeugt ein leeres Textfeld bestimmter Breite. Die Anzahl einzugebender Zeichen ist nicht begrenzt. Ist der Text länger, so scrollt er innerhalb des Textfelds public TextField(String text) Über den Parameter text kann eine Zeichenkette vorgegeben werden, die beim aufruf des Textfelds vorgelegt wird. public TextField(String text, int anzSpalten) Methoden zum Zugriff bzw. Verändern von Textfeldern. public String getText(); public void setText(String text) Mit public int getColumns() kann die Anzahl der darstellbaren Zeichen eines Textfelds abgefragt und mit public void setColumns(int anzSpalten) verändert werden. Markieren von Text. public void selectAll() //markiert den kompletten Bereich public void select(int erstes, int letztes) //markiert den Bereich von „erstes“ bis „letztes“. Das „Zählen“ beginnt bei 0. Mit public int getSelectionStart() bzw. public int getSelectionEnd() kann die aktuelle Auswahl abgefragt werden. Mit public int getCaretPosition() und public void setCaretPosition(int position) kann auf die aktuelle Cursorposition zugegriffen werden. Bearbeiten von Textfeldern. Über public void setEditable(boolean erlaubt) // Aufruf mit Parameter false unterbindet weitere Eingaben bzw. public boolean isEditable() // Abfrage des aktuellen Status kann man die Veränderung von Text verhindern. Mit public void setEchoChar(char z) (Übergabe eines Zeichens, das beim Tastendruck anstelle des vom anwender eingegebenen Zeichens ausgegeben wird) bzw. public char getEchoChar() besteht die Möglichkeit verdeckter Eingaben (z.B. für Paßwörter). Generieren eines Action-Event erfolgt beim Drücken der Enter-Taste innerhalb des Textfelds. Die Methode String getActionCommand() des Action-Events liefert den Inhalt des Textfelds. Generieren eines Text-Ereignisses bei jeder Änderung im Textfeld. Ein Empfänger kann mit der Methode addTextListener vom Textfeld registriert werden. Als Argument wird ein Objekt erwartet, das das Interface TextListener implementiert. Beim Auftreten eines Text-Ereignisses wird vom TextListener die Methode 291 Programmieren in Java textValueChanged mit einem „TextEvent“ als Parameter aufgerufen. „TextEvent“ ist aus „AWTEvent“ abgeleitet und erbt die Methoden getID(), getSource(). Typischerweise wird innerhalb von public void textValueChanged(TextEvent e) mit getSource() das zugehörige Textfeld beschafft und mit getText() auf seinen Inhalt zugegriffen. Bsp.: import java.awt.*; import java.awt.event.*; public class PR14170 extends Frame { Button b1 = new Button("Hole Text"), b2 = new Button("Setze Text"); TextField t1 = new TextField(30), t2 = new TextField(30), t3 = new TextField(30); String s = new String(); public PR14170() { setLayout(new FlowLayout()); b1.addActionListener(new B1A()); b2.addActionListener(new B2A()); t1.addTextListener(new T1()); t1.addActionListener(new T1A()); add(b1); add(b2); add(t1); add(t2); add(t3); } class T1 implements TextListener { public void textValueChanged(TextEvent e) { t2.setText(t1.getText()); } } class T1A implements ActionListener { private int zaehler = 0; public void actionPerformed(ActionEvent e) { t3.setText("t1 ActionEvent " + zaehler++); } } class B1A implements ActionListener { public void actionPerformed(ActionEvent e) { s = t1.getSelectedText(); if (s.length() == 0) s = t1.getText(); t1.setEditable(true); } } class B2A implements ActionListener { public void actionPerformed(ActionEvent e) { t1.setText("Eingefuegt durch Button 2: " + s); t1.setEditable(false); } 292 Programmieren in Java } public static void main(String args[]) { PR14170 f = new PR14170(); f.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(300,200); f.setBackground(Color.lightGray); f.setVisible(true); } } TextArea Mit dieser Klasse können mehrzeilige Textfelder erzeugt werden. Zusätzlich kann der Text in allen Richtungen „scrollen“, so daß auch größere Texte bequem bearbeitet werden können. Konstruktoren. public TextArea() //erzeugt ein leeres TextArea-Objekt in einer vom System vorgegebenen Größe. public TextArea(int zeilen, int spalten) //erzeugt eine leeres TextArea-Objekt mit einer bestimmten Anzahl von sichtbaren Zeilen //und Spalten. public TextArea(String text, int zeilen, int spalten) //erzeugt ein TextArea-Objekt mit Text. public TextArea(String text, int zeilen, int spalten, int scrollbars) //erzeugt ein TextArea-Objekt, in dem über scrollbars die Ausstattung der TextArea mit //Schieberegistern festgelegt werden kann. Dazu stellt TextArea die Konstanten //SCROLLBARS_BOTH, SROLLBARS_NONE, SCROLLBARS_VERTICAL_ONLY, //SCROLLBARS_HORIZONTAL_ONLY zur Verfügung, die als Argumente übergeben werden können. Zusätzliche in der Klasse bereitgestellte Methoden zum Verändern von Teilen des Texts. Neben den bereits in der Klasse TextField angegebenen Methoden stehen zur Verfügung: public void insert(String str, int pos) //verändert die Zeichenkette str ab Position pos. Der dahinter stehende Text wird entsprechend //nach hinten verschoben. public void append(String str) //hängt den über str übergebenen Text hinten an. public void replaceRange(String text, int start, int ende) //ersetzt den Text zwischen start und end durch den String text. Senden von Text-Events. Ein Objekt der Klasse TextArea sendet Text-Events, so wie es bei TextField beschrieben wurde. 5.2.7 Schieber und Bildlaufleisten Listen und Textbereiche besitzen standardmäßig die Eigenschaft bei Bedarf nicht in den Anzeigebereich passenden Inhalt über Bildlaufleisten und Schieber zu scrollen. Bildlaufleisten können auch individuell erstellt werden und dienen zur (quasi-) analogen Anzeige bzw. Eingabe eines Werts aus einem vorgebenen Wertebereich. 293 Programmieren in Java Der Schieberegler kann horizotal und vertikal angeordnet werden und besitzt einen Schieber, dessen Größe veränderlich ist. Der interne Wert eines Schiebereglers und die Anzeigeposition seines Schiebers sind untrennbar miteinander verbunden. Ändert der Anwender die Position des Schiebers, ändert sich automatisch auch sein interner Wert. Wird vom Programm der Wert verändert, führt das automatisch zu einer Positionierung des Schiebers. Zur Änderung des aktuellen Werts einer Bildlaufleiste können drei verschieden Teile der Bildlaufleiste verwendet werden: - Pfeile an beiden Enden zum Erhöhen / Erniedrigen des Werts um eine Einheit (standardmäßig 1) Ein Bereich in der Mitte zum Erhöhen / Erniedrigen eines Werts um je eine größere Einheit (standardmäßig 10) Ein Bildlauffeld (Schieber) in der Mitte, der den momentan gewählten Wert anzeigt. Durch Verschieben des Bildlauffelds mit dem Mauszeiger kann innerhalb der Bildlaufleiste ein anderer Wert gewählt werden. Nötig ist nur die Festlegung eines Höchst- und Mindestwerts für die Bildlaufleiste. Der Rest wird von Java erledigt. Konstruktoren. public Scrollbar() //erzeugt einen vertikalen Schieberegler mit 0,0 als anfänglichen Höchst-, Mindestwert. public Scrollbar(int orientierung) //orientierung bezeichnet die Ausrichtung und kann über Scrollbar.HORIZONTAL bzw. //Scrollbar.VERTICAL festgelegt werden. public Scollbar(int orientierung, int wert, int bh, int minimum, maximum) //orientierung...bestimmt die Ausrichtung des Schiebereglers //wert...Anfangswert des Schiebereglers (Wert zwischen dem Höchst- und Mindestwert //bh...Breite bzw. Höhe der dem Schieberegler zugeteilten Box (Seite) //minimum, maximum...bezeichnet den Mindest-, Höchstwert des Schiebereglers int Methoden der Scrollbar-Klasse versorgen die Handhabung der Werte des Schiebereglers: Methode public public public public public public Bedeutung Zugriff auf den aktuellen Wert des Schiebereglers Setzt den aktuellen Wert des Schiebereglers Bestimmt die Ausrichtung des Schiebereglers Zugriff auf den Mindestwert Zugriff auf den Höchstwert Zugriff der Parameter, die die Stärke der Veränderung des Werts beim Klicken auf die Buttons bzw. die Schaltfläche zwischen Schieber und Button bestimmen. public void setUnitIncrement(int l) Veränderung der Parameter, die die Stärke der Veränderung des Werts beim Klicken auf die Buttons zwischen Schieber und Button bestimmen public int getBlockIncrement() Zugriff der Parameter, die die Stärke der Veränderung des Werts beim Klicken auf die Buttons bzw. die Schaltfläche zwischen Schieber und Button bestimmen public void setBlockIncrement(int l) Veränderung der Parameter, die die Stärke der Veränderung des Werts beim Klicken auf die Schaltfläche zwischen Schieber und Button bestimmen int getValue() void setValue(int wert) int getOrientation() int getMinimum() int getMaximum() int getUnitIncrement() Ereignisse. Ein Scrollbar sendet Adjustment-Ereignisse an seine Ereignisempfänger. Diese müssen das Interface AdjustmentListener implementieren und sich durch Aufruf von public void addAdjustmentListener(AdjustmentListener l) registrieren. Das Adjustment-Ereignis führt im Ereignisempfänger zum Aufruf der Methode: 294 Programmieren in Java public abstract void adjustmentValueChanged(AdjustmentEvent e), die ein AdjustmentEvent übergeben bekommt. Dieses besitzt die Metode getAdjustable(), mit der der auslösende Scrollbar bestimmt werden kann. Zusätzlich gibt es die Methode getAdjustmentType(), die Auskunft darüber gibt, welche Benutzeraktion zur Auslösung des Ereignisses führte. Konstanten, die von getAdjustmentType Bedeutung zurückgegeben werden AdjustmentEvent.UNIT_INCREMENT Der Wert durch Klicken eines „Button“ um eine Einheit erhöht. AdjustmentEvent.UNIT_DECREMENT Der Wert durch Klicken eines „Button“ um eine Einheit erniedrigt. AdjustmentEvent.BLOCK_INCREMENT AdjustmentEvent.BLOCK_DECREMENT AdjustmentEvent.TRACK Der Wert wurde durch Klicken der Schaltfläche zwischen Button und Schieber um eine Seite erhöht Der Wert wurde durch Klicken der Schaltfläche zwischen Button und Schieber um eine Seite vermindert Der Wert wurde durch Ziehen des Schiebers verändert Bsp.: import java.awt.*; import java.awt.event.*; public class PR14175 extends Frame { public PR14175() { Panel p = new Panel(); p.setLayout(new BorderLayout()); Scrollbar hsb = new Scrollbar(Scrollbar.HORIZONTAL,1,10,1,100); hsb.addAdjustmentListener(new HsbAL()); p.add("South",hsb); Scrollbar vsb = new Scrollbar(Scrollbar.VERTICAL,1,10,1,100); vsb.addAdjustmentListener(new VsbAL()); p.add("East",vsb); add(p); pack(); } public class HsbAL implements AdjustmentListener { public void adjustmentValueChanged(AdjustmentEvent e) { switch(e.getAdjustmentType()) { case AdjustmentEvent.UNIT_INCREMENT: System.out.println("Adjustment.UNIT_INCREMENT"); break; case AdjustmentEvent.UNIT_DECREMENT: System.out.println("Adjustment.UNIT_DECREMENT"); break; case AdjustmentEvent.BLOCK_INCREMENT: System.out.println("Adjustment.BLOCK_INCREMENT"); break; case AdjustmentEvent.BLOCK_DECREMENT: System.out.println("Adjustment.BLOCK_DECREMENT"); break; case AdjustmentEvent.TRACK: System.out.println("Adjustment.TRACK"); break; } System.out.println("Wert: " + e.getValue()); } 295 Programmieren in Java } public class VsbAL implements AdjustmentListener { public void adjustmentValueChanged(AdjustmentEvent e) { switch(e.getAdjustmentType()) { case AdjustmentEvent.UNIT_INCREMENT: System.out.println("Adjustment.UNIT_INCREMENT"); break; case AdjustmentEvent.UNIT_DECREMENT: System.out.println("Adjustment.UNIT_DECREMENT"); break; case AdjustmentEvent.BLOCK_INCREMENT: System.out.println("Adjustment.BLOCK_INCREMENT"); break; case AdjustmentEvent.BLOCK_DECREMENT: System.out.println("Adjustment.BLOCK_DECREMENT"); break; case AdjustmentEvent.TRACK: System.out.println("Adjustment.TRACK"); break; } System.out.println("Wert: " + e.getValue()); } } public static void main(String args[]) { PR14175 f = new PR14175(); f.addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setBackground(Color.lightGray); f.setVisible(true); } } 5.2.8 ScrollPane Ein ScrollPane ist ein Container für automatisches horizontales und vertikales Scrolling. ScrollPane unterscheidet sich durch zwei Eigenschaften von einem gewöhnlichen Panel: - Es kann genau ein Dialogelement aufnehmen und benötigt keinen eigenen Layoutmanager Es ist in der Lage, eine virtuelle Ausgabefläche zu verwalten, die größer ist als die auf dem Bildschirm zur Verfügung stehende Ausgabefläche. Die von einem ScrollPane angezeigte Komponente arbeitet mit einer virtuellen Ausgabefläche (und merkt nichts von evtl. Größenbeschränkungen auf dem Bildschirm). Falls die benötigte Ausgabefläche größer ist als die anzeigbare, blendet ein ScrollPane automatisch die erforderlichen Schieberegister ein, um Dialogelemente horizontal und vertikal verschieben zu können. Konstruktoren. public ScrollPane() public ScrollPane(int scrollbarDisplayPolicy) //srollbarDisplayPolicy definiert die Strategie zur Anzeige der Schieberegler entsprechend 296 Programmieren in Java //den in der folgende Liste aufgelisteten Konstanten: ScrollPane.SCROLLBARS_AS_NEEDED ScrollPane.SCROLLBARS_ALWAYS ScrollPane.SCROLLBARS_NEVER Die Schieberegler werden genau dann angezeigt, wenn es erforderlich ist, d.h.: Es wird mehr Platz benötigt, als für sie zur Anzeige zur Verfügung steht. Die Schieberegler werden immer angezeigt Die Schieberegler werden nie angezeigt, der Bildschirm kann nur vom Programm aus verschoben werden 5.2.9 Zeichenbereiche Ein Canvas ist ein frei definiertes Dialogelement, das in der Grundversion praktisch keine Funktionalität zur Verfügung stellt. Damit ein Canvas etwas Sinnvolles tun kann, muß daraus eine eigene Klasse abgeleitet werden. Konstruktor: public Canvas() Die Methode paint(Graphics g). Durch Überlagerung von public void paint(Graphics g) sorgt eine Canvas-Komponente für die Darstellung auf dem Bildschirm. Die vom System bereitgestellte Version zeichnet nur die Ausgabefläche in der aktuellen Hintergrundfarbe. Eine überlagerte Version kann hier natürlich ein beliebig komplexes Darstellungsverhalten erzielen. Der Punkt (0,0) des übergebenen Graphics-Objekts entspricht dabei der linken oberen Ecke des Ausgabebereichs. Ereignisse. Da die Klasse Canvas aus Component abgeleitet ist, bekommt ein Canvas-Objekt alle Ereignisse zugestellt, die auch an eine Komponente gehen. Hierzu zählen Tastatur-, Maus-, Mausbewegungs-, Fokusund Komponentenereignisse. Bsp.: Die von Canvas abgeleitete Klasse ZeichneCanvas import java.awt.*; import java.awt.event.*; public class ZeichneCanvas extends Canvas { // Instanzvariable public boolean zeichneObjekte; private Font font; // Konstruktoren public ZeichneCanvas() { this(400,400); } public ZeichneCanvas(int breite,int hoehe) { super(); setSize(breite,hoehe); setBackground(Color.white); bereinigen(); font = new Font("Helvetica",Font.BOLD,24); } // Instanzmethoden public void bereinigen() { zeichneObjekte = false; repaint(); 297 Programmieren in Java } public void zeichnen() { zeichneObjekte = true; repaint(); } public void paint(Graphics g) { System.out.println( „Methode paint wurde aufgerufen, zeichneObjekte = „ + zeichneObjekte); if (zeichneObjekte) { // Individuelle Auspraegung der Zeichnung int r = 8; int i, j; int x, y; g.setColor(Color.red); for (i=1; i<=10; ++i) { x = 100 – r * i; y = (int) (40 + (i – 1) * 1.7321 * r); for (j=1; j<=i; ++j) { g.fillOval(x,y,2*r,2*r); x += 2 * r; } } // g.drawLine(0,0,100,100); // g.setColor(Color.blue); // g.drawString("Hallo I2A und I2B",100,100); // g.setColor(Color.red); // g.draw3Drect(150,150,50,50,true); } } } Die Klasse ZeichneGUI benutzt die Klasse ZeichneCanvas. Sie bezieht die Zeichenfläche in eine grafische Benutzeroberfläche mit Schaltknöpfen und Textfeldern ein. import java.awt.*; import java.awt.event.*; public class ZeichneGUI extends Frame implements MouseMotionListener { // Instanzvariable private TextField xFeld; private TextField yFeld; private Button zeichne; private Button bereinige; private ZeichneCanvas zeichenflaeche; // Konstruktoren public ZeichneGUI() { this(200,200); } public ZeichneGUI(int breite,int hoehe) { super(); setTitle("Demonstration Zeichenflaeche"); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); 298 Programmieren in Java setLayout(new BorderLayout()); Panel panelN = new Panel(); panelN.add(new Label(„x: „)); xFeld = new TextField(5); xFeld.setEditable(false); panelN.add(xFeld); panelN.add(new Label("y: ")); yFeld = new TextField(5); yFeld.setEditable(false); panelN.add(yFeld); add("North",panelN); zeichenflaeche = new ZeichneCanvas(breite,hoehe); zeichenflaeche.addMouseMotionListener(this); add("Center",zeichenflaeche); Panel panelS = new Panel(); zeichne = new Button("Zeichne"); zeichne.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { zeichenflaeche.zeichnen(); } }); panelS.add(zeichne); bereinige = new Button("Bereinige"); bereinige.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { zeichenflaeche.bereinigen(); } }); panelS.add(bereinige); add("South",panelS); pack(); setVisible(true); } // Implementierung des MouseMotionListener-Interface public void mouseMoved(MouseEvent e) { xFeld.setText(" " + e.getX()); yFeld.setText(" " + e.getY()); } public void mouseDragged(MouseEvent e) { xFeld.setText(" " + e.getX()); yFeld.setText(" " + e.getY()); } // Start der GUI public static void main(String args[]) { ZeichneGUI gui = new ZeichneGUI(); } } 299 Programmieren in Java 5.3 Container Container sind allgemeine Komponenten, die andere Komponenten oder auch andere Container enthalten. 5.3.1 Panels Eine sehr häufig vorkommende Container-Form ist das sog. Panel, das einen Container darstellt, der am Bildschirm dargestellt werden kann. Ein Panel ist ein „reiner“ Container, kein eigenes Fenster. Sein einziger Zweck ist es, Komponenten in einem Fenster anzuordnen. Erzeugen eines Panels. Das Erzeugen erfolgt mit Hilfe des Konstruktors „Panel()“, z.B.: Panel meinPanel = new Panel(); Hinzufügen von einem Panel in einen anderen Container. Ein Panel kann bspw. mit add(meinPanel) in ein Applet eingebaut werden. Verschachteln von Panels. Panels lassen sich in beliebig vielen Ebenen verschachteln. Sinn machen solche verschachtelten Panels vor allem in Verbindung mit den verschiedenen Layouts. 5.3.2 Frames Ein Frame ist ein voll funktionsfähiges Fenster mit eigenem Titel und Icon. Frames können Pulldown-Menüs haben und verschieden gestaltete Mauszeiger verwenden. Erzeugen von Frames. Mit der Frame-Klasse kann ein funktionsfähiges Fenster mit Menüleiste erstellt werden. Zum Erzeugen von Frames stehen verschiedene Konstruktoren zur Verfügung: public Frame() //Damit kann ein Frame erzeugt werden, der zu Beginn nicht sichtbar ist und keinen Titel hat. public Frame(String titel) //Diese Version vergibt einen Titel bei der Erzeugung, das Frame ist zu Beginn nicht sichtbar. Verändern der Titelzeile. Sie läßt sich in den Klassen Frame und Dialog mit den Methoden public void setTitle(String titel) // ändert die Beschriftung der Titelleiste in titel public String getTitle() // Abfrage der Titelleiste beeinflussen. Einstellen des Standard-Font. Ein Fenster hat einen Standard-Font, der zur Ausgabe der Schrift verwendet wird, (wenn nicht im Grafik-Kontext ein anderer Font ausgewählt wird). Der Standard-Font eines Fensters wird mit „public void setFont (Font f)“ der Klasse Component eingestellt. Einstellen von Vorder- Und Hintergrundfarbe. Die Vordergrundfarbe dient zur Ausgabe von Grafik- und Textobjekten, wenn im Grafik-Kontext keine andere Farbe 300 Programmieren in Java gesetzt wird. Wird die Einstellung nicht geändert, werden für Vordergrund- und Hintergrundfarbe die unter Windows eingestellten Standardfarben verwendet. Mit public void setBackground(Color farbe) public void setForeground(Color farbe) können Vordergrund- und Hintergrundfarbe des Fensters geändert werden. Größe und Position eines Fensters können bestimmt werden über public void setSize(int breite, int hoehe) // verändert die Größe des Fensters auf den Wert (breite, hoehe) public void setSize(Dimension d) public void setBounds(int x, int y, int breite, int hoehe) // positioniert ein Fenster der Größe (breite,hoehe) an die Position (x,y) public void setBounds(Rectangle r) public void setLocation(int x, int y) // bewegt die linke obere Ecke an die Bildschirmposition (x,y) public void setLocation(Point p); Anzeigen, Wegblenden und Löschen von Frames. Die Konstruktoren haben das Frame unsichtbar gelassen. Frames müssen vor Gebrauch sichtbar gemacht werden. Der 1. Schritt besteht darin, einen Frame eine Größe mit der Methode public void resize(int breite /* Breite vom Frame in Pixel */, int hoehe /* Hoehe in Pixel */) zu geben. Sichtbar wird der Frame über die show()-Methode227. Mit public void hide()228 kann der Frame wieder unsichtbar gemacht werden. Über public void dispose() wird der Frame beendet. Mit public boolean isShowing() kann geprüft werden, ob das Fenster bereits angezeigt ist. Standardlayout für Fenster: Border-Layout. Hinzufügen von Komponenten: Rahmen sind Container wie Panels, andere Komponenten können mit der add()-Methode hinzugefügt werden. 5.3.3 Menüs Jedes Fenster / Frame kann eine eigene Menüleiste besitzen. Jede Menüleiste kann mehrere Menüs enthalten und jedes Menü beliebige Einträge. Java unterstützt die Konstruktion von Menüs durch eine Reihe speziell dafür vorgesehenen Klassen: Klasse MenuBar Menu MenuItem CheckboxMenuItem Bedeutung Beschreibt die Menüzeile (-leiste)eines Fensters Beschreibet ein einzelnes, der in der Menüzeile enthaltenen Menüs Bildet die vom Anwender auswählbaren Einträge innerhalb der Menüs Die Menüleiste Sie beschreibt das Hauptmenü eines Fensters. Sie befindet sich unterhalb der Titelleiste am oberen Rand des Fensters und zeigt die Namen der darin enthaltenen Menüs an. Eine Menüleiste wird durch Instanzen der Klasse MenuBar erzeugt: 227 Für show() aus dem Paket java.awt.Component gibt es als neue Variante die Methode setVisible(boolean) 228 hide() ist in java.awt.Frame als „deprecated“ deklariert. Statt dessen steht setVisible(boolean) zur Verfügung 301 Programmieren in Java Konstruktor. public MenuBar() Einfügen von Menüs. public void add(Menu m). Entfernen von bestehenden Menüs. public void remove(int index) public void remove(MenuComponent m) Zugriff auf ein beliebiges Menü. public Menu getMenu(int index). getMenu liefert das Menüobjekt, das sich an der Position mit dem angegebenen Index befindet. Zum Binden einer Menüleiste an ein Fenster mit dem angegebenen Index (index) befindet. Binden einer Menüleiste an einen Frame. public void setMenuBar(MenuBar mb) .Durch Aufruf dieser Methode wird die angegebene Menüleiste im Fenster angezeigt und beim Auswählen des Menüpunkts werden Nachrichen ausgelöst und an das Fenster gesendet. Die Fensterklasse kann diese Nachrichen durch das Registrieren eines Objekts vom Typ ActionListener bearbeiten. Menüs. Sie bilden die Bestandteile einer Menüleiste und werden durch Instanzen der Klasse Menu repräsentiert. Konstruktor. public Menu(String label) // label gibt den Namen des Menüs an Standardhilfe-Menü: public void setHelpMenu(Menu m) erzeugt ein spezielles Standardhilfemenü. Menüeinträge Einfache Menüeinträge sind die elementaren Bestandteile eines Menüs. Sie besitzen einen Text, mit dem sie dem Anwender die dahinterstehende Funktion anzeigen. Wenn der zugehörige Menüpunkt aufgerufen wird, sendet das Programm eine Nachricht an das zugehörige Fenster, die dann zum Aufruf der entsprechenden Methode führt. Erzeugen von Menüeinträgen: public MenuItem(String label). „label“ ist der Name des Menüeintrags. Zugriff auf den Namen bzw. Setzen des Namens eines Menüeintrags: public String getLabel() public void setLabel(String label) Neben dem Namen besitzt ein Menüeintrag eine interne Zustandsvariable. Sie zeigt an, ob der Menüeintrag aktiv ist oder nicht. Nur ein aktiver Eintrag kann vom Anwender ausgewählt werden und so eine Nachricht auslösen. Nach dem Aufruf des Konstruktors ist ein Menüeintrag zunächst aktiviert. Er kann durch public void setEnabled(boolean b) mit Parameterwert „false“ deaktiviert und mit Parameterwert true aktiviert werden. Über public boolean isEnabled() kann der aktuelle Zustand abgefragt werden. Methoden für die Bearbeitung von Menüeinträgen. public public public public void void void void add(MenuItem m) add(String label) remove(int index) remove(MenuComponent Item) Seperatoren für Menüeinträge. Ein Seperator wird als waagrechter Strich zur Trennung der Menüeinträge angezeigt: public void addSeparator() // fügt den Separator hinter dem zuletzt eingefügten Menüeintrag ein public void insertSeparator(int index) // Einfügepostion kann frei angegeben werden. 302 Programmieren in Java Zugriff auf Menüeintrag. public MenuItem getItem(int index) Anzahl der Einträge. public int getItemCount() Die Klasse CheckboxMenuItem. Sie ist aus MenuItem abgeleitet und besitzt zusätzlich eine interne Zustandsvariable, die zwischen true und false umgeschaltet werden kann. Die visuelle Darstellung der Zustandsvariablen erfolgt durch Anfügen oder Entfernen eines „Häkchens“ neben dem Menüeintrag. Die Instanzierung eines CkeckboxMenuItem erfogt wie bei einem MenuItem. Zusätzlich stehen die beiden Methoden setState() und getState() zum Setzen und Abfragen des Zustands zur Verfügung: public void setState(boolean status) public boolean getState() Menüaktionen: Ein Menü macht nur Sinn, wenn damit eine Aktion ausgelöst werden kann. Diese Aktion kann wie jede andere Aktion mit der Methode actionPerformed behandelt werden. Bsp.: Zusammenstellung der wesentlichen Elemente zur Gestaltung von Menüs import java.awt.*; import java.awt.event.*; public class PR53331 extends Frame { // TextField t = new TextField("Keine Anzeige",30); public PR53331() { // Menueleiste erzeugen MenuBar mb = new MenuBar(); // Menueleiste an Rahmen verankern setMenuBar(mb); // Menuepunkt erzeugen Menu m1 = new Menu("Farben"); // Menuepunkt 1 // Ein Untermenue erzeugen // Menueeintraege in der Menuezeile plazieren mb.add(m1); // Eintraege in Menuepunkt 1 MenuItem rot = new MenuItem("Rot"); m1.add(rot); rot.addActionListener(new RotL()); MenuItem blau = new MenuItem("Blau"); m1.add(blau); blau.addActionListener(new BlauL()); MenuItem gruen = new MenuItem("Gruen"); m1.add(gruen); gruen.addActionListener(new GruenL()); MenuItem gelb = new MenuItem("Gelb"); m1.add(gelb); gelb.addActionListener(new GelbL()); // Untermenue erzeugen Menu unterMenue = new Menu("Farbanpassung"); // Trennlinie MenuItem trennLinie = new MenuItem("-"); m1.add(trennLinie); // Untermenue hinzufuegen m1.add(unterMenue); MenuItem farbe = new MenuItem("Farbe"); MenuItem mono = new MenuItem("Mono"); farbe.addActionListener(new FarbeL()); mono.addActionListener(new MonoL()); unterMenue.add(farbe); unterMenue.add(mono); // Menue zur Dateibearbeitung 303 Programmieren in Java Menu d = new Menu("Datei"); MenuItem[] datei = { // new MenuItem("Open"), // menu shortcut new MenuItem("Exit",new MenuShortcut(KeyEvent.VK_E)) }; ML ml = new ML(); datei[0].setActionCommand("Open"); datei[0].addActionListener(ml); datei[1].setActionCommand("Exit"); datei[1].addActionListener(ml); for (int i = 0; i < datei.length; i++) d.add(datei[i]); mb.add(d); // CheckboxMenuItem Menu s = new Menu("Sicherheit"); CheckboxMenuItem[] sicherung; { new CheckboxMenuItem("Wache"), new CheckboxMenuItem("Versteck") }; CMIL cmil = new CMIL(); sicherung[0].setActionCommand("Wache"); sicherung[0].addItemListener(cmil); sicherung[1].setActionCommand("Versteck"); sicherung[1].addItemListener(cmil); for (int i = 0; i < sicherung.length; i++) s.add(sicherung[i]); d.add(s); // Hilfsmenue Menu meineHilfe = new Menu("Hilfe"); mb.setHelpMenu(meineHilfe); // Eintraege im Hilfsmenue meineHilfe.add(new MenuItem("Info")); meineHilfe.add(new MenuItem("Hilfe")); // Textfeld initialisieren // t.setEditable(false); // add(t,BorderLayout.CENTER); } class ML implements ActionListener { public void actionPerformed(ActionEvent e) { MenuItem ziel = (MenuItem) e.getSource(); String aktionsKommando = ziel.getActionCommand(); if (aktionsKommando.equals("Open")) { System.out.println("Oeffnen"); } else if (aktionsKommando.equals("Exit")) { dispatchEvent(new WindowEvent(PR53331.this, WindowEvent.WINDOW_CLOSING)); } } } class CMIL implements ItemListener { public void itemStateChanged(ItemEvent e) { CheckboxMenuItem ziel = (CheckboxMenuItem) e.getSource(); String aktionsKommando = ziel.getActionCommand(); if (aktionsKommando.equals("Wache")) { 304 Programmieren in Java System.out.println("Wache " + ziel.getState()); } else if (aktionsKommando.equals("Versteck")) { System.out.println("Versteck " + ziel.getState()); } } } class RotL implements ActionListener { public void actionPerformed(ActionEvent e) { setBackground(Color.red); } } class BlauL implements ActionListener { public void actionPerformed(ActionEvent e) { setBackground(Color.blue); } } class GruenL implements ActionListener { public void actionPerformed(ActionEvent e) { setBackground(Color.green); } } class GelbL implements ActionListener { public void actionPerformed(ActionEvent e) { setBackground(Color.yellow); } } class FarbeL implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("Im Untermenue wurde Farbe ausgewaehlt"); } } class MonoL implements ActionListener { public void actionPerformed(ActionEvent e) { System.out.println("Im Untermenue wurde Mono gewaehlt"); } } public static void main(String args[]) { PR53331 f = new PR53331(); f.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); f.setSize(300,200); f.setVisible(true); } } 5.3.4 Dialoge Unter einem Dialog versteht man ein Popup-Fenster. Dialoge werden entweder „modal“ oder „non-modal“ erzeugt. „modal“ bedeutet: Das Dialogfeld blockiert andere Fenster, wenn es angezeigt wird. 305 Programmieren in Java Erzeugen. Ein modaler Dialog muß immer von der Klasse Dialog abgeleitet sein. Dialog bietet 4 Konstruktoren an public public public public Dialog(Frame Dialog(Frame Dialog(Frame Dialog(Frame eltern) eltern, boolean modal) eltern, String titel) eltern, String titel, boolean modal) Der Parameter modal entscheidet, ob der Dialog modal wird oder nicht. Zugriff auf modale bzw. „nicht modale“ Eigenschaft. Dafür gibt es die Methoden public boolean isModal() // Rückgabewert ist true, falls der Dialog modal ist. Anderenfalls ist er // false. public void setModal(boolean b) Unterbinden der Veränderbarkeit der Größe eines Fensters. Das kann über public void setResizable(boolean resizable) public boolean isResizable() geschehen bzw. überprüft werden. 306 Programmieren in Java 5.4 Die Layout-Manager Das AWT-System kümmert sich weitgehend selbstständig um Größenanpassung und Postionierung von Komponenten auf der Oberfläche. Je nach Plattform und Bedingungen werden Komponenten in die Oberfläche optimal angepaßt. Über Layout-Manager kann das AWT angewiesen werden, wo Komponenten im Verhältnis zu anderen Komponenten stehen sollen. Der Layout-Manager bestimmt nach gewissen Regeln, an welche Stelle die Komponenten am besten passen und ermittelt die optimale Größe der Komponenten. 5.4.1 Layout-Regeln Drei Aspekte bestimmen das Aussehen einer AWT-Oberfläche: 1. Die Plattform und die Bedingungen, die unter dieser Plattform vorliegen. Java ist plattformunabhängig, daher kann der Programmierer hier keine Aussagen machen. Die Reihenfolge, in der die AWT-Komponenten in einen Container eingefügt werden. Die Art des Layout-Managers. Das AWT umfaßt 5 verschiedene Typen von Layout-Managern, die die Oberfläche unterschiedlich aufgliedern: 2. 3. - Flow-Layout Grid-Layout Border-Layout Card-Layout GridBag-Layout Jedes Panel kann einen eigenen Layout-Manager verwenden. Erstellen eines Layout-Managers. Zum Erstellen eines Layout-Managers für einen Panel kann ( in der init()-Methode) die Methode public void setLayout(LayoutManager mgr) verwendet werden, z.B.: setLayout(new FlowLayout()); 307 Programmieren in Java 5.4.2 Die einzelnen Layout-Manager Die FlowLayout-Klasse << interface >> LayoutManager FlowLayout public static int CENTER public static int LEADING public static int LEFT public static int RIGHT public static int TRAILING << Konstruktoren >> public FlowLayout() public FlowLayout(int align) public FlowLayout(int align, int hgap, int vgap) //erzeugt ein FlowLayout mit angegebenen Alignment und einem //horizontalen bzw. vertikalen Rand << Methoden >> public void addLayoutComponent(String name, Component comp) public int getAlignment() //liefert das Alignment des FlowLayout-Managers public void setAlignment(int align) //setzt das Alignment mit Hilfe der Konstanten //FlowLayout.RIGHT, FlowLayout.LEFT, FlowLayout.CENTER ... Abb. Die Klasse FlowLayout FlowLayout ordnet Komponenten von links nach rechts an, bis keine weiteren Komponenten mehr in die Zeile passen. Dann geht es zur nächsten Zeile und bewegt sich wieder von links nach rechts. Die Standard-.Ausrichtung für ein FlowLayout ist zentriert. Der FlowLayout-Manager ist der Standard-Layout-Manager für alle Applets. Bsp.: Plazierung von Schaltflächen. Die Komponenten werden in der Reihenfolge, in der sie in den Container eingefügt werden, spaltenweise von links nach rechts eingeordnet, bis keine weiteren Komponenten mehr in eine Zeile passen. So führt der folgende Quellcode import java.awt.*; public class FlowLayoutTestApplet extends java.applet.Applet { public void init() { setBackground(Color.yellow); setLayout(new FlowLayout()); Button meinKnopf1 = new Button("Rot"); add(meinKnopf1); Button meinKnopf2 = new Button("Blau"); add(meinKnopf2); Button meinKnopf3 = new Button("Gruen"); add(meinKnopf3); 308 Programmieren in Java Button meinKnopf4 add(meinKnopf4); Button meinKnopf5 add(meinKnopf5); Button meinKnopf6 add(meinKnopf6); Button meinKnopf7 add(meinKnopf7); } } = new Button("Pink"); = new Button("Rosa"); = new Button("Gelb"); = new Button("Cyan"); mit den im Applet-Tag angegebenen Abmessungen <HTML> <HEAD> <TITLE>FlowLayout-Test</TITLE> </HEAD> <BODY> <APPLET CODE="FlowLayoutTestApplet.class" WIDTH=100 HEIGHT=120> </APPLET> </BODY> </HTML> zur folgenden Darstellung Eine Veränderung in der Größe des Applet macht den zeilenweisen Aufbau deutlich: <HTML> <HEAD> <TITLE>FlowLayout-Test</TITLE> </HEAD> <BODY> <APPLET CODE="FlowLayoutTestApplet.class" WIDTH=300 HEIGHT=100> </APPLET> </BODY> </HTML> 309 Programmieren in Java Die BorderLayout-Klasse << interface >> LayoutManager BorderLayout public static String CENTER public static String NORTH public static String EAST public static String SOUTH public static String WEST << Konstruktoren >> public BorderLayout() public BorderLayout(int hgap, int vgap) << Methoden >> public void addLayoutComponent(Component comp, Object constraints) public void layoutContainer(Container target) public Dimension preferredLayoutSize(Container target) public void removeLayoutComponent(Component comp) ... Abb. Die Klasse BorderLayout Die BorderLayout-Klasse unterteilt einen Container in fünf Bereiche: North, South, East, West, Center. Falls einem Container Komponenten hinzugefügt werden, geschieht das über ein spezielles Exemplar der add()-Methode ( mit einem dieser fünf Bereichsnamen). Bsp.: BorderLayout-Applet, das einem Applet einige Schaltfläche hinzufügt. Der Quellcode ist import java.awt.*; import java.applet.*; public class BorderLayoutTestApplet extends Applet { public void init() { int i = 0; setBackground(Color.yellow); setLayout(new BorderLayout()); add("North",new Button("Schaltflaeche " + i++)); add("South",new Button("Schaltflaeche " + i++)); add("East", new Button("Schaltflaeche " + i++)); 310 Programmieren in Java add("West", new Button("Schaltflaeche " + i++)); add("Center",new Button("Schaltflaeche " + i++)); } } Für das Applet wurde folgende Größe vereinbart: <HTML> <HEAD> <TITLE>FlowLayout-Test</TITLE> </HEAD> <BODY> <APPLET CODE="BorderLayoutTestApplet.class" WIDTH=500 HEIGHT=100> </APPLET> </BODY> </HTML> Das führt zu dem folgenden Layout: Die GridLayout-Klasse Diese Klasse teilt den Container in ein Gitter mit gleich großen Zellen ein. Hinzugefügte Komponenten werden von links nach rechts angeordnet, begonnen wird bei den linken, oberen Zellen. Bsp.: Plazieren von Schaltflächen Der folgende Quellcode import java.awt.*; import java.applet.*; public class GridLayoutTestAppl extends Applet { public void init() { setLayout(new GridLayout(7,3)); for (int i = 0; i < 20; i++) add(new Button("Schaltflaeche " + i)); } } führt über die folgende Größe im Applet-Tag <HTML> <HEAD> <TITLE>FlowLayout-Test</TITLE> </HEAD> <BODY> <APPLET CODE="GridLayoutTestAppl.class" WIDTH=300 HEIGHT=200> </APPLET> </BODY> </HTML> zu der folgenden Darstellung 311 Programmieren in Java Die GridBagLayout-Klasse und die GridBagConstraint-Klasse Die Klasse GridBagLayout ermöglicht die Anordnung der Komponenten in einem rasterähnlichen Layout. Zusätzlich zum GridLayout kann kontrolliert werden: Die Werte einzelner Zellen im Raster, die Proportionen zwischen Zeilen und Spalten sowie die Anordnung von Komponenten innerhalb der Zellen im Raster. Zur Erstellung eines GridLayout dienen zwei Klassen: - GridBagLayout, die den Layoutmanager bereitstellt - GridBagConstraints, die die Eigenschaften jeder Komponente im Raster bestimmt. Die GridBagLayout-Klasse ermöglicht einer Komponente auch die Belegung von mehr als einer Zelle. Der gesamte Bereich, den die Komponente einnimmt, wird „display area“ genannt. Bevor einem Container eine Komponente hinzugefügt wird, müssen GridBagLayout Vorschläge unterbreitet werden, wo die Komponente hingestellt werden soll. Die GridBagConstraint-Klasse besitzt verschiedene Variable zur Steuerung der Anordnung der Komponenten: - - - - - die Variablen gridx und gridy (Koordinaten der Zelle in der die nächste Komponente plaziert werden soll). Die obere linke Ecke von GridBagLayout liegt bei (0,0), der Standardwert ist GridBagConstraints.RELATIVE. Für gridx ist das die erste rechte Zelle neben der letzten hinzugefügten Komponente. Für gridy ist es genau die Zelle, die unterhalb der letzten Komponente hinzugefügt wurde. die Variablen gridwidth und gridheight bestimmen, wie viele Zellen hoch und breit eine Komponente sein sollte (Standardwert ist 1). Der Wert GridBagConstraints.REMAINDER für gridwidth bestimmt, daß eine Komponente die letzte in der Spalte sein sollte. Die Variable fill gibt an, welche Dimension einer Komponente sich verändern soll, wenn eine Komponente kleiner als der Anzeigebereich ist. Gültige Werte sind: NONE, BOTH, HORIZONTAL und VERTICAL. „GridBagConstraints.BOTH“ sorgt für eine Streckung der Komponenten in beiden Richtungen so, daß sie den Anzeigebereich voll ausfüllen. anchor wird eingesetzt, wenn die Komponente kleiner als ihr Anzeigebereich ist. Sie zeigt an, wo die Komponente innerhalb des Anzeigebereichs plaziert werden soll. Der Standardwert ist „GridBagConstraints.CENTER“. Er gibt an, welche Komponente im „Zentrum des Anzeigebereichs“ stehen soll. Die anderen Komponenten sind Kompasspunkte: GridBagConstraints.NORTH, GridBagConstraints.NORTHEAST etc. Die Variable weightx und weighty definieren die Gewichtung für freien Raum innerhalb eines Containers und bestimmen die relative Größe der Komponenten. Ein Komponente mit einem Wert 312 Programmieren in Java - von 2.0 für weightx nimmt doppelt soviel Platz in horizontaler Richtung wie eine Komponente mit dem Wert 1.0 für weightx. Durch „insets“ (Objekte der Klasse Insets)229 wird der freizuhaltende Rand zwischen Komponente und Gitter festgelegt. Die Standardwerte für alle 4 Seiten sind Null. Die Elemente können zum Teil auch vergrößert werden. Die Pixel, die rechts und links zuzufügen sind, gibt ipadx an. Wieviel oben und unten hinzukommt, speichert ipady. (ipadx, ipady sind standardmäßig 0). gridwidth insets ipady fill gridheight ipadx Bsp.230: Plazieren von Schaltflächen import java.awt.*; import java.util.*; import java.applet.Applet; public class GridBagBsp extends Applet { protected void macheSchalter(String name, GridBagLayout gridbag, GridBagConstraints c) { Button schalter = new Button(name); gridbag.setConstraints(schalter, c); add(schalter); } public void init() { GridBagLayout gridbag = new GridBagLayout(); GridBagConstraints c = new GridBagConstraints(); setFont(new Font("Helvetica", Font.PLAIN, 14)); setLayout(gridbag); c.fill = GridBagConstraints.BOTH; c.weightx = 1.0; macheSchalter("Schaltflaeche1", gridbag, c); macheSchalter("Schaltflaeche2", gridbag, c); macheSchalter("Schaltflaeche3", gridbag, c); // letzter Schalter in der Zeile: c.gridwidth = GridBagConstraints.REMAINDER; macheSchalter("Schaltflaeche4", gridbag, c); // neue Zeile, weightx wiederherstellen: c.weightx = 0.0; macheSchalter("Schaltflaeche5", gridbag, c); // vorletztes Element der Zeile: c.gridwidth = GridBagConstraints.RELATIVE; macheSchalter("Schaltflaeche6", gridbag, c); // letztes Element: c.gridwidth = GridBagConstraints.REMAINDER; macheSchalter("Schaltflaeche7", gridbag, c); 229 Beim Erstellen eines neuen Layout-Managers erzeugte horizontale und vertikale Abstände dienen zur Bestimmung des Platzes zwischen Komponenten in einem Panel. Die Klasse Insets bietet Eckeinsetzwerte für oben, unten, links und rechts an, die dann verwendet werden, wenn das Panel gezeichnet wird. 230vgl. pr54205 313 Programmieren in Java // neue Zeile, Wert wiederherstellen: c.gridwidth = 1; c.gridheight = 2; c.weighty = 1.0; macheSchalter("Schaltflaeche8", gridbag, c); c.weighty = 0.0; c.gridwidth = GridBagConstraints.REMAINDER; c.gridheight = 1; macheSchalter("Schaltflaeche9", gridbag, c); macheSchalter("Schaltflaeche10", gridbag, c); resize(300, 100); } public static void main(String args[]) { Frame f = new Frame("GridBag Layout Beispiel"); GridBagBsp bsp = new GridBagBsp(); bsp.init(); f.add("Center", bsp); f.pack(); f.resize(f.preferredSize()); f.show(); } Die CardLayout-Klasse CardLayout231 plaziert die Elemente auf sich gegenseitig verdeckende Karten. Jeweils eine dieser Karten kann sichtbar gemacht werden. Das Null-Layout Ein Aufruf der Methode „setLayout“ mit dem Argument null erzeugt ein NullLayout. In diesem Fall verwendet das Fenster keinen Layoutmanager, sondern überläßt die Positionierung der Komponenten der Anwendung. Je Komponente sind drei Schritte auszuführen - Erzeugen der Komponente Festlegung von Größe und Position der Komponente Übergabe der Komponente an das Fenster. Größe und Position des Dialogelements können mit public void setBounds(int x, int y, int breite, int hoehe) 231 Das CardLayout ist als Notizbuchersatz durch die Einführung der Klasse JTabbedPane in Swing fast überflüssig geworden 314 Programmieren in Java festgelegt werden. Der Punkt (breite,hoehe) die Größe. (x,y) bestimmt die Bsp.: import java.awt.*; import java.awt.event.*; public class NL extends Frame { public static void main(String args[]) { NL f = new NL(); f.setVisible(true); } public NL() { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { setVisible(false); dispose(); System.exit(0); } }); setSize(300,250); setLayout(null); for (int i = 0; i < 5; i++) { Button schalter = new Button("Schalter" + (i+1)); schalter.setBounds(10+i*35,40+i*35,100,30); add(schalter); } } } 315 Anfangsposition und Programmieren in Java 5.5 Die Event-Modelle 1.0 und 1.1 5.5.1 Der AWT-Handler 1.0 Die Methode handleEvent Die handleEvent()-Methode232 ist unter dem AWT-Event-Handler die allgemeinste Art, wie das AWT auf irgendwelche Ereignisse eingeht, die auf der Benutzeroberfläche stattfinden. Ereignisse werden innerhalb der handleEvent()Methode interpretiert und dann gezielt passende Methoden aufgerufen. Damit die handleEvent()-Funktion nicht zu groß und unübersichtlich wird, ruft handleEvent() mehrere Hilfsfunktionen (mouseEnter, keydown, action, ... ) auf, die die jeweils zugeordneten Events bearbeiten. Die Verarbeitung von Ereignissen stützt sich auf die Klasse Event. Die Instanzvariable „id“ der Klasse Event enthält die Art des Ereignisses und liefert alle notwendigen Informationen. Das Attribut „target“ enthält die Information, welches Objekt das Ereignis ausgelöst hat. Bsp.: Darstellung einer Datei im Textbereich eines Fensters (Frame) import java.awt.*; import java.io.*; public class DateiDarsteller extends Frame { // Instanzvariable String dateiName; // Konstruktoren public DateiDarsteller() throws IOException { super("Dateidarsteller "); MenuBar menue = new MenuBar(); Menu m = new Menu("Datei"); m.add(new MenuItem("Oeffnen")); m.add(new MenuItem("Schliessen")); menue.add(m); // Installiere diesen Menuebalken setMenuBar(menue); this.show(); } // Methoden public void dateiDarstellen() throws IOException { try { File d = new File(dateiName); int groesse = (int) d.length(); int anzZeich = 0; FileInputStream ein = new FileInputStream(d); byte[] daten = new byte[groesse]; while (anzZeich < groesse) anzZeich += ein.read(daten,anzZeich,groesse); String s = new String(daten,0); TextArea textBereich = new TextArea(s,24,80); textBereich.setFont(new Font("Helvetiva",Font.PLAIN,10)); textBereich.setEditable(false); this.add("Center",textBereich); } 232 Die Methode ist in der Klasse Component definiert, von der die Klasse java.applet.Applet abgeleitet ist. Dadurch steht die Methode allen Applets zur Verfügung, 316 Programmieren in Java catch (IOException e) { } this.show(); } public boolean handleEvent(Event e) { if ((e.id == Event.ACTION_EVENT) && (e.target instanceof MenuItem)) { if (((MenuItem) e.target).getLabel().equals("Oeffnen")) { FileDialog fd = new FileDialog(this,"Dateidialog"); fd.show(); dateiName = fd.getFile(); System.out.println("Dateiname: " + dateiName); try { this.dateiDarstellen(); } catch (IOException a ) { } return true; } if (((MenuItem) e.target).getLabel().equals("Schliessen")) { this.hide(); this.dispose(); System.exit(0); return true; } } return false; } public static void main(String argv[]) throws IOException { try { DateiDarsteller DD = new DateiDarsteller(); } catch (IOException e) { System.out.println(e); } } } Behandlung von Aktionsereignissen Aktionsereignisse werden über public boolean action(Event e, Object arg) behandelt. Zuerst muß innerhalb der methode überprüft werden, welche Komponente der Benutzeroberfläche die Aktion erzeugt hat. Zur vereinfachten Bearbeitung beinhaltet das Event-Objekt, das beim Aufruf von action() erhalten wird, eine "target"-Instanzvariable, die eine Referenz zu dem Objekt, das das Ereignis aufgenommen hat, enthält. Mit dem instanceof-Operator kann dann bestimmt werden, von welcher Komponente das Ereignis ausging. Das zweite Argument dient zum Bestimmen des Labels233, der Elemente, des Inhalts der Komponenten234. Anstatt des zusätzlichen Arguments kann auch die Methode235 getLabel verwendet werden. Behandlung von Fokus-Ereignissen Für die Ereignisse "Fokuserhalt" und "Fokusverlust" können die Methoden 233 vgl. pr52105 Nicht vergessen: Das Argument in den richtigen Objekt-Typ zu casten 235 vgl. pr54305 234 317 Programmieren in Java public boolean getFocus(Enet evt, Object arg) bzw. public boolean lostFocus(Event evt, Object arg) verwendet werden. Ereignisse von Listenfeldern Listenfelder erzeugen drei verschiedene Ereignisarten: Auswahl bzw. Abwahl eines Eintrags in der Liste bzw. Doppelklick auf einen Eintrag. Ein Doppelklick auf einen Eintrag kann mit der Methode action bearbeitet werden. Auswahl und Abwahl eines Listeneintrags kann mit "handleEvent" und Überprüfen auf die Ereignisse mit LIST_SELECT und LIST_DESELECT erfolgen. 5.5.2 Das Event-Handling in Java 1.1 bzw. Java 1.2 Im neuen Ereignismodell java.util.EventObject. java.awt.AWTEvent. erben die Ereignisklassen von der AWT-Events erben von der Klasse Klasse EventListener Falls eine Klasse im Ereignisbehandlungsmodell von Java 1.2 aus ein Ereignis reagieren will, muß diese eine Schnittstelle implementieren, die dieses Ereignis verarbeitet. Diese Schnittstellen werden als EventListener bezeichnet. Jeder Listener behandelt eine bestimmte Ereignisart. Eine Klasse kann so viele Listener implementieren, wie benötigt werden. Die folgenden EventListener stehen zur Verfügung: ActionListener AdjustmentListener FocusListener ItemListener KeyListener MouseListener MouseMotionListener WindowListener Aktionsereignisse, die durch den Benutzer ausgelöst werden, z.B. Klick auf eine Schaltfläche Ereignisse, die erzeugt werden, wenn werte einer Komponente eingestellt werden (z.B. Bewegung eines Schiebers einer Bildlaufleiste) Ereignisse, die erzeugt werden, wenn eine Komponente, z.B. ein textfeld, den eingabefokus erhält oder verliert. Ereignisse, die erzeugt werden, wenn ein Element, z.B. ein Kontrollkästchen, verändert wurde Tastaturereignisse, die bei Tastatureingaben erzeugt werden. Mausereignisse, die erzeugt werden, wenn mit der maus geklickt wird, die Maus in den Bereich einer Komponente eintritt bzw. diese wieder verläßt Mausereignisse, die die Bewegung einer Maus über eine Komponente verfolgen Ereignisse, die von Fenstern erzeugt werden Das Paket java.awt.event beinhaltet alle elementaren EventListener. Über import java.awt.event.* erfolgt das Importieren in die Anwendungen. 318 Programmieren in Java Event-Handling mit Hilfe lokaler Klasse Für die Ereignisbehandlung werden lokale Klassen herangezogen. Im JDK 1.0 wurden Klassen nur auf Paketen definiert, eine Schachtelung war nicht möglich. Im JDK 1.1 kann innerhalb bestehender Klassen eine neue Klasse definiert werden (Konzept der „Inner Classes“), die nur innerhalb der bestehenden Klasse sichtbar ist. Objektinstanzen der inneren Klasse können nur aus der umfassenden Klasse heraus erzeugt werden. Allerdings kann die innere Klasse auf die Instanzvariablen der äußeren Klasse zugreifen. Mit Hilfe lokaler Klassen werden die benötigten EventListener implementiert. Dazu wird in dem GUI-Objekt, das einen Event-Handler benötigt, eine lokale Klasse definiert (und aus einer passenden AdapterKlasse abgeleitet). Ein einführendes Bsp.: Event-Handling nach der Bearbeitung von Schaltflächen des AWT in Version 1.0 und 1.1 // Einfangen von Klicks auf Schaltflaechen import java.awt.*; import java.awt.event.*; import java.applet.*; public class { Button sch1 Button sch2 public void { SchalterAppl extends Applet = new Button("Schaltflaeche 1"); = new Button("Schaltflaeche 2"); init() sch1.addActionListener(new Sch1()); sch2.addActionListener(new Sch2()); add(sch1); add(sch2); } class Sch1 implements ActionListener { public void actionPerformed(ActionEvent e) { getAppletContext().showStatus("Schaltflaeche 1"); } } class Sch2 implements ActionListener { public void actionPerformed(ActionEvent e) { getAppletContext().showStatus("Schaltflaeche 2"); } } /* public boolean action(Event e, Object welcheAktion) { if (e.target.equals(sch1)) getAppletContext().showStatus("Schaltflaeche 1"); else if (e.target.equals(sch2)) getAppletContext().showStatus("Schaltflaeche 2"); else return super.action(e,welcheAktion); return true; } 319 Programmieren in Java */ } Einfügen / Entfernen passender Listener Das neue Event-Modell von Java 1.1 bzw. 1.2 hat mit dem JDK 1.0 offensichtlich nur noch wenig gemeinsam. Alle AWT-Komponenten haben im neuen AWT-EventHandling „addXXXListener()“und „removeXXXListener()“-Methoden erhalten. „XXX“ beschreibt den Typ des Events. Die folgende Tabelle zeigt Ereignisse, „Listeners“, Methoden und Komponenten, die über addXXXListener() bzw. removeXXXListener() auf spezifische Ereignisse reagieren: Event, „listener interface“ „remove“-Methoden ActionEvent ActionListener addActionListener() removeActionListener() AdjustmentEvent AdjustmentListener addAdjustmentListener() removeAdjustmentListener() ComponentEvent ComponentListener addComponentListener() removeComponentListener() ContainerEvent ContainerListener addContainerListener() removeContainerListener() FocusEvent FocusListener addFocusListener() removeFocusListener() KeyEvent KeyListener addKeyListener() removeKeyListener() MouseEvent MouseListener addMouseListener() removeMouseListener() MouseEvent MouseMotionListener addMouseMotionListener() removeMouseMotionListener() WindowEvent WindowListener addWindowListener() removeWindowListener() ItemEvent ItemListener addItemListener() removeItemListener() und „add“- bzw. Komponenten, die dieses Ereignis unterstützen Button, List, TextField, MenuItem und seine Abkömmlinge einschl. CheckboxMenuItem, Menu und PopupMenu Scrollbar, alles was mit der Erzeugung vom „Adjustable“Interface zu tun hat Component und seine Abkömmlinge, einschl. Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, TextField Container und seine Abkömmlinge, einschl. Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame Component und seine Abkömmlinge einschl. Button, Canvas, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, TextField Component und seine Abkömmlinge einschl. Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, TextField Component und seine Abkömmlinge einschl. Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, TextField Component und seine Abkömmlinge einschl. Button, Canvas, Checkbox, Choice, Container, Panel, Applet, ScrollPane, Window, Dialog, FileDialog, Frame, Label, List, Scrollbar, TextArea, TextField Window und seine Abkömmlinge einschl. Dialog, FileDialog und Frame Checkbox, CkeckboxMenuItem, Choice, List und alles, was das ItemSelectable Interface implementiert 320 Programmieren in Java TextEvent TextListener addTextListener() removeTextListener() Alles was von TextComponent einschl. TextArea und TextField abgeleitet ist Abb.: Event- und Listener-Typen Jeder Komponenten-Typ unterstützt nur bestimmte Ereignis-Typen: Komponenten-Typ Adjustable Applet Button Canvas Checkbox CheckboxMenuItem Choice Component Container Dialog FileDialog Frame Label List Menu MenuItem Panel PopupMenu Scrollbar ScrollPane TextArea TextComponent TextField Window Ereignis, das durch die Komponente unterstützt wird AdjustmentEvent ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ActionEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ItemEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ActionEvent, ItemEvent ItemEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent FocusEvent, KeyEvent, MouseEvent, ComponentEvent ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent FocusEvent, KeyEvent, MouseEvent, ComponentEvent ActionEvent, FocusEvent, KeyEvent, MouseEvent, ItemEvent, ComponentEvent ActionEvent ActionEvent ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ActionEvent AdjustmentEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ContainerEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ActionEvent, TextEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent ContainerEvent, WindowEvent, FocusEvent, KeyEvent, MouseEvent, ComponentEvent Abb.: Komponenten-Typ und Ereignisse Methoden für die Ereignisbehandlung 321 Programmieren in Java Da nun bekannt ist, welche Ereignisse eine bestimmte Komponente unterstützt, kann das zugehörige „Listener“-Interface angegeben werden: „Listener“-Interface ActionListener AdjustmentListener ComponentListener ComponentAdapter ContainerListener ContainerAdapter FocusListener FocusAdapter KeyListener KeyAdapter MouseListener MouseAdapter MouseMotionListener MouseMotionAdapter WindowListener Windowadapter ItemListener TextListener Methoden actionPerformed(ActionEvent) AdjustmentValueChanged( AdjustmentEvent) componentHidden(ComponentEvent) componentShown(ComponentEvent) componentMoved(ComponentEvent) componentResized(ComponentEvent) componentAdded(ContainerEvent) componentRemoved(ContainerEvent) focusGained(FocusEvent) focusLost(FocusEvent) keyPressed(KeyEvent) keyReleased(KeyEvent) keyTyped(KeyEvent) mouseClicked(MouseEvent) mouseEntered(MouseEvent) mouseExited(MouseEvent) mousePressed(MouseEvent) mouseReleased(MouseEvent) mouseDragged(MouseEvent) mouseMoved(MouseEvent) windowOpened(WindowEvent) windowClosing(WindowEvent) windowClosed(WindosEvent) windowActivated(WindowEvent) windowDeactivated(WindowEvent) windowIconified(WindowEvent) windowDeiconified(WindowEvent) itemStateChanged(ItemEvent) textValueChanged(TextEvent) Abb.: Listener-Interface Adapter besorgen Default-Methoden für jede Methode im Interface. Nur die Methode, die geändert wird, muß überschrieben werden. Komponenten des AWT in Fenstern bzw. Applets unter Java 1.1 Die in den folgenden Programmen verwendeten Komponenten können in einem Fenster (Frame) über die Instanz eines Applet dargestellt werden. Das Programm enthält deshalb zusätzlich zu den für Applets nötigen Methoden eine main()Methode, die eine Instanz eines Applets innerhalb eines Frame aufbaut. Bsp.: Bearbeitung von zwei Schaltflächen und einem Textfeld236 // Eine Anwendung und ein Applet import java.awt.*; import java.awt.event.*; import java.applet.*; public class SchalterApplet extends Applet 236 pr55202 322 Programmieren in Java { Button sch1 = new Button("Schaltflaeche 1"); Button sch2 = new Button("Schaltflaeche 2"); TextField t = new TextField(20); public void init() { sch1.addActionListener(new Sch1()); sch2.addActionListener(new Sch2()); add(sch1); add(sch2); add(t); } class Sch1 implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Schaltflaeche 1"); } } class Sch2 implements ActionListener { public void actionPerformed(ActionEvent e) { t.setText("Schaltflaeche 2"); } } // Schliessen der Applikation static class WL extends WindowAdapter { public void windowClosing(WindowEvent e) { System.exit(0); } } // main()-Methode fuer die Applikation public static void main(String[] args) { SchalterApplet applet = new SchalterApplet(); Frame einRahmen = new Frame("Schalterdarstellung"); einRahmen.addWindowListener(new WL()); einRahmen.add(applet,BorderLayout.CENTER); einRahmen.setSize(300,200); applet.init(); applet.start(); einRahmen.setVisible(true); } } 323 Programmieren in Java 5.6 Benutzerschnittstellen mit Swing Die Oberflächentechnik „Swing“ (Paket: javax.swing) von Java 2 umfaßt eine Vielzahl mächtiger Klassen (, z.B. Tabellen, Baumansichten). Swing benutzt den Model-View-Controller237-Ansatz, d.h. die konsequente Trennung der Daten und der Darstellung. Swing-Elemente fallen in zwei Gruppen: - Fenster, die direkt vom Betriebssystem dargestellt werden, z.B. JFrame, JDialog oder JApplet. Diese sind von den jeweiligen AWT-Komponenten abgeleitet. - Kontrollelemente, die von Java selbst gerendert werden. Diese sind Subklassen von JComponent. javax.swing, javax.swing.event, javax.swing.table, javax.swing.tree, javax.swing.text Interfaces - ButtonModel - ComboBoxModel - ListModel - TreeModel - TableModel - TableColumnModel - BoundedRangeModel - Renderer - ListCellRenderer - TableCellRenderer - TableCellEditor - TreeCellRenderer - TreeCellEditor - ComboBoxEditor - TreeNode - MutableTreeNode - Border - Scrollable - MenuElement - Document - Caret - Style - StyledDocument - DesktopManager - Icon - SwingConstants - *Listener Klassen Ereignisbehandlung KontrollElemente Fenster - ListDataEvent - JFrame - JComponent - ListSelectionEvent - JButton - JDialog - TableModelEvent - JPanel - JCheckBox - TreeModelEvent - JRadioButton - JApplet - TreeExpansionEvent - JLabel - JFileChooser - TreeSelectionEvent - JTextField - JColorChooser - CaretEvent - JPasswordField - JDesktopPane - MenuEvent - JTextArea - JInternalFrame - PopupMenuEvent - JComboBox - JOptionPane - JList - JScrollPane - JScrollBar - JTabbedPane - JSlider - JProgressBar - JTree - JTable Modelimplementierung Border - AbstractListModel - DefaultListModel - AbstractTableModel - ... 237 - BorderFactory - TitledBorder - LineBorder - ... Bilder Menüs - JMenuBar - JMenuItem - JCheckboxMenuItem - JRadioBoxMenuItem - JMenu - JPopupMenu - JToolBar - JSeparator Sonstiges - ...... - ImageIcon -ButtonGroup - KeyStroke - ... Beim Model-View-Controller-Entwurfsmuster (MVC) unterscheidet man drei Arten von Applikationselementen: Model (Datenmodell) Im Datenmodell werden Zustand und Funktionalität gespeichert. Es kann Anfragen vom View über seinen Zustand beantworten und Zustandsänderungen vom Controller oder anderen Objekten verarbeiten. View (Darstellung „look“) Die Ansicht weiß nichts oder nur sehr wenig (über das Datenmodell und stellt lediglich die Daten dar). Controller (Steuerung „feel“) Die Steuerung stellt ein Modell der realen Welt dar und reagiert auf Benutzereingaben von Maus, Tastatur und anderen Eingabegeräten. 324 Programmieren in Java 5.6.1 Die Komponentenstruktur javax.swing.JComponent ist von java.awt.Container abgeleitet. Container können alle JComponents auch andere Komponenten enthalten. Container JComponent public void addAncestorListener(AncestorListener l) public void addNotify() ... public boolean contains(int x, int y) public JToolTip createToolTip() ... public float getAlignmentX() public float getAlignmentY() ... public Border getBorder() public void setBorder(Border border) public Rectangle getBounds(Rectangle rv) protected Graphics getComponentGraphics (Graphics g) ... public Graphics getGraphics() public int getHeight() public Insets getInsets() ... public Point getLocation(Point rv) public Dimension getMaximumSize() public Dimension getMinimumSize() ... … public Dimension getSize(Dimension rv) public Point getToolTipLocation(MouseEvent event) public String getToolTipText() public String getToolTipText(MouseEvent event) ... public int getWidth() public int getX() public int getY() ... public void paint(Graphics g) ... public void repaint(Rectangle r) ... public void setToolTipText(String text) protected void setUI(ComponentUI newUI) public void setVisible(boolean aFlag) ... public void update(Graphics g) public void updateUI() … Abb. Die Klasse JComponent 325 Als Programmieren in Java Viele der Swing-Komponenten sind direkt oder indirekt aus der Klasse JComponent abgeleitet Instanzen von JComponent können ausgestattet werden mit Umrandungen, Tooltips, transparentem Hintergrund, Doppelpufferung, etc. Umrandungen Instanzen von JComponent können über die Methode setBorder() Umrandungen erhalten. public void setBorder(Border border) Border ist ein Interface, zu dem es verschiedene Implementierungen gibt. Die wichtigsten sind: Klassenname EmptyBorder LineBorder BevelBorder EtchedBorder CompoundBorder TitledBorder Beschreibung Unsichtbarer Rand mit einstellbarer Dicke Einfache Linie mit einstellbarer Farbe und Dicke Erhabener und vertiefter 3D-Effekt Eingelassene Line mit 3D-Effekt Aus 2 anderen Umrandungen zusammengesetzt Umrandung mit einem eingeketteten Text Abb.: Border-Implementierungen Die Klassen besitzen sehr unterschiedliche Konstruktoren, mit denen ihre jeweiligen Eigenschaften festgelegt werden. Border-Instanzen können mit new über die Klasse BorderFactory mit den Methoden createEmptyBorder(), createLineBorder(), usw. erzeugt werden. Bsp.238: import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.border.*; public class Raender extends JPanel { public Raender() { setLayout(new GridLayout(2,4)); add(showBorder(new TitledBorder("Titel"))); add(showBorder(new EtchedBorder())); add(showBorder(new LineBorder(Color.blue))); add(showBorder(new MatteBorder(5,5,30,30,Color.green))); add(showBorder(new BevelBorder(BevelBorder.RAISED))); add(showBorder(new SoftBevelBorder(BevelBorder.LOWERED))); add(showBorder(new CompoundBorder(new EtchedBorder(), new LineBorder(Color.red)))); } static JPanel showBorder(Border b) { JPanel jp = new JPanel(); jp.setLayout(new BorderLayout()); String nm = b.getClass().toString(); nm = nm.substring(nm.lastIndexOf('.')+1); jp.add(new JLabel(nm,JLabel.CENTER),BorderLayout.CENTER); 238 pr56001 326 Programmieren in Java jp.setBorder(b); return jp; } public static void main(String args[]) { JFrame frame = new JFrame("Begrenzungen"); // frame.addWindowListener(new WindowClosingAdapter(true)); frame.getContentPane().add(new Raender(),BorderLayout.CENTER); frame.setLocation(100,100); frame.setSize(500,300); frame.pack(); /* Die Methode pack() sorgt fuer die Reduzierung der Groesse des Frame, dass alle Komponenten gerade darin noch Platz finden */ frame.setVisible(true); /* Macht den Frame sichtbar */ } } Abb. Tooltips JComponent kann Komponenten ein Tooltip-Text zuweisen. Dieser wird angezeigt, wenn die Maus über das Dialogelement bewegt und dort angehalten wird. TooltipTexte werden mit setToolTipText() zugewiesen bzw. mit getToolTipText() abgefragt: public void setToolTipText(String text) public void getToolTipText() Das Fenster mit dem Hilfetext ist immer im Swing-Fenster und nie außerhalb. Passt die Hilfe nicht auf das Fenster wird sie gar nicht dargestellt, und die Komponente macht durch "Flackern" auf sie aufmerksam Bsp.239: import java.awt.*; import javax.swing.*; public class TooltipDemo { public static void main(String args []) { JFrame frame = new JFrame(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); String text = "<html>Ich brauche Hilfe.<p>Tempo</html>"; JButton button = new JButton(text); String hilfe = "<html>Hier ist sie, die <b>Hilfe:</b>" + "<ul><li>Cool bleiben<li>Handbuch lesen</ul></html>"; 239 pr56001 327 Programmieren in Java button.setToolTipText(hilfe); frame.getContentPane().add(button); frame.setSize(250, 250); frame.show(); } } Abb.: Tooltip-Demonstration Bildschirmdarstellung der Komponenten Normalerweise braucht eine GUI-Anwendung sich um die konkrete Darstellung nicht zu kümmern. Das ändert sich, wenn eine eigene Komponente entwickelt werden soll. Bei AWT-Anwendungen wurde dies aus Canvas abgeleitet, in überlagerten Varianten von paint() oder update() wurde die nötige Bildschirmausgabe bereit gestellt. Die Methode paint() hat in JComponent eine recht aufwendige Implementierung und wird normalerweise nicht mehr überlagert. Im Prinzip ruft sie nacheinander ihre Methoden paintComponent(), paintBorder() (Zeichnen der Umrandung) und paintChildren() (Zeichnen der Dialogelemente) auf. Für die Darstellung der eigenen Komponenten ist protected void paintComponent(Graphics g) zuständig. In JComponent wird jeder Aufruf von paintComponent() an das ComponentUI delegiert. Jede Swing-Komponente besitzt ein ComponentUI, das je Look and Feel unterschiedlich sein kann. Eine selbstdefinierte Komponente muß also entweder für jedes Look and Feel ein passendes ComponentUI zur Verfügung stellen oder die Bildschirmdarstellung durch Überlagern von paintComponent() selbst erledigen. Doppelpufferung Swing-Komponenten, die aus JComponent abgeleitet sind, können automatisch doppelgepuffert werden. Dazu ist lediglich der Aufruf von public void setDoubleBuffered(boolean einFlag) mit Übergabe von "true" erforderlich. Mit 328 Programmieren in Java public boolean isDoubleBuffered() kann der aktuelle Zustand abgefragt werden. Größenangaben In der Klasse Component sind die Methoden getMinimumSize(), getPreferredSize(), getMaximumSize() definiert. Sie werden in abgeleiteten Klassen überlagert, um dem Layoutmanager die minimale, optimale, maximale Größe mitzuteilen. In JComponent gibt es zusätzlich public void setMinimumSize(Dimension minimumSize) public void setPreferredSize(Dimension preferredSize) public void setMaximumSize(Dimension maximumSize) zur Veränderung der Größenangaben bestehender Komponenten. Invalidierung / Validierung Zur Mitteilung an den Container, dass das Layout seiner Komponenten komplett aufgebaut werden soll, ist validate() aufzurufen. public void validate() Zuvor muß der Container, auf dem der Aufruf erfolgte, zuvor mit public void invalidate() als ungültig deklariert sein. In JComponent gibt es zusätzlich revalidate(), das die beiden Schritte nacheinander durchführt. 329 Programmieren in Java 5.6.2 Fenster Container Window JComponent Frame Dialog JFrame JDialog Container contentPane Int defaultCloseOperation << Konstruktoren >> public JFrame() public JFrame(String titel) << Konstruktoren >> public JDialog() Abb.: Die Klassen JFrame und JDialog Die Swing-Klassen für Hauptfenster (JFrame) und Dialogboxen (JDialog) sind direkt von den AWT-Klassen Frame und Dialog abgeleitet und erben all ihre Methoden. Der Hauptunterschied ist, daß keine Kontrollelemente mehr direkt auf das Fenster platziert werden, sondern auf einen Client-Container (mit dem Namen „ContentPane“). Nun können beide Klassen Menüleisten aufnehmen (Typ JMenuBar) und besitzen Vorgaben beim Schließen eines Fensters. 5.6.2.1 JFrame class javax.swing.Jframe extends Accessible, RootPaneContainer Frame implements WindowConstants, JFrame dient für den Ersatz der schwergewichtigen Komponente Frame und ist Ausgangsbasis für die meisten Swing-Oberflächen. Die Klasse JFrame erbt die wichtigsten Methoden zum Administrieren von Rahmen von ihren Vorgängern. Die wichtigste Methode ist die von Component geerbte Methode setVisible(true) (oder show()), da Fenster standardmäßig nicht sichtbar sind. „dispose()“ macht Fenster unsichbar und gibt damit alle verbundenen Ressourcen frei. Ein JFrame ist ein vollwertiges Fenster. Es besitzt einen Rahmen mit Titelleiste und u.U.a. eine MenuBar (Attribut JMenuBar). „resizable()“ gibt an, ob der Benutzer die Größe eines Fensters verändern kann und iconImage() dient zur Festlegung eines Symbols, falls das Fenster den Zustand (state) Frame.ICONIFIED annimmt. 330 Programmieren in Java Unterschied zum AWT-Frame. Mit frame.add(komponente) wird in einem AWTProgramm ein Objekt zu einem Fenster hinzugefügt. Bei einem JFrame muß das Component-Objekt erst zu der Zeichenfläche, die ContentPane genammt wird, zugefügt werden, z.B.: Container con = frame.getContentPane(); con.add(komponente); Das läßt sich abkürzen zu: frame.getContentPane().add(komponente) Auch beim Schließen eines Fensters verhält sich ein JFrame etwas anders als ein AWT-Frame. Beim JFrame verschwindet das Fenster in den Hintergrund. Dieses Verhalten kann mit der Funktion „void setDefaultCloseOperation(int operation)“ geändert werden. So verhält sich bspw. mit setDefaultCloseOperation(WindowConstants.DO_NOTHING_ON_CLOSE) ein JFrame wie ein Frame. Jetzt lässt sich mit Hilfe eines WindowListener-Objekts auf windowClosing() reagieren. Neben DO_NOTHING_ON_CLOSE existieren drei weitere Konstanten: WindowConstants.HIDE_ON_CLOSE Das Fenster wird automatisch verdeckt, nachdem die WindowListener aufgerufen werden (Standard). WindowConstants.DISPOSE_ON_CLOSE Bewirkt die Freigabe der Ressourcen, nachdem die Listener abgearbeitet sind und das Fenster geschlossen wird. WindowConstants.EXIT_ON_CLOSE Ruft System.exit() auf und schließt die Anwendung. „getDefaultCloseOperation()“ liefert die eingestellte Eigenschaft beim Schließen des Fensters. Das Applikationsgerüst. Für das Einfügen in einen Swing-Frame ist folgendes Applikationsgerüst zweckmäßig: Container und Komponenten müssen in einem Zwischen-Container mit dem Namen „ContentPane“ (Inhaltsbereich) eingefügt werden. Der JFrame ist in verschieden Bereiche (panes) unterteilt. Der Hauptbereich ist der Inhaltsbereich, der die gesamte Fläche des Frame repräsentiert, in den Komponenten eingefügt werden können. Das Erzeugen einer Komponenten in den Inhaltsbereich umfasst: - Erzeugen eines JPanel-Objekts Einfügen aller Komponenten in das JPanel-Objekt über dessen add (Component)-Methode Implementieren des JPanel-Objekts über die Methode setContentPane(Container). Bsp.240: import java.awt.*; import java.awt.event.*; import javax.swing.*; public class Rahmenwerk extends JFrame { public Rahmenwerk() { super("Anwendung - Titel"); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLocation(100,100); setSize(300,200); pack(); 240 pr56110 331 Programmieren in Java /* pack() sorgt fuer die Reduzierung der Groesse des Frame, dass alle Komponenten gerade darin noch Platz finden */ setVisible(true); /* Macht den Frame sichtbar */ } public static void main(String args[]) { JFrame frame = new Rahmenwerk(); } } Layout. LayouManager verwalten die Anordnung der Komponenten in einem Container. Das Layout wird durch die Methode setLayout(LayoutManager lm) angegeben. Bsp.241: Anwendung des LayoutManagers BorderLayout Das Programm fügt in das JFrame ein Texfeld mit einem Scrollbalken, einem JColorChooser und 2 "Karteikarten mit jeweils einer Tabelle" ein. Das Textfeld wird durch das Anlegen eines JTextArea-Objekts erzeugt. Durch Übergabe dieses Objekts an den Konstruktor eines JScrollPane-Objekts wird das Textfeld mit einem Scrollbalken auf der rechten Seite ausgestattet. import javax.swing.*; import java.awt.*; import java.awt.event.*; public class Pr56352 extends JFrame { Color farbe; JColorChooser jcc; JButton jw; public static void main(String args[]) { Pr56352 pr56352 = new Pr56352(); } public Pr56352() { super("BorderLayout"); JTextArea ja = new JTextArea("Norden"); JScrollPane jsp = new JScrollPane(ja, JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); jsp.setPreferredSize(new Dimension((int) this.getWidth(),100)); jcc = new JColorChooser(); jw = new JButton("Westen"); jw.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { farbe = jcc.getColor(); jw.setBackground(farbe); } }); JTabbedPane jtp = new JTabbedPane(2); JTable jt1 = new JTable(13,2); JTable jt2 = new JTable(10,1); jt2.setBackground(Color.green); jtp.addTab("2",jt1); jtp.addTab("1",jt2); JButton js = new JButton("Sueden"); 241 vgl. pr56352 in pr56350 332 Programmieren in Java js.setBackground(Color.yellow); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } } ); getContentPane().setLayout(new BorderLayout()); getContentPane().add(BorderLayout.NORTH,jsp); getContentPane().add(BorderLayout.WEST,jw); getContentPane().add(BorderLayout.EAST,jtp); getContentPane().add(BorderLayout.CENTER,jcc); getContentPane().add(BorderLayout.SOUTH,js); setSize(700,400); setVisible(true); } } Abb. Grafische Benutzroberfläche, die mit BorderLayout erzeugt wurde 333 Programmieren in Java Interne Struktur von JFrame. Die interne Struktur weist mehrere übereinanderliegende Bereiche auf, denen verschiedene Aufgaben zugeteilt wurden. Im wesentlichen sind dies: - Wurzelbereich - Schichtbereich (dient hauptsächlich zur Darstellung von Menüs) - Inhaltsbereich (Container für die meisten visuellen Elemente, kann mit getContentPane() angefordert werden) - Glasbereich (Komponenten, die in den Glasbereich eingefügt werden, erscheinen im Vordergrund und überdecken die darunter liegenden Schichten. Diese Eigenschaft ist vor allem für die Darstellung von PopUp-Menüs von Bedeutung.) JFrame JRoot JLayeredPane Inhaltsbereich Glasbereich Abb.: Interne Struktur von JFrame Die Hauptkomponente wird als RootPane bezeichnet und ist vom Typ JRootPane. Eine RootPane enthält folgende Komponenten: - eine aus Container abgeleitete GlassPane - eine aus JLayeredPane abgeleitete LayeredPane. Eine LayeredPane enthält ihrerseits zwei Unterkomponenten: - eine aus Container abgeleitete ContentPane - eine aus JMenuBar abgeleitete Menüleiste. Die RootPane und mit ihr die darin enthaltene GlassPane, LayeredPane und ContentPane werden beim Anlegen des Fensters automatisch erzeugt (nur die Menüleiste bleibt standardmäßig leer). Außerdem implementieren alle Hauptfenster das Interface RootPaneContainer, das den Zugriff auf die RootPane vereinfacht. Einige seiner Methoden sind: public public public public JRootPane getRootPane() Container getContentPane() JLayeredPane getLayeredPane() Component getClassPane() Neben den “getter”-Methoden gibt es auch „setter“-Methoden, die den strukturellen Aufbau der RootPane vollständig verändern können. 334 Programmieren in Java 5.6.2.2 JWindow JWindow ist aus Window abgeleitet und dient dazu, ein rahmenloses Fenster zu erzeugen, das an beliebiger Stelle und in beliebiger Größe auf dem Bildschirm platziert werden kann. 5.6.2.3 JDialog, JFileChooser, JColorChooser JDialog Mit der aus Dialog abgeleiteten Klasse JDialog stehen auch im Swing Dialogfenster zur Verfügung. Dialogfenster unterscheiden sich von Hauptfenstern, dass sie kein Menü und nur eingeschränkte Systemfunktionen besitzen. Sie besitzen denselben strukturellen Aufbau wie JFrame und JWindow und implementieren ebenfalls das Interface RootPaneContainer. Auch hier erfolgt das Hinzufügen und Anordnen von Komponenten nicht auf dem Fenster selbst, sondern auf seiner ContentPane. Die (wichigsten) Konstruktoren sind: public public public public JDialog(Frame JDialog(Frame JDialog(Frame JDialog(Frame owner) owner, bollean modal) owner, String titel) owner, String titel, boolean modal) Als "owner" sollte der Aufrufer das Fenster übergeben, zu dem der Dialog logisch gehört. Alle Konstruktoren gibt es auch owner vom Typ Dialog. Wahlweise kann ein JDialog auch ohne owner konstruiert werden (mit dem parameterlosen Konstruktor), doch kann es dann u.U. Fokusprobleme beim Wechsel zwischen den Anwendungen geben. Modaler bzw. nicht-modaler Dialog. Bei einem modalen Dialog wird der Aufruf von show() (bzw. setVisible(true)) erst dann beendet, wenn der Dialog geschlossen wurde. Bei einem nicht-modalen Dialog fährt das Programm dagegen unmittelbar nach der nächsten Anweisung hinter show() fort. JFileChooser Diese Klasse ermöglicht einen Standarddialog, mit dessen Hilfe komfortable und Betriebssystem unabhängige Datei- und Verzeichnisauswahl möglich ist. JColorChooser Diese Klasse erlaubt eine einfache, visuelle und kontextunabhängige Auswahl von Farbinformationen. 335 Programmieren in Java Bsp.242: JColorChooserTest import java.awt.*; import java.awt.event.*; import javax.swing.*; public class JColorChooserTest extends JFrame implements ActionListener { public JColorChooserTest() { super("Using JColorChooser"); // WindowUtilities.setNativeLookAndFeel(); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); Container content = getContentPane(); content.setBackground(Color.white); content.setLayout(new FlowLayout()); JButton colorButton = new JButton("Choose Background Color"); colorButton.addActionListener(this); content.add(colorButton); setSize(300, 100); setVisible(true); } public void actionPerformed(ActionEvent e) { // Args are parent component, title, initial color Color bgColor = JColorChooser.showDialog(this, "Choose Background Color", getBackground()); if (bgColor != null) getContentPane().setBackground(bgColor); } public static void main(String[] args) { new JColorChooserTest(); } } Nach Drücken des Button "ChooseBackgroundColor" erscheint 242 pr56370 336 Programmieren in Java zur Auswahl der gewünschten Farbe, die hier zum Füllen des Hintergrunds benutzt wird. Mit dem Dialog lassen sich Farben in drei unterschiedlichen Dialogen auswählen. Der Benutzer hat die Auswahl unter den vordefinierten Farben, HSB-Werten und RGB-Werten. Der Farbauswahl-Dialog erscheint auf dem Bildschirm nach dem Aufruf JColorChooser.showDialog() mit drei Argumenten: einem Component-Objekt, auf dem gezeichnet werden kann dem Titel einer Anfangsfarbe Rückgabewert ist nach Beenden des Dialogs die ausgewählte Farbe (bei Abbruch: null). 5.6.2.4 JOptionPane Die Klasse javax.swing.JOptionPane ist in der Lage, einfache Dialoge, die lediglich ein Icon, einen Text oder ein Eingabefeld und eine Anzahl der Schaltflächen "Yes", "No" und "Cancel" enthalten, mit einem einzigen Aufruf einer statischen Methode zu erzeugen. 337 Programmieren in Java 5.6.2.5 JInternalFrame Bei vielen Programmen gibt es ein einziges Hauptfenster und zahlreiche geöffnete Kindfenster innerhalb des Hauptfensters. Diese unter Windows als MDI (Multiple Document Interface) bezeichnete Technik ist mittlerweise weit verbreitet. In Swing können MDI-Anwendungen entwickelt werden. Dazu werden zwei Arten von Komponenten benötigt: - das als Desktop bezeichnete Hauptfenster der Anwendung - das Subfenster der Anwendung 5.6.2.6 JPanel und JLayeredPane JPanel Ein JPanel ist ein Fensterbereich, in dem andere Kontrollelemente platziert werden können (Container). Hauptaufgabe ist die Gestaltung und Gliederung von Benutzeroberflächen. JPanel ist die Basisklasse für GUI-Container, die nicht Hauptfenster sind. Standardmäßig ist einem JPanel ein FlowLayout als LayoutManager zugeordnet. Neben den geerbten Methoden von JComponent, Container, Component und Object kommen keine nenneswerten Funktionen hinzu. Konstruktoren. public JPanel() // erzeugt ein neues JPanel mit Double-Buffering public JPanel(boolean isDoubleBuffered) // erzeugt ein neues JPanel mit FlowLayout und der angegebenen Puffer-Strategie public JPanel(LayoutManager layout) // erzeugt einen JPanel mit Double-Buffering und dem angegebenen Layout-Manager public JPanel(LayoutManager layout, boolean isDoubleBuffered) // erzeugt ein neues JPanel mit dem angegebenen LayoutManager und der angegebenen Puffer// Strategie JLayeredPane JLayeredPane fügt den Dialogen eine dritte Dimension hinzu und ermöglicht mit Hilfe eines Layerkonzepts die kontrollierte Anordnung von Komponenten übereinander. JLayeredPane ist die Elternklasse von JDesktopPane und wichtiger Bestandteil der Struktur von Hauptfenstern. 338 Programmieren in Java 5.6.3 Menüs 5.6.3.1 Grundlagen Menüs dienen zur Auswahl von Funktionen und Optionen in Anwendungen. Sie können entweder an der Oberseite von Fenstern oder als PopUp-Menüs an einer beliebigen Stelle auftauchen. JMenuBar In Swing können alle Hauptfenster mit Ausnahme von JWindow eine Menüleiste haben. JMenu Die einzelnen Menüs einer Menüleiste sind Instanzen der Klasse JMenu, die aus JMenuItem abgeleitet ist. JMenuItem Die Klasse JMenuItem repräsentiert Menüeinträge, also Elemente, die sich in einem Menu befinden. Dabei handelt es sich um Texte, die wahlweise mit einem Icon oder mit einem Häkchen versehen werden können. 5.6.3.2 Kontextmenüs 339 Programmieren in Java JComponent AbstractButton JMenuItem << Konstruktoren >> public JMenuItem() public JMenuItem(Icon icon) public JMenuItem(String text) public JMenuItem(String text, Icon icon) public JMenuItem(String text, int mnemonic) << Methoden >> public void addMenuDragMouseListener(MenuDragMouseListener l) public void addMenuKeyListener(MenuKeyListener l) public KeyStroke getAccelerator() … public MenuKeyListener [] getMenuKeyListeners() public MenuElement [] getSubElements() … public void init(String text, Icon icon) … public void removeKeyListener(MenuKeyListener l) public void setAccelerator(KeyStroke keyStroke) … public void setEnabled(boolean b) public void setUI(MenuItem ui) public void updateUI() JMenu JCheckBoxMenuItem << Konstruktoren >> public JMenu() public JMenu(Action a) public JMenu(String s) public JMenu(String s, boolean b) << Methoden >> public JMenuItem add(Action a) public JMenuItem add(JMenuItem menuItem) public JMenuItem add(String s) public addmenuListener(MenuListener l) public void addSeparator() … public void doClick(int pressTime) … public JMenuItem getItem(int pos) public int getItemCount() … public JMenuItem insert(Action a, int pos) Abb.: Die Swing-Menüklassen 340 JRadioButtonMenuItem Programmieren in Java JComponent JMenuBar JPopupMenu << Konstruktor >> public JMenuBar() << Methoden >> public void add(JMenu c) public void addNotify() … public JMenu getMenu(int index) … public MenuElement [] getSubElements() public MenuBarUI getUI() … public boolean isSelected() … public void setHelpMenu(JMenu menu) … public void setUI(MenuBarUI ui) public void updateUI() << Konstruktor >> public JPopupMenu() public JPopupMenu(String label) << Methoden >> public JMenuItem add(Action a) public JMenuItem add(JMenuItem m) public JMenuItem add(String s) 341 Programmieren in Java 5.6.4 Die Container-Klassen JToolBar, JSplitPane, JScrollPane, JTabbedPane JToolBar Mit JToolBar können Werkzeugleisten erstellt werden, die Buttons enthalten. JSplitPane Das Kontrollelment teilt eine Fläche in zwei Teile. JScrollPane Der Container JScrollPane ermöglicht die Anzeige eines Komponentenausschnitts. In Verbindung mit den Klassen JTextArea und JList besteht die Ausgabe von JScrollPane darin, Dialogelemente, die zu groß für den zur Verfügung stehenden Platz sind, mit Hilfe eines verschiebbaren Fensters ausschnittsweise sichtbar zu machen. Konstruktoren. public JScrollPane(Component view) public JScrollPane(Component view, int vsbPolicy, int hsbPolicy) Die Argumente vsbPolicy und hsbPolicy geben an, wann ein horizintaler bzw. vertikaler Schieberegler eingeblendet wird. Es können folgende Werte angegeben werden: Konstante VERTICAL_SCROLLBAR_NEVER VERTICAL_SCROLLBAR_ALLWAYS VERTICAL_SCROLLBAR_AS_NEEDED HORIZONTAL_SCROLLBAR_NEVER HORIZONTAL_SCROLLBAR_ALLWAYS HORIZONTAL_SCROLLBAR_AS_NEEDED Bedeutung Der vertikale Schieberegler wird nie angezeigt. Der vertikale Schieberegler wird immer angezeigt. Der vertikale Schieberegler wird nur angezeigt, wenn er benötigt wird Der horizontale Schieberegler wird nie angezeigt Der horizontale Schieberegler wird immer angezeigt Der horizontale Schieberegler wird nur angezeigt, wenn er benötigt wird Falls die Argumente vsbPolicy und hsbPolicy nicht angegeben werden, blendet JScrollPane die Schieberegler nur dann ein, wenn sie wirklich benötigt werden. Methoden. public void setColumnHeaderView(Component view) Angabe einer Komponente für den Spaltenkopf. Sie wird über dem eigentlichen Dialogelement angezeigt, bei horozontalen Bewegungen mit diesem verschoben. Bei vertikalen Schieberbewegungen bleiben sie dagegen am Platz. public void setRowheaderView(Component view) Angabe eines Zeilenkopfs links neben der eigentlichen Komponente, wird bei vertikalen Bewegungen verschoben, behält bei horizontalen Bewegungen seinen Platz bei. 342 Programmieren in Java public void setCorner(String key, Component corner) Ein Dialogelement kann in eine beliebige der vier ungenutzten Ecken243 einer JScrollPane plaziert werden. Der Parameter key gibt dabei an, welche Ecke belegt werden soll. Argument kann einer der Konstanten LOWER_LEFT_CORNER, LOWER_RIGHT_CORNER, UPPER_LEFT_CORNER, UPPER_RIGHT_CORNER sein. Spaltenheader Eckfläche Eckfläche ViewPort Zeilenheader Eckfläche Vertikaler Schieberegler Eckfläche Horizontaler Schieberegler Abb.: Aufbau einer JScrollpane Der sichtbare Ausschnitt heißt ViewPort und wird durch folgende Methoden eingestell: public void setViewPort(JViewPort viewport) public void setViewPortView(Component view) eingestellt. JTabbedPane Ein JTabbedPane erlaubt die Darstellung mehrerer Container, die über Kartenreiter zugänglich sind. 243 u.U. stehen Eckflächen nicht zur Verfügung. Die beiden Ecken auf der linken Seite sind bspw. nur vorhanden, wenn ein Zeilenkopf eingeblendet wurde. Die rechte obere ist nur vorhanden, wenn ein vertikaler Schieberegler eingeblendet wurde, und die rechte untere erfordert sogar die Anwesenheit beider Schieberegler. Auch kann die Anwendung praktisch keinen Einfluß auf die Größe der Ecken nehmen. Diese wird ausschließlich die Ausdehnung der Schieberegler und des Zeilenkopfes bestimmt. 343 Programmieren in Java 5.6.5 Swing-Komponenten 5.6.5.1 Label und Textfelder JLabel Ein JLabel ist ein Dialogelement zur Anzeige und Beschriftung innerhalb eines GUIComtainers, besitzt Text und Icon, die in beliebiger Anordnung dargestellt werden können. Die beiden Elemente sind optional, ein JLabel kann auch nur ein Icon enthalten. Auf Benutzereingeben reagiert ein JLabel nicht. In der Swing-Bibliothek gibt es mit dem Interface Icon bzw. der Klasse ImageIcon Unterstützung zur Bildverarbeitung, wobei der Ladevorgang mit Hilfe von Mediatracker intern erledigt wird. Konstruktoren. public JLabel(String text) public JLabel(Icon image) public JLabel(String text, Icon icon,int horizontalAlignment) // Der Parameter horizontalAlignment gibt an, wie das Label horizontal plaziert werden soll, falls // links und rechts mehr Platz als erforderlich zur Verfügung steht. Hier kann eine der Konstanten // LEFT, CENTER oder RIGHT aus dem Interface SwingConstants angegeben werden. Methoden. public void setHorizonatTextPosition(int textPosition) public void getHorizontalTextPosition() public void setHorizontalAlignment(int alignment) // horizontale Ausrichtung, mögliche Parameter: LEFT, CENTER, RIGHT public int getHorizontalAlignment() public void setVerticalAlignment(int alignment) // vertikale Ausrichtung, mögliche Parameter: TOP, CENTER, BOTTOM public int getVerticalAlignment() Anzeige von Bildern (im Gegensatz zur AWT-Implementierung). Angegeben werden zur Anzeige von Bildern Icon-Objekte. Die Klasse ImageIcon implemntiert die IconSchnittstelle. interface java.awt.swing.Icon public int getIconWidth() // liefert die Breite eines Icons public int getIconHeight() // liefert die Höhe eines Icons public void paintIcon(Component c, Graphics g, unt x, int y) // zeichnet das Icon an die angegebene Position. Die Klasse ImageIcon implementiert die Icon-Schnittstelle und nutzt die Klasse Image (und deren verwandte Klassen. Ein ImageIcon ist serialisierbar (implementiert das Interface Serializable). Eine Referenz auf das Image-Objekt kann durch den Aufruf von getImage() der Klasse Toolkit hergestellt werden. Mit der Methode protected void loadImage(Image image) wartet man dann mit Hilfe eines Mediatrackers auf das Bild. Dabei setzt der Mediatracker Höhe und 344 Programmieren in Java Breite, die sich über Icon-Methoden abfragen lassen. Zur Serialisierbarkeit sind die Methoden readObject() und writeObject() implementiert. Im zugehörigen Dateistrom befinden sich Breite und Höhe und anschließend ein Integer-Feld mit Pixelwerten. Bei readObject() liest s.readObject() – wobei s der aktuelle ObjectInputStream ist -, das Feld wieder ein. Über die Toolkit-Funktion createImage() wird die Klasse MemoryImage genutzt, um das Feld wieder bzu einem Image-Objekt zu konvertieren. Umgekehrt geht es genauso einfach: writeObject() schreibt Breite und Höhe und anschließend das Integer-Feld, das es über ein PixelGrabber bekommen hat. Die ImageIcon-Klasse kann somit das Laden von Icon-Objekten mit einer Programmzeilen beschreiben: Image image = new ImageIcon(("bild.jpg").getImage()) Da ein Image immer wieder in ein ImageIcon umgewandelt werden kann, eignet sich die Klasse hervorragend zum Laden und Speichern. Bsp.244: Einige Labels mit unterschiedlichen Eigenschaften import java.awt.*; import javax.swing.*; class LabelDemo extends JFrame { public LabelDemo() { super("JLabel Demo"); setSize(600, 100); JPanel content = (JPanel) getContentPane(); content.setLayout(new GridLayout(1, 4, 4, 4)); JLabel label = new JLabel(); label.setText("JLabel"); label.setBackground(Color.white); content.add(label); label = new JLabel("JLabel",SwingConstants.CENTER); label.setOpaque(true); label.setBackground(Color.white); content.add(label); label = new JLabel("JLabel"); label.setFont(new Font("Helvetica", Font.BOLD, 18)); label.setOpaque(true); label.setBackground(Color.white); content.add(label); ImageIcon image = new ImageIcon("flight.gif"); label = new JLabel("JLabel", image,SwingConstants.RIGHT); label.setVerticalTextPosition(SwingConstants.TOP); label.setOpaque(true); label.setBackground(Color.white); content.add(label); setVisible(true); } public static void main(String args[]) { new LabelDemo(); } } 244 pr56360 345 Programmieren in Java Abb.: Demonstration zu den Klassen JLabel und IconImage JTextField Ein JTextField ist ein einzeiliges Eingabefeld. Konstruktoren. public JTextField(int columns) // ein leeres Textfeld mit der angegebenen Anzahl Spalten public JTextField(String text) // erzeugt ein Textfeld mit angegebenem Text public JTextField(String text,int columns) // erzeugt ein Textfeld nach Vorgabe von Spaltenanzahl und Text // Die Spaltenanzahl wird zur Berechnung der Breite der Textfelder verwendet Methoden. public String getText() public void setText() public String getSelectedText() public int getSelectionStart() public void setSelectionStart() public void setSelectionEnd(int selectionEnd) public int getCaretPosition() public void setCaretPosition(int pos) public void moveCaretPosition(int pos) // verändert die Position der Einfügemarke und markiert dabei den Bereich zwischen alter und // neuer Position. Für alle Positionsangaben gilt, dass der Platz vor dem ersten Zeichen die // Position 0, und der nach dem letzten Textzeichen die Position Länge des Texts hat Listener. public void addActionListener(ActionListener l) // wird immer aufgerufen, wenn im Eingabefeld ENTER gedrückt wird. public void addCaretListener(CaretListener l) // wir aufgerufen, wenn sich die Position der Einfügemarke geändert hat Bsp.245: import javax.swing.*; import java.awt.*; public class JTextFieldTest extends JFrame { public JTextFieldTest() { super("JTextField Test"); getContentPane().setLayout(new FlowLayout()); JTextField textField1 = new JTextField("m",1); JTextField textField2 = new JTextField("mm",2); JTextField textField3 = new JTextField("mmm",3); JTextField textField4 = new JTextField("mmmm",4); 245 pr56370 346 Programmieren in Java JTextField textField5 = new JTextField("mmmmm",5); JTextField textField6 = new JTextField("mmmmmm",6); JTextField textField7 = new JTextField("mmmmmmm",7); JTextField textField8 = new JTextField("mmmmmmmm",8); JTextField textField9 = new JTextField("mmmmmmmmm",9); JTextField textField10 = new JTextField("mmmmmmmmmm",10); JTextField textField11 = new JTextField("mmmmmmmmmmm",11); JTextField textField12 = new JTextField("mmmmmmmmmmmm",12); JTextField textField13 = new JTextField("mmmmmmmmmmmmm",13); JTextField textField14 = new JTextField("mmmmmmmmmmmmmm",14); getContentPane().add(textField1); getContentPane().add(textField2); getContentPane().add(textField3); getContentPane().add(textField4); getContentPane().add(textField5); getContentPane().add(textField6); getContentPane().add(textField7); getContentPane().add(textField8); getContentPane().add(textField9); getContentPane().add(textField10); getContentPane().add(textField11); getContentPane().add(textField12); getContentPane().add(textField13); getContentPane().add(textField14); setSize(300,170); setVisible(true); } public static void main(String argv[]) { new JTextFieldTest(); } } Abb.: Demonstration zur Klasse JTextField JTextArea JTextArea ist eine Komponente zur Anzeige und Eingabe von mehrzeiligen Texten. Wie die AWT-Klasse TextArea dient sie dazu, unformatierte Texte zu bearbeiten. Diese können Zeilenumbrüche und Tabulatoren, nicht aber unterschiedliche Schriften, Farben oder grafische Elemente enthalten246. Konstruktoren. public JTextArea(String text) public JTextArea(int rows, int columns) public JTextArea(String text,int rows,int columns) 246 Für diesen Zweck gibt es die Klassen JEditorPane und JTextPane 347 Programmieren in Java Methoden. public int getLineCount() // liefert Anzahl der Zeilen public int getLineStartOffset(int line) public int getLineEndOffset(int line) // Zu einer beliebigen Zeile kann Anfangs- und Endoffset bestimmt werden. public int getLineOfOffset(int offset) // ermittelt die Nummer einer Zeile, wenn der Offset eines darin enthaltenen Zeichens bekannt ist. public void setTabSize(int size) public int getTabSize() // Zugriff auf die Tabulatorwerte (standardmäßig mit Wert 8247) public void setLineWrap(boolean wrap) // Festlegung, ob zu breite Spalten automatisch unterbrochen werden sollen public void setWrapStyleWord(boolean word) // Umbruch an Wortgrenzen oder mitten im Wort Automatisches Scrollen. JTextArea kann Text nicht automatisch scrollen. Wird das gewünscht, muß das Textfeld in einer Komponente von JScrollPane eingebettet werden. Bsp.248: import java.awt.*; import javax.swing.*; import javax.swing.border.*; public class BspScrolling extends JFrame { public BspScrolling() { super(); JScrollPane scroller = new JScrollPane(); getContentPane().add(scroller); StringBuffer bigBuffer = new StringBuffer(); for (int i = 0; i < 50; i++) { bigBuffer.append(Integer.toString(i)); bigBuffer.append(' '); } JLabel longLabel = new JLabel(bigBuffer.toString()); scroller.getViewport().add(longLabel); } public static void main(String [] args) { BspScrolling einFrame = new BspScrolling(); einFrame.setVisible(true); } } JEditorPane und JTextPane Die Klasse JEditorPane und deren Subklasse JTextPane erlauben verschiedene Typen von Text darzustellen. Die beiden Klassen unterstützen HTML und Rich Text Format (RTF). Eigene Implementierungen lassen sich hinzufügen (sog. Editor-Kits). Der Editor stellt Text dar, der ihm mit 247 Zur Umrechnung in Bildschirmpixel wird er mit der Breite des breitesten Buchstabens im aktuellen Font multipliziert. 248 pr56110 348 Programmieren in Java public void setContentType(String type) übergeben wird. Ohne eigene Erweiterung sind text/html (Standard), text/plain und text/rtf erlaubt249. Wird mit Standardkonstruktor gearbeitet, dann kann mit public void setPage(String url) bzw. public void setPage(URL page) ein URL-Objekt oder ein String eine Seite belegen. Auch public void setText(String t) erlaubt Setzen des spezifizierten Inhalts von Text ohne Textkomponente. Konstruktoren. public public public public JEditorPane() JEditorPane(String url) JEditorPane(String type,String text) JEditorPane(URL initialPage) Bsp.250:Anzeige einer HTML-Seite import java.io.*; import java.awt.*; import java.awt.event.*; import javax.swing.*; public class PR56510 extends Frame { String url = "file:///d:/dok/pgj/ws02/progr/pr56510/Skript48.html"; public PR56510() { try { JEditorPane htmlPane = new JEditorPane(url); htmlPane.setEditable(false); add(new JScrollPane(htmlPane)); } catch(IOException e) { System.out.println("Error" + url);}; addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0);} }); setSize(300,200); setVisible(true); } public static void main(String args[]) { PR56510 pr56510 = new PR56510(); } } 249 Soll nur Text ohne Formatierungen und ohne Attribute dargestellt werden, lässt sich gleich TextField verwenden. 250 pr56510 349 Programmieren in Java JTextComponent JTextArea JEditorPane << Konstruktoren >> public JTextArea() public JTextArea (Document doc, String text, int rows, int columns) public JTextArea(int rows,int columns) public JTextArea(String text) public JTextArea(String text, int rows, int columns) << Methoden >> public void append(String str) … public int getColumns() … public int getColumnCount() … public int getRows() … public void insert(String str, int pos) … public void replaceRange(String str,int start,int end) public void setColumns(int columns) public void setFont(Font f) … public void setRows(int rows) << Konstruktoren >> public JEditorPane() public JEditorPane(String type, String text) public JEditorPane(URL initialPage) … << Methoden >> public String getContentType() public EditorKit getEditorKit() … public URL getPage() public Dimension getPreferedSize() … public String getText() … public void setPage(String url) public void setPage(URL page) public void setText(String t) JTextPane << Konstruktoren >> public JTextPane() public JTextPane(StyledDocument doc) << Methoden >> public Style addStyle(String nm, Style parent) … public String getUIClassID() public void setStyleDocument( StyledDocument doc) Abb.: Die Klassen JTextArea und JTextPane 350 Programmieren in Java 5.6.5.2 Schaltflächen Die Klasse JButton und das Interface ButtonModel Buttons (Schaltflächen) sind rechteckige Knöpfe, die eine Aktion auslösen, wenn der Benutzer sie anklickt. Die visuelle Komponente wird durch die Klasse JButton repräsentiert, die das Modell und die Ansicht speichert. Gleichzeitig dient die Klasse als Vermittler beim Zugriff auf das Modell oder bei Anforderungen an die Ansicht. Das jeweilige Modell ist durch eine gleichnamige Schnittstelle implementiert. So definiert z.B. die Schnittstelle ButtonModel den Zustand der Schaltflächen-Klassen mit den Zugriffsmethoden: public String getActionCommand() // liefert den Namen des Aktionsfeldes public boolean isArmed() public boolean isEnabled() public boolean isPressed() public boolean isRollover() public boolean isSelected() Der Zugriff auf das Modell der Komponente muß mit Hilfe der Methode public ButtonModel getModel() der Klasse JButton angefordert werden bzw. kann mit public void setModel(ButtonModel newModel) der Klasse JButton angefordert werden. In Standardfällen ist der direkte Zugriff auf das Modell nicht erforderlich, da die Klasse JButton über Zugriffsmethoden auf die meisten Inhalte des Modells verfügt und diese kapselt. Alle Modelle mit "Schaltflächen"-Funktionalität (Schaltflächen, Optionsfelder, Kontrollkästchen) können dasselbe Modell verwenden. 351 Programmieren in Java JComponent AbstractButton protected ActionListener actionListener protected ChangeEvent changeEvent protected ChangeListener changeListener protected ItemListener itemListener public void addActionListener(ActionListener l) public void addChangeListener (ChangeListener l) public void addItemListener(ItemListener l) … public void doClick() … public int getHorizontalTextPosition() public int getHorizontalAlignment() public int getVerticalAlignment() … public Icon getIcon() … public Insets getMargin() public int getMnemonic() public ButtonModel getModel() … public Icon getSelectedIcon() public String getText() public ButtonUI getUI(); … public void setUI(ButtonUI ui) … public void updateUI() JButton << Konstruktoren >> public JButton() public JButton(String text) public JButton(Icon icon) public JButton(String text,Icon icon) << Methoden >> … public boolean isDefaultButton() … public void removeNotify() … Abb.: Die Klasse JButton 352 Programmieren in Java Die Klassen JCheckBox und JRadioButton In Swing werden runde und eckige Optionsfelder nicht mehr in einer Klasse zusammengefasst, sondern durch die Klassen JCheckBox und JRadioButton dargestellt. JCheckBox Die Klasse JCheckBox stellet einen Button dar, der vom Anwender wahlweise anoder ausgeschaltet werden kann. JCheckBox ist von der Klasse JToggleButton abgeleitet, die als Abstraktion von Buttons, die ihren Zustand ändern können, auch Basisklasse von JRadioButton ist. JCheckBox kann eine textuelle Beschriftung oder ein Icon oder beides enthalten. Konstruktoren. public JCheckBox(String text) public JCheckBox(String text,boolean selected) // Mit dem Parameter "selected" kann bereits bei der Instanzierung angegeben werden, // ob die Checkbox aktiviert oder deaktiviert sein soll. Methoden. public void getSelected(boolean b) public boolean isSelected() Wie ein JButton sendet eine JCheckBox bei jeder Bestätigung ein ActionEvent an registrierte Listener. Zudem wird bei Zustandsänderungen ein ItemEvent versendet, auf das ein ItemListener reagieren kann. JRadioButton Die Klasse JRadioButton stellt eine Button dar, der wahlweise an- und ausgeschaltet werden kann. Anders als bei der JCheckBox ist in einer Gruppe von Radiobuttons allerdings immer nur ein Button zur Zeit aktiviert, alle anderen sind deaktiviert. Konstruktoren. public JRadioButton(String text) public JRadioButton(String text, boolean selected) Methoden. public void add(AbstractButton b) public ButtonModel getSelection() public Enumeration getElements() // liefert alle Buttons der Gruppe 353 Programmieren in Java AbstractButton JToggleButton ButtonGroup protected Vector Buttons << Konstruktor >> public ButtonGroup() << Methoden >> public void add(AbstractButton b) public int getButtonCount() public Enumeration getElement() public ButtonModel getSelection() public boolean isSelected(ButtonModel m) public void remove(AbstractButton b) public void setSelected(ButtonModel m, boolean b) JCheckBox << Konstruktoren >> public JCheckBox() public JCheckBox(String text)) public JCheckBox(String text, boolean selected) << Methoden >> … JRadioButton << Konstruktoren >> public JRadioButton(String text) public JRadionButton(String text, boolean selected) << Methoden >> … Abb. JCheckBox und JRadioButton 354 Programmieren in Java 5.6.5.3 Listen und Comboboxen JList JList ist ein typisches Beispiel für das Model-View-Controller-Prinzip. Dabei stehen für die View (JList) mehrere Modelle zur Verfügung. Am wichtigsten ist das Datenmodell vom Typ ListModel, das Auswahlmodell (ListSelectionModel) und die Klassen für die Darstellung der Einträge (ListCellRenderer). Konstruktoren: JList verfügt über Konstruktoren, die eine Liste aus einem Vector oder Array251 erzeugen. Da die Daten nicht direkt in der Liste gespeichert werden, sondern in einem separaten Vektor, ist jederzeit einZugriff auf diese Daten möglich. Die Daten können also, nachdem sie in der Liste angezeigt wurden, nachträglich verändert werden. Das Problem ist dabei, dass die Komponenten von der Änderung nichts erfahren, da ein sehr triviales Modell, ohne jegliche Vorkehrungen oder Benachrichtigungen im Fall einer Änderung, durch den Konstruktor verwendet wurde. Darstellung und Auswahl der in der Liste enthaltenen Elemente: Die Anzahl der Elemente, die gleichzeitig angezeigt werden sollen, kann mit der Methode public void setVisibleRowCount(int setVisibleRowCount) eingestellt werden. Die Listenanzeige ist aber völlig von anderen Mechanismen wie z.B. Bildlauf getrennt, d.h. ein Listenfeld führt keinen automatischen Bildlauf durch. Zur Realisierung eines Bildlaufbereichs muß ein JScrollPane-Objekt mit der Liste als Parameter erzeugt werden und diese dann anschließend einem Panel hinzugefügt werden. Entfernen, Modifizieren von Einträgen in bzw. aus der Liste: Das Listenobjekt selbst verfügt über keine Methoden, die es erlauben, einen neuen Eintrag in die List einzufügen. Daten werden nicht in die Liste selbst gespeichert, sondern werden über eine Modell-Schnittstelle ListModel verwaltet. Das ListModel besitzt folgende Methode, mit deren Hilfe auf die Daten zugegriffen werden kann: public void getElementAt(int index) public int getSize() Es besteht die Möglichkeit, die Dateninhalte, statt sie in der Liste selbst zu speichern oder aus einem Vektor zu lesen, mit Hilfe einer Neudefinition von getElementAt() und getSize() z.B. online zu berechnen oder aus einer Datenbank anzufordern. Die zur Anzeige benötigten Inhalte werden dann dynamisch durch die Liste vom vorhandenen ListModel über den zugehörigen Index angefordert. Mit Hilfe der Methode public void addDataListener(ListDataListener l) kann ein Listener zum Modell hinzugefügt werden, der dafür Sorge tragen kann, dass eine Benachrichtigung der Listener-Komponente stattfindet, falls ein Element modifiziert wurde. Da die Liste nichts über die Elemente, die angezeigt werden sollen, weiß, muß für jedes Element Breite und Höhe neu berechnet werden. Mit Hilfe von public void setFixedCellHeight(int height) 251 Übergabe beim Erstellen des Elements 355 Programmieren in Java public void setFixedCellWidth(int width) der Klasse JList können konstante Abmaße der darzustellenden Elemente definiert werden, wodurch die Berechnung bei gleichartigen Elementen, was die überwiegende Mehrheit der Fälle sein dürfte, entfallen kann. Dynamische Speicherung der Objekte im Modell durch Einsatz von DefaultListModel: Das DefaulListModel verfügt über die Methoden public void addElement(Object element) public boolean removeElement(Object obj) , die zum Einfügen und Entfernen von Listenelementen eingesetzt werden können. Darstellung beliebiger, benutzerdefinierter Zeichnungen und Objekte: Hierzu muß ein Listenzellen-Renderer definiert werden. Dieser kann dafür Sorge tragen, dass entsprechende Elemente benutzerdefiniert innerhalb der Liste gezeichnet werden. Ein Listenzellen-Renderer kann eine beliebige Klasse sein, die eine Schnittstelle mit public Component getListCellRendererComponent(JList list,Object value, int index, boolean isSelected,boolean hasFocus) implementiert. Die Schnittstelle gibt ein Objekt vom Typ Component zurückgeben, das wiederum die Methoden paintComponent() und getPreferredSize() implementiert. paintComponent() sorgt für das Zeichnen der Komponente, getPreferredSize() ist für das Berechnen der Abmaße erforderlich, falls keine feste Größen definiert wurden. 356 Programmieren in Java JComponent JList ... << Konstruktoren >> public JList() public JList(ListModel dataModel) public JList(Object[] listData) public JList(Vector listData) << Methoden >> public void addListSelectionListener(ListSelectionListener l) public void addSelectionInterval(int anchor,int lead) public void clearSelection() protected ListSelectionModel createSelectionModel() … public AccessibleContext getAccessibleContext() public int getAnchorSelectionIndex() public Rectangle getCellBounds(int index0,int index1) public ListCellRenderer getCellRenderer() … public void getSelectionMode(int selectionMode) public int getSelectedIndex() public int [] getSelectedIndices() public Object getSelectedValue() public Object [] getSelectedValues() public boolean isSelectedIndex(int index) … public void ListSelectionListener[] getSelectionListeners() public int getMaxSelectionIndex() public int getMinSelectionIndex() public ListModel getModel() … public ListUI getUI() … public String paramString() … public void setListData(Object[] listData) public void setListData(Vector listData) public void setModel(ListModel model) … public void setSelectionModel(ListSelectionModel selectionModel) public void setUI(ListUI ui) public void setValueIsAdjusting(boolean b) public void setVisibleRowCount(int visibleRowCount) public void updateUI() Abb.: Die Klasse JList 357 Programmieren in Java << interface >> ListModel public void addListDataListener(ListDataListener l) public void setUI(ListUI ui) public void setValueIsAdjusting(boolean b) public void setVisibleRowCount(int visibleRowCount) public void updateUI() AbstractListModel { abstract } public void addListDataListener(ListDataListener l) … public void removeDataListener(ListDataListener l) DefaultListModell << Konstruktoren >> public DefaultListModel() << Methoden >> public void add(int index,Object element) public void addElement(Object obj) public void clear() public void contains(Object elem) public void copyInto(Object[] anArray) public Object elementAt(int index) public Enumeration elements() … public Object get(int index) public Object getElementAt(int index) … public void insertElementAt(Object obj,int index) public boolean isEmpty() public Object lastElement() … public Object remove(int index) public void removeAllElements() … public Object set(int index,Object element) public void setElementAt(Object obj,int index) public voiud setSize(int newSize) public int size() public Object[] toArray() public String toString() public void trimToSize() 358 Programmieren in Java << interface >> ListSelectionModel public void addListSelectionListener(ListSelectionListener x) public void addSelectionInterval(int index0,int index1) public void clearSelection() public int getAnchorSelectionIndex() public int getMaxSelectionIndex() public int getMinSelectionIndex() public int getSelectionMode() … public boolean isSelectionEmpty() … public void setSelectionMode() public void setValueIsAdjusting(boolean valueIsAdjusting) DefaultListSelectionModel … protected EventListenerList listenerList << Konstruktoren >> public DefaultListSelectionModel() << Methoden >> public void addListSelectionListener(ListSelectionListener l) public void addSelectionInterval(int index0,int index1) public void clearSelection() public Object clone() … public String toString() << interface >> ListCellRenderer public Componet getListCellRendererComponent(JList list,Object value, int index, boolean isSelected,boolean hasFocus) DefaultListCellRenderer << Konstruktor >> public DefaultListCellRenderer() << Methoden >> public void firePropertyChange(String propertyName,boolean oldValue,boolean newValue) … public void validate() 359 Programmieren in Java JComboBox Die JComboBox teilt dasselbe Darstellungsmodell (ListCellRenderer) mit der JList. Das Datenmodell ComboBoxModel ist von ListModel abgeleitet und erweitert es um Methoden zum Hinzufügen und Löschen von Einträgen. In einer JComboBox kann immer nur ein Eintrag ausgewählt sein. 5.6.5.4 Quasi analoge Komponenten In Swing gibt es drei Kontrollelemente zur Eingabe und Visualisierung von Zahlenwerten aus einem festgelegten Wertbereich: JScrollBar (ein einfacher Scrollbalken), JSlider (ein komplexer Schieberegler mit Skala), JProgressBar (eine Fortschrittsanzeige). Alle drei unterstützen das BoundedRangeModelInterface, das den Zugriff auf die Attribute value, minimum, maximum, extent bzw. valueAdjusting (ein Flag, das anzeigt, ob der Wert gerade eingestellt wird) und die Implementierung von ChangeEvents sicherstellt. << interface >> BoundedRangeModel public void addChangeListener(ChangeListener x) public int getExtent() public int getMaximum() public int getMinimum() public int getValue() public boolean getValueIsAdjusting() public void removeChangeListener(ChangeListener x) public void setExtent(int newExtent) public void setMaximum(int newMaximum) public void setRangeProperties(int value, int extent, int min, int max, boolean adjusting) public void setValue(int newValue) public void setValueIsAdjusting(boolean b) DefaultBoundedRangeModel Abb.: Interface BoundedRangeModel JScrollBar Attrribute. Die Klasse JScrollBar besitzt zusätzlich die Attribute unitIncrement für die Schrittweite, wenn auf die Pfeiltatsen gedrückt wird (Standard ist 1) und blockIncrement für einen Mausklickj auf den weißen Hintergrung des Scrollbalkens (Standard ist 10). Konstruktor. Der wichtigste Konstruktor ist public JScrollBar(int orientation, int value, int extent, int min, int // Ausrichtung des Schiebereglers // Anfangswert des Schiebers // Ausdehnung des Schiebereglers max) 360 Programmieren in Java Mit orientation wird die Ausrichtung (HORIZONTAL oder VERTICAL) des Schiebereglers festgelegt, min gibt den kleinsten, max den größten möglichen Wert an. Mit extent wird die Ausdehnung des Schiebers festgelegt. Sie muß mindestens eins, kann auch größer sein. value ist der Anfangswert des Schiebers. Er muß zwischen min und max – extent liegen. Methoden. public int getMaximum() public void setMaximum(int maximum) public int getVisibleAmount() // liefert die Ausdehnung des Schiebers public void setVisibleAmount(int extent) public int getValue() public void setValue(int value) public int getUnitIncrement() // gibt an, um welchen Betrag der Wert des Schiebereglers sich verändert, wenn der Anwender // einen Pfeilbutton betätigt public void setUnitIncrement(int unitIncrement) public int getBlockIncremnent() // ermittelt den Betrag der Änderung, wenn zwischen Schieber und Pfeiltatsen geklickt wird. public void setBlockIncrement(int blockIncrement) Ereignisbehandlung. Aus Kompatibilitätsgründen zur AWT-Scrollbar implementiert die Klasse das Interface Adjustable und unterstützt den AdjustmentListener. Wird ein Wert einer JScrollBar verändert, sendet sie ein AdjustmentEvent an registrierte Listener. Diese müssen das Interface AdjustmentListener implementieren und werden durch addjustmentListener() registiert. Mit getValueAdjusting() kann festgestellt werden, auf welche Weise der Wert verändert wird. Rückgabe von true erfolgt dann, wenn der Anwender den Schieber betätigt. false wird zurückgegeben, wenn die Änderung durch einen Mausklick auf einen Button oder auf die Fläche zwischen Button und Schieber ausgelöst wird. JSlider Wichtiger (konzeptioneller) Unterschied zum JScrollbar: 1. Ein JSlider kann eine Anzeigeskala mit grober und fiener Beschriftung haben. 2. Ein JSlider kennt keine unterschiedlichen Schiebergrößen. Ihre Ausdehung ist immer 1. Die Klasse JSlider ist ein Schieberegler mit optionaler Skala. Die Skalenstriche nennen sich Ticks und existieren in zwei Längen: majorTicks, minorTicks. Wichtigster Konstruktor. public JSlider(int orientation, int min, int max, int value) Methoden. public int getMaximum() public void setMaximum(int maximum) public int getMinimum() public void setMinimum(int minimum) 361 Programmieren in Java public int getValue() public void setValue(int value) public int getMajorTickSpacing() // Vorgabe des Abstands der großen Markierung public void setMajorTockSpacing(int n) public int getMinorTickSpacing() public void setMinorTickSpacing(int n) // Vorgabe des Abstands der kleinen Markierung public void setPaintTicks(boolen b) // Anzeige der Anzeigeskala durch Übergabe von true public void setPaintLabels(boolean b) // Anzeige der Beschriftung durch Übergabe von true Ereignisbehandlung. Ein JSlider sendet ein ChangeEvent, wenn sein Wert verändert wird. Zur Reaktion auf dieses Ereignis muß das Interface ChangeListener implementiert sein, und das zu implementierende Objekt durch den Aufruf addChangeListener() registriert sein. Mit getValueIsAdjusting() kann festgestellt werden, ob die Änderung Bestandteil einer Kette von Änderungen sit oder ob sie einzeln aufgetreten ist. Bsp.252: import java.awt.*; import java.awt.event.*; import javax.swing.*; public class JSliders extends JFrame { public JSliders() { super("Using JSlider"); // WindowUtilities.setNativeLookAndFeel(); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); Container content = getContentPane(); content.setBackground(Color.white); JSlider slider1 = new JSlider(); slider1.setBorder(BorderFactory.createTitledBorder ("JSlider without Tick Marks")); content.add(slider1, BorderLayout.NORTH); JSlider slider2 = new JSlider(); slider2.setBorder(BorderFactory.createTitledBorder ("JSlider with Tick Marks")); slider2.setMajorTickSpacing(20); slider2.setMinorTickSpacing(5); slider2.setPaintTicks(true); content.add(slider2, BorderLayout.CENTER); JSlider slider3 = new JSlider(); slider3.setBorder(BorderFactory.createTitledBorder ("JSlider with Tick Marks & Labels")); slider3.setMajorTickSpacing(20); slider3.setMinorTickSpacing(5); slider3.setPaintTicks(true); slider3.setPaintLabels(true); content.add(slider3, BorderLayout.SOUTH); pack(); 252 vgl. pr56370 362 Programmieren in Java setVisible(true); } public static void main(String[] args) { new JSliders(); } } JProgressBar Die Klasse JProgressBar zeigt einen im Programm berechneten Fortschritt eines Vorgangs an. Im Fortschritts-Balken kann ein Text angezeigt werden Konstruktoren. public JProgressBar(int orient) public JProgressBar(int min, int max) public JProgressBar(int orient,int min,int max) Methoden. public void setStringPainted(boolean b) // Standardmäßig wird die Fortschrittsanzeige ohne Beschriftung dargestellt. Durch Aufruf von // setStringPainted() mit Übergabe true kann ein prozentualer Fortschrittswert angezeigt // werden. public void setValue(int n) // Veränderung der grafischen darstellung des Fortschritts public int getValue() // Abfrage des Werts des grafischen Fortschritts Bsp.253: import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.event.*; public class JProgressBarDemo extends JFrame { protected int m_min = 0; protected int m_max = 100; protected int m_counter = 0; protected JProgressBar jpb; public JProgressBarDemo() { 253 pr56370 363 Programmieren in Java super("JProgressBar Demo"); setSize(300,50); UIManager.put("ProgressBar.selectionBackground", Color.black); UIManager.put("ProgressBar.selectionForeground", Color.white); UIManager.put("ProgressBar.foreground", new Color(8,32,128)); jpb = new JProgressBar(); jpb.setMinimum(m_min); jpb.setMaximum(m_max); jpb.setStringPainted(true); JButton start = new JButton("Start"); start.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent e) { Thread runner = new Thread() { public void run() { m_counter = m_min; while (m_counter <= m_max) { Runnable runme = new Runnable() { public void run() { jpb.setValue(m_counter); } }; SwingUtilities.invokeLater(runme); m_counter++; try { Thread.sleep(100); } catch (Exception ex) {} } } }; runner.start(); } }); getContentPane().add(jpb, BorderLayout.CENTER); getContentPane().add(start, BorderLayout.WEST); WindowListener wndCloser = new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }; addWindowListener(wndCloser); setVisible(true); } public static void main(String[] args) { new JProgressBarDemo(); } } 364 Programmieren in Java JComponente JScrollBar JProgressBar protected int blockIncrement protected BoundRangeModel model protected int orientation protected int unitIncrement protected ChangeEvent changeEvent protected ChangeListener changeListener protected BoundRangeModel model protected int orientation protected boolean paintBorder protected boolean paintString protected String progressString << Konstruktor >> public JScrollBar() public JScrollBar(int orientation) public JScrollBar(int orientation, int value, int extent, int min, int max) << Methoden >> public void AddAdjustmentListener( AdjustmentListener l) …. public int getBlockIncrement() public int getMaximum() public Dimension getMaximumSize() … << Konstruktor >> public JProgressBar() … << Methoden >> public void addChangeListener( ChangeListener l) protected ChangeListener createChangeListener … JSlider public ChangeEvent changeEvent public ChangeListener changeListener << Konstruktor >> public JSlider() << Methoden >> … Abb.: Die Klassen JProgressBar, JScrollBar und JSlider 365 Programmieren in Java 5.6.5.5 Tabellen Das Modell für JTable muß das Interface TableModel implementieren. Die Klasse AbstractTableModel kann als Ableitungsbasis für die konkrete Datenklasse verwendet werden. Die Klasse DefaultTableModel enthält eine Implementierung aus ineinander verschachtelten Vector-Objekten. Konstruktoren. public JTable(Object[][] rowData,Object ColumnNanes) Im zweidimensionalen Array enthält die erste Dimension die Zeilen, die zweite die Spalten. Zur Darstellung in der Tabelle werden die Arrayelemente mit toString() in Strings umgewandelt. Das 2. Array enthält ein Array mit Strings, die als Spaltenköpfe angezeigt werden. public JTable(Vector rowData,Vector columnNames) Daten und Spalten werden in einem Vector übergeben. Der Datenvektor rowData muß für jede Zeile einen Subvektor mit den Datenelementen der Zeile enthalten. public JTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) Das TableModel stellt Daten zur Verfügung. Das TableColumnModel definiert die Spalten. Das ListSelectionModel ist für die Auswahl von Tabellenelelementen zuständig. Bsp.: Erzeugen einer einfachen Tabelle import java.awt.*; import java.awt.event.*; import javax.swing.*; public class SimpleTable extends JFrame { public static final String[][] DATEN = { {"Uebungsblatt 1", "Binaere Baumknoten in Java"}, {"Uebungsblatt 1a", "Baumansichten der Java Foundation Classes"}, {"Uebungsblatt 2", "Verarbeitung binaerer Suchbaumknoten"}, {"Uebungsblatt 3", "Die generische Klasse BinaererSuchbaum in Java"}, {"Uebungsblatt 4", "Die generische Klasse AvlBaum in Java"}, {"Uebungsblatt 5", "Perfekt ausgeglichene Baeume"}, {"Uebungsblatt 6", "Rot-Schwarz-Baeume"}, {"Uebungsblatt 7", "AA-Baeume"}, {"Uebungsblatt 8", "Splay-Baeume"}, {"Uebungsblatt 9", "Bayer-Baeume"}, {"Uebungsplatt 10", "Auf Platte / Diskette gespeicherte Bayer-Bäume"}, {"Uebungsblatt 11", "Hash-Tabellen"}, {"Uebungsblatt 12", "Heap-Algorithmen"}, {"Uebungsblatt 13", "Vorrangwarteschlangen"}, {"Uebungsblatt 14", "Binomial Queue, Fibonacci Heap"}, {"Uebungsblatt 15", "Backtracking-Algorithmen"}, {"Uebungsblatt 16", "Ansichten der Java Foundation Classes JList und JTable"}, {"Uebungsblatt 17", "Skip Lists"} }; public static final String[] COLHEADS = { "Ausgabe", "Titelthema" }; // Konstruktor public SimpleTable() { super("Uebersicht zu den Uebungen in AD"); JTable table = new JTable(DATEN, COLHEADS); Container cp = getContentPane(); cp.add(new JLabel("Uebungen zu AD im WS 02"), "North"); cp.add(new JScrollPane(table), "Center"); } 366 Programmieren in Java public static void main(String[] args) { SimpleTable frame = new SimpleTable(); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } }); frame.setLocation(100, 100); frame.setSize(300, 200); frame.setVisible(true); } } Das Programm erzeugt die folgende Tabelle: Abb.: Tabelle, die von SimpleTable erzeugt wird Konfiguration der Tabelle. Mit public void setRowHeight(int rowHeight) wird die Gesamthöhe einer Zeile festgelegt. Alle Zeilen sind dabei gleich hoch. Mit public void setRowMargin(int rowMartgin) wird der am oberen Rand und unteren Rand jeder Zeile frei bleibende Platz bestimmt. Der für den Inhalt der Zelle verfügbare Platz ergibt sich aus der Zellenhöhe minus oberem und unterem Rand. Durch Aufruf von public void setIntercellSpacing(Dimension newSpacing) kann (zusammen mit dem vertikalen) auch der horizontale Rand der Zellenelemente festgelegt werden. Standardmäßig werden die Zellen einer JTable mit senkrechten und waagrechten Begrenzungslinien voneinander getrennt. Mit public void setShowGrid(boolean b) können beide Linienarten zugleich an- oder ausgeschaltet werden. Sollen die horizontalen oder vertikalen Linien separat aktiviert oder deaktiviert werden, können public void setShowHorizontalLines(boolean b) public void setShowVerticalLines(boolean b) verwendet werden. Das Verändern der Zellen ist in begrenzter Weise mit folgenden Methoden möglich: public void setGridColor(Color newColor) // verändert die Farbe, mit der Gitterlinien angezeigt werden public void setSelectionForeground(Color selectionForeground) public void setSelectionBackground(Color selectionBackground) Das Verhalten der Tabelle, nachdem die Breite einer einzelnen Spalte verändert wurde, bestimmt public void setAutoResize(int mode) 367 Programmieren in Java Der dadurch frei werdende oder zusätzlich benötigte Platz kann auf unterschiedliche Weise den übrigen Spalten zugeordnet werden. Der Parameter mode kann folgende Werte annehmen: Modus AUTO_RESIZE_OFF Bedeutung keine automatische Größenanpassung der Spalten. Wurde die Tabelle in JScrollPane verpackt, bekommt sie nötigenfalls einen horizontale Schieberegler. AUTO_RESIZE_LAST_COLUMN Die letzte Spalte wird zum Größenausgleich verwendet. Dadurch reduziert sich der Platz für die letzte Spalte, wenn eine andere Spalte vergrößert wird, und er erhöht sich, wenn sie verkleinert wird. AUTO_RESIZE_NEXT_COLUMN Die recht neben der modifizierten Spalte liegende Spalte wird zum Größenausgleich verwendet. AUTO_RESIZE_SUBSEQUENT_COLUMNS Die Größenänderung wird gleichmäßig auf alle nachfolgenden Spalten verteilt. AUTO_RESIZE_ALL_COLUMNS Die Größenänderung wird auf alle Spalten der Tabelle verteilt. Selektieren von Elementen Selektionsmodi: Die folgenden Methoden der Klasse JTable regeln die Selektion: public void setRowSelectionAllowed(boolean flag) Zeilenweise Selektion nach Aufruf dieser Methode mit true als Argument. public void setColumnSelectionAllowed(boolean flag) Spaltenweise Selektion nach Aufruf dieser Methode mit true als Argument. Durch Übergabe von false können Selektionsmarken ausgeschaltet werden und nur noch einzelne Zellen selektiert werden. Standardmäßig kann zeilen-, aber nicht spaltenweise selektiert werden. public void setSelectionMode(int selectionMode) legt fest, ob ein einzelnes Element, ein zusammenhängender Bereich oder mehrere Bereiche selektiert werden können. Hier ist eine der Konstanten SINGLE_SELECTION, SINGLE_INTERVAL_SELECTION, oder MULTIPLE_INTERNAL_SELECTION der Klasse ListSelectionModel zu übergeben. public void setSelectionEnabled(boolean flag) Der Aufruf dieser Methode mit true als Argument bewirkt: Zeile und Spalten können gleichzeitig markiert und so zusammenhängende rechteckige Bereiche von Zellen (einschl. einer einzelnen) selektiert werden. Abfragen der Selektion. Die folgenden Methoden der Klasse JTable bestimmen, welche Elemente selektiert wurden: public int getSelectionRow() public int getSelectionColumn() Die beiden Methoden liefern die selektierte Zeile bzw. Spalte, wenn der Selektionsmodus SINGLE_SELECTION ist. Die 1. Zeile und Spalte haben jeweils den Index 0. Erlaubt der aktuelle Selektionsmodus das Selektieren ganzer Zeilen und Spalten, dann impliziert das Ergebnis: Alle Elemente dieser Zeile bzw. Spalte sind selektiert. Falls keine Elemente selektiert sind, wird –1 zurückgegeben. public int [] getSelectedRows() public int [] getSelectedCloumns() Falls einer der Mehrfachselektionsmodi aktiviert ist, können über diese beiden Methoden Arrays mit allen selektierten Zeilen und Spalten beschafft werden. Falls keine Elemente selektiert sind, wird ein leeres Array zurückgegeben. Verändern der Selektion. JTable stellt Methoden bereit mit denen die Selektion programmgesteuert verändert werden kann: public void selectAll() public void clearSelection() public void setRowSelectionIntervall(int index0, int index1) 368 Programmieren in Java Markieren eines zusammenhängenden Bereichs von Zeilen public void addRowSelectionIntervall(int index0, int index1) Hinzufügen eines zusammenhängenden Bereichs zur aktuellen Selektion public void removeRowSelectionIntervall(int index0, int index1) Entferenen eines zusammenhängenden Bereichs aus der aktuellen Selektion public void setColumnSelectionIntervall(int index0, int index1) public void addColumnSelectionIntervall(int index0, int index1) public void removeColumnSelectionIntervall(int index0, int index1) Zugriff auf den Inhalt der Tabelle Daten in der Tabelle. Unabhängig von der aktuellen Selektion kann auf den Inhalt der Tabelle zugegriffen werden. pulic int getRowCount() public int getColumnCount() liefern die aktuelle Zeilen- bzw. Spaltenanzahl der Tabelle. Editieren von Tabellenelementen. Nach einem Doppelklick auf eine Zelle kann der Anwender die in diesem Element enthaltenen Daten verändern. JTable besitzt eigene Methoden, mit denen abgefragt werden kann, ob und in welcher Zelle die Tabelle gerade editiert wird. Das Tabellenmodell. Ein eigenes Tabellenmodell muß das Interface TableModel implementieren und bei der Instanzierung an den Konstruktor der JTable übergeben. Wahlweise kann nach Instanzierung auf das Modell mit folgenden Methoden der Klasse JTable zugegriffen werden: public void setModel(TableModel newModel) public TableModel getModel() Das Interface TableModel definiert folgende Methoden: public public public public public public public public void addTableModelListener(TableModelListener l) Class getColumnClass(int columnIndex) int getColumnCount() int getRowCount() Object getValueAt(int rowIndex,int columnIndex) boolean isCellEditable(int rowIndex,int columnIndex) void removeTableModelLister(TableModelListener l) void setValueAt(Object aValue,int rowIndex,int columnIndex) Das Spaltenmodell. Das Spaltenmodell einer JTable muß das Interface TableColumnModel aus dem Paket javax.swing.table implementieren. Es wird bei der Instanzierung einer JTable an den Konstruktor übergeben. Ohne weitere Ableitung kann die Standard-Implementierung DefaultTableColumnModel verwendet werden. Sie stellt Methoden zum Hinzufügen bzw. Entfernen von Spaltenobjekten (Typ TableColumn) bereit. public void addColumn(TableColumn aColumn) public void removeColumn(TableColumn aColumn) Bsp.: import import import import java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.table.*; public class SimpleTable2 extends JFrame { 369 Programmieren in Java public static final String[][] DATEN = { {"Uebungsblatt 1", "Binaere Baumknoten in Java"}, {"Uebungsblatt 1a", "Baumansichten der Java Foundation Classes"}, {"Uebungsblatt 2", "Verarbeitung binaerer Suchbaumknoten"}, {"Uebungsblatt 3", "Die generische Klasse BinaererSuchbaum in Java"}, {"Uebungsblatt 4", "Die generische Klasse AvlBaum in Java"}, {"Uebungsblatt 5", "Perfekt ausgeglichene Baeume"}, {"Uebungsblatt 6", "Rot-Schwarz-Baeume"}, {"Uebungsblatt 7", "AA-Baeume"}, {"Uebungsblatt 8", "Splay-Baeume"}, {"Uebungsblatt 9", "Bayer-Baeume"}, {"Uebungsplatt 10", "Auf Platte / Diskette gespeicherte Bayer-Bäume"}, {"Uebungsblatt 11", "Hash-Tabellen"}, {"Uebungsblatt 12", "Heap-Algorithmen"}, {"Uebungsblatt 13", "Vorrangwarteschlangen"}, {"Uebungsblatt 14", "Binomial Queue, Fibonacci Heap"}, {"Uebungsblatt 15", "Backtracking-Algorithmen"}, {"Uebungsblatt 16", "Ansichten der Java Foundation Classes JList und JTable"}, {"Uebungsblatt 17", "Skip Lists"} }; public static final String[] COLHEADS = { "Ausgabe", "Titelthema" }; // Konstruktor public SimpleTable2() { super("Uebersicht zu den Uebungen in AD"); //Spaltenmodell erzeugen DefaultTableColumnModel cm = new DefaultTableColumnModel(); for (int i = 0; i < COLHEADS.length; i++) { TableColumn col = new TableColumn(i, i == 2 ? 150 : 60); col.setHeaderValue(COLHEADS[i]); cm.addColumn(col); } //Tabellenmodell erzeugen TableModel tm = new AbstractTableModel() { public int getRowCount() { return DATEN.length; } public int getColumnCount() { return DATEN[0].length; } public Object getValueAt(int row, int column) { return DATEN[row][column]; } }; JTable table = new JTable(tm, cm); Container cp = getContentPane(); cp.add(new JLabel("Uebungen zu AD im WS 02"), "North"); cp.add(new JScrollPane(table), "Center"); } public static void main(String[] args) { SimpleTable2 frame = new SimpleTable2(); frame.addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) 370 Programmieren in Java { System.exit(0); } }); frame.setLocation(100, 100); frame.setSize(300, 200); frame.setVisible(true); } } Rendering254 der Zellen public TableCellRenderer getDefaultRenderer(Class columnClass) public void setDefaultCellRenderer(Class columnClass, TableCellRenderer renderer) Sofern nicht in den Tabellenspalten ein eigener Renderer bestimmt ist, ist der Standard-Renderer für die Darstellung aller Tabellenelemente zuständig. Es muß das Interface TableCellRenderer implementieren, das nur eine einzige Methode enthält. public Component getTableCellRendererComponent( JTable table,Object value, boolean isSelected, boolean hasFocus, int row, int column) Diese Methode arbeitet als Factory-Methode und wird nur dann aufgerufen, wenn zur Darstellung einer Zelle ein Renderer benötigt wird. Standardmäßig wird als Renderer eine Instanz der Klasse DefaultTableCellRenderer verwendet. Sie ist eine Ableitung von JLabel mit deren Hilfe Farbe, Hintergrund und das Look-and-Feel der Tabelle an die Erfordernisse der jeweiligen Zelle angepasst werden. Reaktion auf Ereignisse. JTable generiert eine Vielzahl vom Ereignissen, um registrierte Listener über Änderungen des Tabellenzustands zu informieren. Will ein Objekt beispielsweise darüber informiert werden, daß sich die Selektion geändert hat, muß es zwei ListSelectionListener registrieren. Einer davon wird auf dem Selektionsmodell registriert, das mit getSelectionModel() ermittelt werden kann. Da dieser nur Informationen über Änderungen an der Zeilenselektion versendet, muß ein zweiter Listener auf dem Modell für die Spaltenselektion registriert werden. Es kann durch Aufruf von getColumnModel() beschafft werden, und auf sein Selektionsmodell kann ebenfalls mit getSelectionModel() zugegriffen werden. Bei jeder Änderung der Selektion wird nun valueChanged() aufgerufen und kann mit Hilfe der oben erläuterten Methoden herausfinden, welche Zeilen und Spalten selektiert sind. Die Tabelle informiert auch über Änderungen ihrer Daten. Dazu muß auf dem Tabellenmodell (das mit getModel() beschafft wird) durch Aufruf von addTableModelListener() ein TableModelListener registriert werden. Bei jeder Änderung des Modells wird dann dessen Methode tableChanged() aufgerufen. Schließlich können alle in den Superklassen von JTable definierten Listener registriert werden. 254 Vorgang, der dafür sorgt, dass die Zellen auf dem Bildschirm dargestellt werden. Die dafür verantwortlichen Komponenten werden als Renderer bezeichnet. 371 Programmieren in Java JComponent JTable ... protected TableCellEditor cellEditor protected boolean cellSelectionEnabled protected TableCloumnModel columnModel protected TableModel dataModel … protected Color selectionBackground potected Color selectionForeground protected ListSelectionModel selectionModel protected boolean showHorizontalLines protected boolean showVerticalLines << Konstruktoren >> public JTable() public JTable(int numRows, int numColumns) public JTable(Object[][] neuDaten,Object [] columnNames) public JTable(TableModel dm) public JTable(TableModel dm, TableColumnModel cm) public JTable(TableModel dm, TableColumnModel cm, ListSelectionModel sm) public JTable(Vector rowData, Vector columnNames) << Methoden >> public void addColumn(TableColumn aColumn) … public void addNotify() … public void clearSelection() public void columnAdded(TableColumnModelEvent e) public int columnAtPoint(Point point) … public void doLayout() public boolean editCellAt(int row, int column) … public TableCellEditor getCellEditor() public TableCellEditor getCellEditor(int row,int column) public Rectangle getCellRect(int row,int column,boolean includeSpacing) public TableCellRenderer getCellRenderer(int row,int column) public TableColumn getColumnClass(int column) public int getColumnCount() public TableColumnModel getColumnModel() public String columnName(int column) public boolean getSelectionEnabled() public Class getColumnClass(int column) public int getColumnCount() public TableColumnModel getColumnModel() public String getColumnName(int column) public boolean getColumnSelectionAllowed() … 372 Programmieren in Java public TableModel getModel() public int getRowCount() public int getRowHeight() public int getRowHeight(int row) … public ListSelectionModel getSelectionModel() public boolean getShowHorizontalLines() public boolean getShowVerticalLines() … public JTableHeader getTableHeader() … public TableUI getUI() … public Object getValueAt(int row,int column) … public boolean isCellEditable(int row,int column) … public String paramString() … public void selectAll() … public void setModel(TableModel dataModel) … public void setRowHeight(int rowHeight) public void setRowHeight(int row,int rowHeight) … public void setUI(TableUI ui) … public void tableChanged(TableMoveEvent e) … public void updateUI() public void valueChanged(ListSelectionEvent e) Abb.: Die Klasse JTable 373 Programmieren in Java << interface >> TableModel public void addTableModelListener(TableModelListener l) public Class getColumnClass(int columnIndex) public int getColumnCount() public int getRowCount() public Object getValueAt(int rowIndex,int columnIndex) public boolean isCellEditable(int rowIndex,int columnIndex) public void removeTableModelLister(TableModelListener l) public void setValueAt(Object aValue,int rowIndex,int columnIndex) AbstractTableModel { abstract } … public int findColumn(String columnName) public void fireTableCellUpdated(int row,int column) public void fireTableChanged(TableModelEvent e) public void fireTableChanged() … public EventListener[] getListeners(Class listenerType) public TableModelListener[] getTableModelListener() … DefaultTableModell proteced Vector columnIdentifiers protected Vector dataVector << Konstruktoren >> public DefaultTableModel() public DefaultTableModel(int rowCount,int ColumnCount) public DefaultTableModel(Object[] columnNames,int rowCount) public DefaultTableModel(Vector columnNames,int rowCount) public DefaultTableModel(Vector data,Vector columnNames) << Methoden >> public void addColumn(Object columnName) public void addColumn(Object columnName,Object[] columnData) public void addColumn(Object columnName,Vector columnData) public void addRow(Object[] rowData) public void addRow(Vector data) … public int getColumnCount() public String getColumnName() public Vector getDataVector() public int getRowCount() public Object getValueAt(int row,int column) public void insertRow(int row,Object[] rowData) public void insertRow(int row,Vector rowData) … public void setDateVector(Object[][] dataVector,Object[] columnIdentifiers) public setDataVector(Vector dataVector,Vector columnIdentifiers) 374 Programmieren in Java public void setNumRows(int rowCount) public void setRowCount(int rowCount) public void setValueAt(Object aValue,int row,int column) << interface >> TableCellEditor public Component getTableCellEditorComponent(JTable table,Object value, boolean isSelected,int row,int column) DefaultCellEditor … protected JComponent editorComponent << Konstruktoren >> public DefaultCellEditor(JCheckBox checkBox) public DefaultCellEditor(JComboBox checkBox) public DefaultCellEditor(JTextField textField) << Methoden >> … public Object getCellEditorValue() … public Component getComponent() … public boolean isCellEditable(EventObject anEvent) … << interface >> TableCellRenderer public Componet getTableCellRendererComponent(JTable table,Object value, boolean isSelected, boolean hasFocus,int row,int column) DefaultTableCellRenderer << Konstruktor >> public DefaultTableCellRenderer() << Methoden >> public void firePropertyChange(String propertyName,boolean oldValue,boolean newValue) … public void validate() 375 Programmieren in Java 5.6.5.6 Hierarchische Strukturen Die Klasse JTree dient zur Darstellung, Navigation und Berabeitung baumartiger, hierarchischer Strukturen. Die Baumdarstellung basiert auf einem hierarchischen Datenmodell. Jede Modellklasse zur Baumdarstellung muß sich auf das Interface TreeModel beziehen. JTree besitzt dafür Konstruktoren bzw. die Zugriffsmethoden getModel() und setModel(). Die einzelnen Baumknoten müssen die Interfaces TreeNode oder MutableTreeNode implementieren. Die Klasse DefaulMutableTreeNode enthält eine universelle Implementierung (inklusive Navigationsmethoden) für Baumknoten Erzeugen eines Baums. Liegt ein geeignetes Datenmodell vor, ist das Instanzieren eines JTree einfach. Die beiden wichtigsten Konstruktoren der Klasse JTree sind: public JTree(TreeModel neuesModell) // erwartet wird ein vordefiniertes TreeModel zur Darstellung der Baumelemente. Ein TreeModel // kapselt alle relevanten Informationen über die Baumstruktur. Es liefert auf Anfrage die Wurzel // des Baums, stellt Informationen über einen bestimmten Knoten bereit oder liefert dessen // Nachfolgeknoten public JTree(TreeNode wurzel) // Die Wurzel wird automatisch in ein geeignetes TreeModel eingebettet. Der JTree erfragt die zur // Darstellung der Navigation erforderlichen Daten immer beim TreeModel. Das über wurzel // Baumwurzel) instanzierte DefaultTreeModel kann diese Informationen aus den Knoten und den // darin gespeicherten Verweisen auf ihre Nachfolgeknoten entnehmen. Zugriffsmethoden auf das Datenmodell. public TreeModel getModel() public void setModel(TreeModel neuesModell) Anzeige bzw. Unterdrücken der Wurzel bei der Baumdarstellung. Eine wichtige Konfigurationsoption regelt, ob die Wurzel des Baums bei seiner Darstellung angezeigt oder unterdrückt werden soll. Auf sie kann mit den Methoden public void setRootVisible(boolean rootVisible) public boolean isRootVisible() zugegriffen werden. Zur Beschriftung eines Knotens bei der visuellen Darstellung verwendet ein JTree die Methode toString(). Abfragen zur Selektion von Baumknoten. JTree stellt eine Reihe von Methoden für Abfragen bereit, ob und welche Knoten selektiert sind, z.B.: public TreePath getSelectionPath() // ermittelt das selektierte Element. Bei aktivierter Mehrfachselektion liefert die Methode das erste aller // selektierten Elemente. Falls kein Knoten selektiert ist, wird null zurückgeliefert. public TreePath[] getSelectionPaths() // zurückgegeben wird ein Array mit allen selektierten Knoten public TreePath getLeadSelectionPath() // liefert das markierte Element Die angegebenen Methoden liefern Objekte der Klasse TreePath. Diese Klasse beschreibt einen Knoten im Baum über den Pfad, der von der Wurzel aus beschritten werden muß, um zu den Knoten zu gelangen. Mit public Object getLastSelectedPathComponent() 376 Programmieren in Java der Klasse TreePath kann das letzte Element dieses Pfads bestimmt werden. Mit public Object[] getPath() der Klasse TreePath kann der vollständige Pfad ermittelt werden. An erster Stelle liegt die Wurzel des Baums, an letzter Stelle das selektierte Element. Solle ermittelt werden, ob und welche Elemente im Baum selektiert sind, können die Methoden public boolean isSelectionEmpty() public boolean isPathSelected(TreePath pfad) aufgerufen werden. Verändern der Selektion. Die Selektion kann programmgesteuert verändert werden: public void clearSelection() // Löschen der Selektion public void addSelectionPath(TreePath path) // Erweitern der Selektion um ein einzelnes Element public void addSelectionPaths(TreePath [] pfade) // Erweitern der Selektion um eine Menge Knoten public void setSelectionPath(TreePath path) // Selektion der als Argument übergebenen Knoten) public void setSelectionPaths(TreePath[] paths) // Selektion der als Argument übergebenen Knoten Zur unmittelbaren Reaktion auf Änderungen der Selektion durch den Anwender kann ein TreeSelectionListener instanziert werden (Registrierung mit addTreeSelectionListener() beim JTree). Bei jeder Selektionsänderung wird public void valueChanged(TreeSelectionEvent event) aufgerufen. "event" stellt public TreePath getOldLeadSelectionPath() public TreePath getNewLeadSelectionPath() zur Verfügung, um auf den vorherigen und aktuellen Selektionspfad zuzugreifen. Öffnen und Schließen der Knoten. Der Anwender kann die Knoten mit Maus- oder Tastaturkommandos öffnen oder schließen. Dadurch werden Nachfolgeknoten sichtbar oder versteckt. public boolean isExpanded(TreePath pfad) // liefert true, wenn der Knoten geöffnet ist public boolean isCollapsed() // liefert true, wenn der Knoten geschlossen ist public boolean hasBeenExpanded(TreePath path) // gibt an, ob der Knoten überhaupt schon einmal geöffnet wurde public boolean isVisible(TreePath pfad) // liefert true, wenn der Knoten sichtbar ist, d.h. all seine Elternknoten geöffnet sind public void makeVisible(TreePath pfad) // Sichtbar Machen eines Knoten public void expandPath(TreePath pfad) // Öffnen eines Knoten public void collapsePath(TreePath pfad) // Schließen eines Knoten Verändern der Baumstruktur. Es können neue Knoten eingefügt, bestehende entfernt oder vorhandene modifiziert werden. 377 Programmieren in Java Interfaces zur Modellierung und Sicht auf einen Baum: Drei Interfaces erlauben Modellierung und Sicht auf einen Baum: TreeModel, TreeSelectionModel, TreeCellRenderer. Ein viertes Interface TreeNode beschreibt, was in jedem Knoten des Baums dargestellt wird. TreeModel Dieses Interface beschreibt das einem JTree zugeordnete Datenmodell. Das TreeModel-Interface spezifiziert, wie ein Baum (über eine Datenstruktur) abgebildet wird. public public public public public Object getChild(Object parent, int index) int getChildCount(Object parent) int getIndexOfChild(Object parent,Object child) Object getRoot() Object isLeaf(Object node) Drei weitere Methoden public void addTreeModelListener(TreeModelListener l) public void removeTreeModelListener(TreeMoldelListener l) public void valueForPathChanged(TreePath pfad, Object neuerWert) behandeln Hinzufügen, Entfernen und die Kentnisnahme von Event-Listeners. Diese Listeners bemerken Änderungen im TreeModel nach Empfang von TreeModelEventNachrichten. Ein Objekt, das diese Methoden definiert, kann Modell für einen JTree sein. Die DefaultTreeModel-Klasse ist eine einfache Implementierung des TreeModel, das explizit TreeNode- und MutableTreeNode-Objekte benutzt. TreeNode JTree-Objekte werden aus TreeNode-Objekten gebildet (einfache Repräsentationen eines Baumknoten). Die wichtigsten Methoden von TreeNode sind: public int getChildCount() // ermittelt die Anzahl der Nachfolgeknoten. Sie werden von 0 an durchnummeriert. public TreeNode getChildAt(int childIndex) // liefert einen beliebigen Nachfolgeknoten public TreeNode getParent() // Ein Knoten kennt seinen Vaterknoten, der mit getParent() ermittelt werden kann public boolean isLeaf() // Abfrage, ob ein Knoten ein Blatt ist oder weitere Nachfolgeknoten enthält Die Klasse DefaultMutableTreeNode definiert Methoden zum Anschauen und Manipulieren von Baumknoten. Diese Klasse implementiert das Interface MutableTreeNode (Extension des Interface TreeNode). Anwendungsbezogene Informationen werden bereits dem Konstruktor übergeben: public DefaultMutableTreeNode(Object benutzerObjekt) 378 Programmieren in Java DefaultMutableTreeNode hat Methoden zum Einfügen und Löschen von Knoten implementiert: public void add(MutableTreeNode neuesKind) // ein Kindknoten wird an das Ende der Liste der Nachfolgeknoten angefügt public void insert(MutableTreeNode neuesKind, int KindIndex) // Einfügen eines neuen Kindknoten an beliebiger Stelle public void remove(int kindIndex) // Entfernen eines beliebigen Knoten public void removeAllChildren() // Entfernen aller Kindknoten /* Zugriff auf Infornationen, die in einem benutzerObjekt gehalten werden */ public void setUserObject(Object benutzerObjekt) public Object getUserObject() // benutzerObjekt ist auch der Lieferant von Knotenbeschriftungen. Jeder Aufruf von toString() // wird an benutzerObjekt weitergeleitet TreeSelectionModel Diese Interface spezifiziert, wie ein Benutzer einen Pfad bestimmter Objekte auswählen kann. JTree benutzt dieses Modell zur Auswahl von Regeln. DefaultTreeSelectionModel ist eine Implementierung von TreeSelectionModel. TreeSelectionModel steuert das Selektieren von Knoten. Die Zugriffsmethoden auf TreeSelectionModel sind: public void setSelectionModel(TreeSelectionModel selectionModel) public TreeSelectionModel getSelectionModel() Standardmäßig erlaubt ein JTree das Selektieren mehrerer Knoten. Soll die Selektionsmöglichkeit auf einen Knoten beschränk werden, muß ein eigenes TreeSelectionModel an setSelectionModel() übergeben werden. Dazu kann eine Instanz der Klasse DefaultTreeSelectionModel erzeugt werden und durch Aufruf von public void setSelectionMode(int mode) und Übergabe einer der Konstanten SINGLE_TREE_SELECTION CONTIGUOUS_TREE_SELECTION DISCONTIGUOUS_TREE_SELECTION konfiguriert werden. TreeCellRenderer Dieses Interface wird zur visuellen Repräsentation von Baumknoten benutzt. Es umfasst nur eine Methode. Anwendungen 379 Programmieren in Java 1. Ein Baum, der aus DefaultMutableTreeNodes aufgebaut ist255. import java.awt.*; import javax.swing.*; import javax.swing.tree.*; public class SimpleTree extends JFrame { public SimpleTree() { super("Creating a Simple JTree"); // WindowUtilities.setNativeLookAndFeel(); /* try { UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName()); } catch(Exception e) { System.out.println("Error setting native L&F: " + e); } */ // addWindowListener(new ExitListener()); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); Container content = getContentPane(); Object[] hierarchy = { "javax.swing", "javax.swing.border", "javax.swing.colorchooser", "javax.swing.event", "javax.swing.filechooser", new Object[] { "javax.swing.plaf", "javax.swing.plaf.basic", "javax.swing.plaf.metal", "javax.swing.plaf.multi" }, "javax.swing.table", new Object[] { "javax.swing.text", new Object[] { "javax.swing.text.html", "javax.swing.text.html.parser" }, "javax.swing.text.rtf" }, "javax.swing.tree", "javax.swing.undo" }; DefaultMutableTreeNode root = processHierarchy(hierarchy); JTree tree = new JTree(root); content.add(new JScrollPane(tree), BorderLayout.CENTER); setSize(275, 300); setVisible(true); } public static void main(String[] args) { new SimpleTree(); } /** Small routine that will make node out of the first entry * in the array, then make nodes out of subsequent entries * and make them child nodes of the first one. The process is * repeated recursively for entries that are arrays. */ private DefaultMutableTreeNode processHierarchy(Object[] hierarchy) { DefaultMutableTreeNode node = new DefaultMutableTreeNode(hierarchy[0]); DefaultMutableTreeNode child; for(int i=1; i<hierarchy.length; i++) { 255 vgl. pr56570 380 Programmieren in Java Object nodeSpecifier = hierarchy[i]; if (nodeSpecifier instanceof Object[]) // Knoten mit Nachfolgeknoten child = processHierarchy((Object[])nodeSpecifier); else child = new DefaultMutableTreeNode(nodeSpecifier); // Blatt node.add(child); } return(node); } } Swing besitzt einen Manager für Benutzerschnittstellen, der das "Look and Feel" (Java L&F) von Komponenten kontrolliert. Das Management des Look and Feel wird von der Klasse UIManager übernommen. Folgende Auswahlmöglichkeiten stehen in Abhängigkeit von der Java-Entwicklungsumgebung zur Verfügung: - das Windows-95- und Windows-NT-Look-and-Feel - das Motif-x-Windows-System-Look-and-Feel - Metal Die Auswahl des Look-and-Feel erfolgt mit der Methode getSystemLookAndFeelClassName() als Argument für die Methode setLookandFeel. Es werden unterschiedliche Ergebnisse auf unterschiedlichen Betriebssystemen erzeugt. 381 Programmieren in Java 382 Programmieren in Java JComponent JTree ... protected TreeCellEditor cellEditor protected TreeCellRendrer cellRenderer … protected boolean rootVisible protected TreeSelectionModel selectionModel … protected TreeModel treeModel protected TreeModellListener treeModelListener … << Konstruktoren >> JTree() JTree(Hashtable wert) JTree(TreeModel neuesModel) JTree(TreeNode root) JTree(Vector wert) JTree(Object[] wert) … << Methoden >> … public void addSelectionPath(TreePath pfad) public void addSelectionPaths(TreePath[] pfade) public TreePath getSelectionPath() // ermittelt das ausgewählte Element public TreePath[] getSelectionPaths() public void setSelectionPath(TreePath pfad) public void setSelectionPaths(TreePath[] pfade) public TreePath getLeadSelectionPath() // liefert das markierte Element … public void setRootVisible(boolean rootVisible) public boolean isRootVisible() public TreeModel getModel() public void setModel(TreeModel neuesModel) public TreeCellRenderer getCellRenderer () public void setCellRenderer(TreeCellrenderer x) … public void setUI(TreeUI ui) public void updateUI() … public void setSelectionModel(TreeSelectionModel selectionModel) … Abb.: Die Klasse JTree 383 Programmieren in Java << interface >> TreeModel public Object getRoot() public Object getChild(Object parent, int index) public boolean isLeaf(Object knoten) public void valueForPathChanged(TreePath pfad,Object neuerWert) public int getIndexOfChild(Object parent, Object child) public void addTreeModelListener(TreeModelListener l) public void removeTreeListener(TreeModelListener l) DefaultTreeModell … proteced TreeNode root protected EventListenerList listenerList << Konstruktoren >> public DefaultTreeModel() public DefaultTreeModel(TreeNode root,boolean asksAllowsChildren) << Metoden >> public void addTreeModelListener l) public void removeTreeModelListener(TreeModelListener l) … public Object getChild(Object parent, int index) public int getChildCount(Object parent) public int getIndexOfChild(Object parent,Object child) … public TreeNode[] getPathToRoot(TreeNode einKnoten) … public Object getRoot() …. public void insertInfo(MutableTreeNode neuesKind, MutableTreeNode parent,int index) public boolean isLeaf(Object knoten) public void nodeChanged(TreeNode knoten) … public void removeNodeFromParent(MutableTreeNode knoten) … public void setRoot(TreeNode root) public void valueForPathChanged(TreePath path,Object neuerWert) 384 Programmieren in Java << interface >> TreeSelectionModel static int CONTIGUOUS_TREE_SELECTION static int DISCONTIGUOUS_TREE_SELECTION static int SINGLE_TREE_SELECTION public void setSelectionMode(int node) public int setSelectionPath(TreePath path) public void setSelectionPaths(TreePath [] paths) public void addSelectionPath(TreePath path) public void addSelectionPaths(TreePath [] paths) public void removeSelectionPath(TreePath path) public void removeSelectionPaths(TreePaths [] paths) public TreePath getSelectionPath() public TreePath[] getSelectionPaths() public int getSelectionCount() public boolean isPathSelected(TreePath path) public boolean isSelectionEmpty() public void clearSelection() public int [] getSelectionRows() public boolean isRowSelected(int row) public void resetRowSelection() public int getLeadSelecttionRow() public TreePath getSelectionPath() public void addPropertyChangeListener(PropertyChangeListener listener) DefaultTreeSelectionModel ... protected int leadIndex ... protected TreePath [] selection … static String SELECTION_MODE_PROPERTY protected int selectionMode … << Konstruktor >> public DefaultTreeSelectionModel() << Methoden >> public void addSelectionPath(TreePath path) public void addSelectionPaths(TreePath [] paths) public void removeSelectionPath(TreePath path) … public void addTreeSelectionListener(TreeSelectionListener x) public void removeTreeSelectionListener(TreeSelectionListener x) … public void updateLeadIndex() public void insureUniqueness public String toString() public Object clone() throws CloneNotSupportedException 385 Programmieren in Java << interface >> TreeNode public Enumertion children() public boolean getAllowsChildren() public TreeNode getChildAt(int childIndex) public int getChildCount() public int getIndex(TreeNode node) public TreeNode getParent() public boolean isLeaf() << interface >> MutableTreeNode public void insert(MutableTreeNode child,int index) public void remove(int index) public void remove(MutableTreeNode node) public void removeFromParent() public void setParent(MutableTreeNode newParent) public void setUserObject(Object objekt) DefaultMutableTreeNode …. protected MutableTreeNode parent << Konstruktoren >> DefaultMutableTreeNode() DefaultMutableTreeNode(Object objekt) DefaultMutableTreeNode(Object benutzerObjekt) << Methoden >> public void add(MutableTreeNode neuesKind) public Enumeartion breadFirstEnumeration() … public Enumeration depthFirstEnumeration() … public TreeNode getChildAfter(TreeNode einKind) … public TreeNode getChildBefore(TreeNode einKind) … public int getDepth() … public int getLevel() … public TreeNode getPath() protected TreeNode[] getPathToRoot(TreeNode einKnoten, int tiefe) … public TreeNode getRoot() … public boolean isRoot() … public void removeAllChildren() … public String toString() 386 Programmieren in Java << interface >> TreeCellEditor public Component getTreeCellEditorComponent(JTree tree, Object value, boolean isSelected, Boolean expanded, boolean leaf, int row) DefaultTreeCellEditor protected Color borderSelectionColor protected booleab canEdit protected Component editingComponent protected Container editingContainer protected Icob editingIcon protected Font font protected TreePath lstPath … protected Timer timer protected JTree tree << Konstruktoren >> public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer) public DefaultTreeCellEditor(JTree tree, DefaultTreeCellRenderer renderer,TreeCellEditor editor) << Methoden >> public void setBorderSelectionColor(Color newColor) public Color getBorderSelectionColor() public void setFont(Font font) public Font getFont() … 387 Programmieren in Java << interface >> TreeCellRenderer public Component getTreeCellRendererComponent( JTree tree, Object value, boolean selected, boolean expanded, bollean leaf, int row, boolean lostFocus) DefaultTreeCellRenderer protected Color backGroundSelectionColor protected Color borderSelectionColor protected Icon closedIcon protected boolean hasFocus protected Icon leafIcon protected Icon openIcon protected boolean selected protected Color textNonSelectionColor protected Color textSelectionColor << Konstruktor >> public TreeCellRenderer() << Methoden >> public void setFont(Font font) public void setLeafIcon(Icon newIcon) public void setOpenIcon(Icon newIcon) public void setTextNonSelectionColor(Color newColor) public void setTextSelectionColor(Color newColor) public void validate() 388 Programmieren in Java 5.6.6 Ereignisbehandlung unter Swing 389 Programmieren in Java 6. Utilities 6.1 Kollektionen (Collections) Kollektionen (Collections) sind Datenstrukturen zur Aufnahme und Verarbeitung von Datenmengen. Typische Collections sind Stacks, Queues, Priority Queues, Listen oder Trees (Bäume). In Java existieren seit der Version 1.0 die Collections Vector, Stack, Hashtable und Bitset. Die Kritik an diesem Collection-Konzept führte zu einer Sammlung von 20 Klassen und Interfaces im Paket java.util des JDK 1.2. Im Wesentlichen sind dies: Set, List und Map. - Eine List ist eine beliebig große Liste von Elementen beliebigen Typs, auf die wahlfrei und sequentiell zugegriffen werden kann Ein Set ist eine Menge von Elementen (ohne Duplikate), auf die mit typischen Mengenoperationen zugegriffen werden kann. Eine Map ist eine Abbildung von Elementen eines Typs auf Elemente eines anderen Typs (Menge zusammengehöriger Paare). Jede dieser Grundformen ist als Interface und den angegebenen Namen implementiert. Zudem gibt es jeweils eine oder mehrere konkrete Implementierungen. So gibt es bspw. für das Interface List die Implementierungsvarianten LinkedList bzw. die abstrakte Implementierung AbstractList. 6.1.1 Durchwandern von Datenstrukturen mit Iteratoren Bei Datenstrukturen gibt es eine Möglichkeit, gespeicherte Daten unabhängig von der Implementierung immer mit der gleichen Technik abzufragen. Bei Datenstrukturen handelt es sich meistens um Daten in Listen, Bäumen oder ähnlichem und oft wird nur die Frage nach der Zugehörigkeit eines Worts zum Datenbestand gestellt (z.B. „Gehört das Wort dazu?“). Auch die Möglichkeit Daten in irgendeiner Form aufzuzählen, ist eine häufig gestellte Aufgabe. Hierfür bieten sich Iteratoren an. In Java umfaßt das Interface Enumeration die beiden Funktionen hasMoreElements() und nextElement(), mit denen durch eine Datenstruktur iteriert werden kann. public interface Enumeration { public boolean hasMoreElements(); // Test, ob noch ein weiteres Element aufgezählt werden kann public Object nextElement() throws NoSuchElementException; /* setzt den internen Zeiger auf das nächste Element, d. h. liefert das das nächste Element der Enumertion zurück. Diese Funktion kann eine NoSuchException auslösen, wenn nextElement() aufgerufen wird, obwohl hasMoreElements() unwahr ist */ } 390 Programmieren in Java Die Aufzählung erfolgt meistens über for (Enumeration e = ds.elements(); e.hasMoreElements(); ) System.out.println(e.nextElements()); Die Datenstruktur ds besitzt eine Methode elements(), die ein Enumeration-Objekt zurückgibt, das die Aufzählung erlaubt. 6.1.2 Die Klasse Vector class java.util.Vector extends AbstractList implements List, Cloneable, Collection, Serializable Die Klasse Vector beschreibt ein Array mit variabler Länge. Objekte der Klasse Vector sind Repräsentationen einer linearen Liste. Die Liste kann Elemente beliebigen Typs enthalten, ihre Länge ist zur Laufzeit veränderbar (Array mit variabler Länge). Vector erlaubt das Einfügen von Elementen an beliebiger Stelle, bietet sequentiellen und wahlfreien Zugriff auf die Elemente. Das JDK realisiert Vector als Array von Elementen des Typs Object. Der Zugriff auf Elemente erfolgt über Indizes. Es wird dazu aber kein Operator [], sondern es werden Methoden benutzt, die einen Index als Parameter annehmen. Anlegen eines neuen Vektors (Konstruktor): public Vector() public Vector(int initialCapacity, int capacityIncrement) // Ein Vector vergrößert sich automatisch, falls mehr Elemente aufgenommen werden, als // ursprünglich vorgesehen (Resizing). Dabei sollen initialCapacity und capacityIncrement // passend gewählt werden. Einfügen von Elementen: public void addElement(Object obj) // Anhängen an des Ende der bisher vorliegenden Liste von Elementen Eigenschaften: public boolean isEmpty() // Prüfen, ob der Vektor leer ist public int size() // bestimmt die Anzahl der Elemente public int capacity() // bestimmt die interne Größe des Arrays. Sie kann mit ensureCapacity() geändert // werden Einfügen an beliebiger Stelle innerhalb der Liste: public void insertElementAt(Object obj, int index) throws ArrayIndexOutOfBoundsException // fügt obj an die Position index in den "Vector" ein. Zugriff auf Elemente: Für den sequentiellen Zugriff steht ein Iterator zur Verfügung. Wahlfreier Zugriff erfolgt über: public Object firstElement() throws NoSuchElementException; public Object lastElement() throws NoSuchElementException; public Object elementAt(int index) throws ArrayIndexOutOfBoundException; firstElement() liefert das erste, lastElement() das letzte Element- Mit elementAt() wird auf das Element an der Position index zugegriffen. Alle 3 Methoden verursachen eine Ausnahme, wenn das gewünschte Element nicht vorhanden ist. Arbeitsweise des internen Arrays. Der Vector vergrößert sich automatisch, falls mehr Elemente aufgenommen werden. Die Operation heißt Resizing. 391 Programmieren in Java Die Größe des Felds. Mit capacity() erhält man die interne Größe des Arrays. Sie kann mit ensureCapacity() geändert werden. ensureCapacity(int minimumCapacity) bewirkt bei einem Vector, daß er mindestens minCapacity Elemente aufnehmen soll. Der Vektor verkleinert nicht die aktuelle Kapazität, falls sie schon höher als minCapacity ist. Zur Veränderung dieser Größe, dient die Methode trimToSize(). Sie reduziert die Kapazität des Vectors auf die Anzahl der Elemente, die gerade im Vector sind. Die Anzahl der Elemente kann über die Methode size() erfragt werden. Sie kann über setSize(int newSize) geändert werden. Ist die neue Größe kleiner als die alte, so werden die Elemente am Ende des Vectors abgeschnitten. Ist newSize größer als die alte Größe, werden die neu angelegten Elemente mit null initialisiert. Bereitstellen des Interface Enumerartion. In der Klasse Vector liefert die Methode public Enumeration elements() einen Enumerator (Iterator) für alle Elemente, die sich in Vector befinden. Vector << Konstruktoren >> public Vector() // Ein Vector in der Anfangsgröße von 10 Elementen wird angelegt public Vector(int startKapazitaet) // Ein Vector enthält Platz für startKapazitaet Elemente public Vector(int startKapazitaet, int kapazitaetsSchrittweite) << Methoden >> public Object elementAt(int index) // Das an der Stelle index befindliche Objekt wird zurückgegeben public int size() public Object firstElement() public Object lastElement(); public void insertElementAt(Object obj, int index) // fügt Object obj an index ein und verschiebt die anderen Elemente public void setElementAt(Object obj, int index) public copyInto(Object einArray[]) // kopiert die Elemente des Vektors in das Array einArray // Falls das bereitgestellte Objektfeld nicht so groß ist wie der Vektor, // dann tritt eine IndexOutOfBoundsException auf public boolean contains(Object obj) // sucht das Element, liefert true zurück wenn o im Vector vorkommt public int indexOf(Object obj) // sucht im Vector nach dem Objekt obj. Falls obj nicht in der Liste ist, wird // -1 übergeben public int lastIndexOf(Object obj) public boolean removeElement(Object obj) // entfernt obj aus der Liste. Konnte es entfernt werden, wird true // zurückgeliefert public void removeElementAt(int index) // entfernt das Element an Stelle index public void removeAllElements() // löscht alle Elemente public int capacity() // gibt an, wieviel Elemente im Vektor Patz haben // (, ohne daßautomatische Größenanpassung erfolgt) public Object clone() // Implementierung der clone()-methode von Object, d.h. eine Referenz // des kopierten Feldes wird zurückgegeben. Die Kopie ist flach. public String toString() Abb.: Die Klasse Vector 392 Programmieren in Java 6.1.3 Die Klasse Stack class java.util.Stack extends Vector Ein Stack ist eine nach dem LIFO-Prinzip arbeitende Datenstruktur. Elemente werden vorn (am vorderen Ende der Liste) eingefügt und von dort auch wieder entnommen. In Java ist ein Stack eine Ableitung von Vector mit neuen Zugriffsfunktionen für die Implementierung des typischen Verhaltens von einem Stack. Konstruktor: public Stack(); Hinzufügen neuer Elemente: public Object push(Object item); Zugriff auf das oberste Element: public Object pop(); // Zugriff und Entfernen des obersten Element public Object peek() // Zugriff auf das oberste Element Suche im Stack: public int search(Object o) // Suche nach beliebigem Element, // Rueckgabewert: Distanz zwischen gefundenem und // obersten Stack-Element bzw. –1, // falls das Element nicht da ist. Test:public boolean empty() // bestimmt, ob der Stack leer ist Vector Stack public Stack() public Object push(Object obj) public Object pop() public Object peek() public int search(Object obj) public boolean empty() Abb.: Die Klasse Stack Anwendungen: 1. Umrechnen von Dezimalzahlen in andere Basisdarstellungen Aufgabenstellung: Defaultmäßig werden Zahlen dezimal ausgegeben. Ein Stapel, der Ganzzahlen aufnimmt, kann dazu verwendet werden, Zahlen bezogen auf eine andere Basis als 10 darzustellen. Die Funktionsweise der Umrechnung von Dezimalzahlen in eine Basis eines anderen Zahlensystem zeigen die folgenden Beispiele: 393 Programmieren in Java 2810 = 3 ⋅ 8 + 4 = 34 8 72 10 = 1 ⋅ 64 + 0 ⋅ 16 + 2 ⋅ 4 + 0 = 1020 4 5310 = 1 ⋅ 32 + 1 ⋅ 16 + 0 ⋅ 8 + 1 ⋅ 4 + 0 ⋅ 2 + 1 = 1101012 Mit einem Stapel läßt sich die Umrechnung folgendermaßen unterstützen: 6 1 leerer Stapel n = 355310 7 7 4 4 4 1 1 1 n%8=1 n/8=444 n%8=4 n/8=55 n%8=7 n/8=6 n%8=6 n/6=0 n = 44410 n = 5510 n = 610 n = 010 Abb.: Umrechnung von 355310 in 67418 mit Hilfe eines Stapel Algorithmus zur Lösung der Aufgabe: 1) Die am weitesten rechts stehende Ziffer von n ist n%b. Sie ist auf dem Stapel abzulegen. 2) Die restlichen Ziffern von n sind bestimmt durch n/b. Die Zahl n wird ersetzt durch n/b. 3) Wiederhole die Arbeitsschritte 1) und 2) bis keine signifikanten Ziffern mehr übrig bleiben. 4) Die Darstellung der Zahl in der neuen Basis ist aus dem Stapel abzulesen. Der Stapel ist zu diesem Zweck zu entleeren. Implementierung: Das folgende kleine Testprogramm256 realisiert den Algorithmus und benutzt dazu eine Instanz von Stack. import java.util.*; public class PR61210 { public static void main(String[] args) { int zahl = 3553; // Dezimalzahl int b = 8; // Basis Stack s = new Stack(); // Stapel do { s.push(new Integer(zahl % b)); zahl /= b; } while (zahl != 0); while (!s.empty()) { System.out.print(s.pop()); } System.out.println(); } } 256 pr61210 394 Programmieren in Java 2. Einlesen einer Folge ganzer Zahlen, Ausgabe der Zahlen in umgekehrter Reihenfolge257 import java.io.*; import java.util.*; public class Reverse { public static void main (String args[]) throws IOException { int a[]=new int[20]; //Stapel Stack s = new Stack(); // Einlesen der ganzen Zahlen, Aufnahme der Zahlen in einen ganzzahligen // Array, Aufnahme der im ganzzahligen Array gespeicherten Zahlen in // einen Stapel BufferedReader ein=new BufferedReader(new InputStreamReader(System.in)); for ( int i=0;i<a.length;i++) { a[i]=Integer.parseInt(ein.readLine()); s.push(new Integer (a[i])); } //Entleeren des Stapels mit Ausgabe auf die Java-Konsole while ( !s.empty() )// 1 Punkt { System.out.print(" "+((Integer)s.pop()).intValue()); } System.out.println( ); } } Ein Stack ist ein Vector. Die Vector-Klasse wird von der Klasse Stack erweitert. Das ist sicherlich nicht immer besonders sinnvoll. Funktionen, die im Gegensatz zur Leistungsfähigkeit eines Stapels stehen sind add(), addAll(), addElement(), capacity(), clear(), clone(), contains(), copyInto(), elementAt(), .... . 257 vgl. pr62100 395 Programmieren in Java 6.1.4 Die Klasse Bitset für Bitmengen class java.util.BitSet implements Cloneable, Serializable Die Klasse Bitset bietet komfortable Möglichkeiten zur bitweisen Manipulation von Daten. Bitset anlegen und füllen. Mit zwei Methoden lassen sich die Bits des Bitsets leicht ändern: set(int bitNummer) und clear(int bitNummer). Mengenorintierte Operationen. Das Bitset erlaubt mengenorientierte Operationen mit einer weiteren Menge. BitSet public void and(BitSet bs) public void or(BitSet bs) public void xor(BitSet bs) public void andNot(Bitset set) // löscht alle Bits im Bitset, dessen Bit in set gesetzt sind public void clear(int index) // Löscht ein Bit. Ist der Index negativ, kommt es // zur Auslösung von IndexOutOfBoundsException public void set(int index) // Setzt ein Bit. Ist der Index negativ, kommt es // zur Auslösung von IndexOutOfBoundsException public boolean get(int index) // liefert den Wert des Felds am übergebenen Index, // kann IndexOutOfBoundsException auslösen. public int size() public boolean equals(Object o) // Vergleicht sich mit einem anderen Bitset-Objekt o. Abb.: Die Klasse BitSet Bsp.258: Anwendung der Klasse BitSet Primzahlen bei der Konstruktion einer Menge von import java.util.*; public class Primtest { final static int MAXPRIM = 30; public static void main(String args[]) { BitSet b = new BitSet(); for (int i = 2; i <= MAXPRIM; i++) { boolean ok = true; for (int j = 2; j < i; j++) if (b.get(j) && (i % j) == 0) { ok = false; break; } if (ok) b.set(i); } for (int i = 1; i < MAXPRIM; i++) { 258 pr61310 396 Programmieren in Java if (b.get(i)) System.out.print(i + " "); } System.out.println(); } } 397 Programmieren in Java 6.1.5 Die Klasse Hashtable und assoziative Speicher Eine Hashtabelle (Hashtable) ist ein assoziativer Speicher, der Schlüssel (keys) mit Werten verknüpft. Die Datenstruktur ist mit einem Wörterbuch vergleichbar. Die Hashtabelle arbeitet mit Schlüssel/Werte Paaren. Aus dem Schlüssel wird nach einer Funktion – der sog. Hashfunktion – ein Hashcode berechnet. Dieser dient als Index für ein internes Array. Dieses Array hat zu Anfang ein feste Grösse. Leider hat dieses Technik einen entscheidenden Nachteil. Besitzen zwei Wörter denselben Hashcode, dann kommt es zu einer Kollision. Auf ihn muß die Datenstruktur vorbereitet sein. Hier gibt es verschiedene Lösungsansätze. Die unter Java implementierte Variante benutzt eine verkettete Liste (separate Chaining). Falls eine Kollision auftritt, so wird der Hashcode beibehalten und der Schlüssel bzw. Wert in einem Listenelement an den vorhandenen Eintrag angehängt. Wenn allerdings irgendwann einmal eine Liste durchsucht werden muß, dann wird die Datenstruktur langsam. Ein Maß für den Füllgrad ist der Füllfaktor (Load Factor). Dieser liegt zwischen 0 und 100 %. 0 bedeutet: kein Listenelement wird verwendet. 100 % bedeutet: Es ist kein Platz mehr im Array und es werden nur noch Listen für alle zukommenden Werte erweitert. Der Füllfaktor sollte für effiziente Anwendungen nicht höher als 75% sein. Ist ein Füllfaktor nicht explizit angegeben, dann wird die Hashtabelle „rehashed“, wenn mehr als 75% aller Plätze besetzt sind. class java.util.Hashtable extends Dictionary implements Map, Cloneable, Serializable Erzeugen von einem Objekt der Klasse HashTable: public Hashtable() /* Die Hashtabelle enthält eine Kapazität von 11 Einträgen und einen Füllfaktor von 75 % */ public Hashtable(int initialCapacity) /* erzeugt eine Hashtabelle mit einer vorgebenen Kapazität und dem Füllfaktor 0.75 */ public Hashtable(int initialCapacity, float loadFactor) /* erzeugt eine Hashtabelle mit einer vorgebenen Kapazität und dem angegebenen Füllfaktor */ Daten einfügen: public Object put(Object key, Object value) /* speichert den Schlüssel und den Wert in der Hashtabelle. Falls sich zu dem Schlüssel schon ein Eintrag in der Hashtabelle befand, so wird dieser zurückgegeben. Anderenfalls ist der Rückgabewert null. Die Methode ist vorgegeben vom Interface Map. Es überschreibt die Methode von der Superklasse Dictionary. */ Daten holen: public Object get(Object key) Schlüssel entfernen. public Object remove(Object key) Löschen der Werte. public void clear() Test. public boolean containsKey(Object key) // Test auf einen bestimmten Schlüssel public boolean containsValue(Object value) // Test auf einen bestimmten Wert Aufzählen der Elemente. Mit keys() und elements() bietet die Hashtabelle zwei Methoden an, die eine Aufzählung zurückgeben: public Enumeration keys() // liefert eine Aufzählung aller Schlüssel, überschreibt keys() in Dictionary. public Enumeration elements() // liefert eine Aufzählung der Werte, überschreibt elements() in Dictionary 398 Programmieren in Java Wie üblich liefern beide Iteratoren ein Objekt, welches das Interface Enumeration implementiert. Der Zugriff erfolgt daher mit Hilfe der Methoden hasMoreElements() und nextElement(). Dictionary {abstract} public abstract Object put (Object key, Object value) public abstract Object get(Object key) public abstract Enumeration elements() public abstract Enumeration keys() public abstract int size() public abstract boolean isEmpty() public abstract Object remove(Object key) Hashtable Map << Konstruktor >> public Hashtable(int initialKapazitaet) public Hashtable(int initialKapazitaet, float Ladefaktor) public Hashtable() << Methoden >> public boolean contains(Object wert) public boolean containsKey(Object key) public void clear() public Object clone() protected void rehash() public String toString() Properties << Konstruktor >> public Properties() // legt einen leeren Container an public Properties(Properties defaults) // füllt eine Property-Liste mit den angegebenen Default-Werten << Methoden >> public String getProperty(String key) public String getProperty(String key, String defaultKey) public void load(InputStream in) throws IOException // Hier muß ein InputStream übergeben werden, der die daten der // Property-Liste zur Verfügung stellt. public void store(OutputStream out, String header) public void list(PrintStream out) public void list(PrintWriter out) public Enumeration propertyNames() // beschafft ein Enumerations-Objekt mit denen Eigenschaften // der Property-Liste aufgezählt werden können Abb.: Die Klassen Hashtable und Properties 399 Programmieren in Java Die Klasse Hashtable ist eine Konkretisierung der abstrakten Klasse Dictionary. Diese Klasse beschreibt einen assoziativen Speicher, der Schlüssel auf Werte abbildet und über den Schlüsselbegriff einen effizienten Zugriff auf den Wert ermöglicht. Einfügen und der Zugriff auf Schlüssel erfolgt nicht auf der Basis des Operators „==“, sondern mit Hilfe der Methode „equals“. Schlüssel müssen daher lediglich inhaltlich gleich sein, um als identisch angesehen zu werden. Beispiele. 1. Hashtabelle zum Test der von Zufallszahlen der Methode Math.random()259. import java.util.*; class Zaehler { int i = 1; public String toString() { return Integer.toString(i); } } public class Statistik { public static void main(String args[]) { Hashtable h = new Hashtable(); for (int i = 0; i < 10000; i++) { // Erzeuge eine Zahl zwischen 0 und 20 Integer r = new Integer((int)(Math.random() * 20)); if (h.containsKey(r)) ((Zaehler) h.get(r)).i++; else h.put(r,new Zaehler()); } System.out.println(h); } } 2. Hashtabelle für direkten Zugriff auf Daten260 import java.io.*; import java.util.*; public class HashTabTest { public static void main(String [ ] args) { // Map map = new HashMap(); Hashtable h = new Hashtable(); String eingabeZeile = null; BufferedReader eingabe = null; try { eingabe = new BufferedReader( new FileReader("eing.txt")); } catch (FileNotFoundException io) { System.out.println("Fehler beim Einlesen!"); 259 260 pr61310 pr61310 400 Programmieren in Java } try { while ( (eingabeZeile = eingabe.readLine() ) != null) { StringTokenizer str = new StringTokenizer(eingabeZeile); if (eingabeZeile.equals("")) break; String key = str.nextToken(); String daten = str.nextToken(); System.out.println(key); h.put(key,daten); // map.put(key,daten); } } catch (IOException ioe) { System.out.println("Eingefangen in main()"); } try { eingabe.close(); } catch(IOException e) { System.out.println(e); } System.out.println("Uebersicht zur Hash-Tabelle"); // System.out.println(map); System.out.println(h); //h.printHashTabelle(); System.out.println("Abfragen bzw. Modifikationen"); // Wiederauffinden String eingabeKey = null; BufferedReader ein = new BufferedReader( new InputStreamReader(System.in)); System.out.println("Wiederauffinden von Elementen"); while (true) { try { System.out.print("Bitte Schluessel eingeben, ! bedeutet Ende: "); eingabeKey = ein.readLine(); // System.out.println(eingabeKey); if (eingabeKey.equals("!")) break; String eintr = (String) h.get(eingabeKey); // String eintr = (String) map.get(eingabeKey); if (eintr == null) System.out.println("Kein Eintrag!"); else { System.out.println(eintr); System.out.println("Soll dieser Eintrag geloescht werden? "); String antwort = ein.readLine(); // System.out.println(antwort); if ((antwort.equals("j")) || (antwort.equals("J"))) { // System.out.println("Eintrag wird entfernt!"); h.remove(eingabeKey); // map.remove(eingabeKey); } } } catch(IOException ioe) { System.out.println(eingabeKey + " konnte nicht korrekt eingelesen werden!"); } } System.out.println(h); 401 Programmieren in Java // // // // } } System.out.println(map); System.out.println("Sortierte Tabelle"); Map sortedMap = new TreeMap(map); System.out.println(sortedMap); Die Klasse Hashtable benutzt das Verfahren der Schlüsseltransformation (HashFunktion) zur Abbildung von Schlüsseln auf Indexpostionen eines Arrays. Die Kapazität der Hash-Tabelle gibt die Anzahl der Elemente an, die insgesamt untergebracht werden können. Der Ladefaktor zeigt an, bei welchem Füllungsgrad die Hash-Tabelle vergrößert werden muß. Das Vergrößern erfolgt automatisch, falls die Anzahl der Elemente innerhalb der Tabelle größer ist als das Produkt aus Kapazität und Ladefaktor. Seit dem JDK 1.2 darf der Ladefaktor auch größer als 1 sein. In diesem Fall wird die Hash-Tabelle erst dann vergrößert, wenn der Füllungsgrad größer als 100% ist und bereits ein Teil der Elemente in den Überlaufbereichen untergebracht wurde. Die Klasse Hashtable ist eine besondere Klasse für Wörterbücher. Ein Wörterbuch ist eine Datenstruktur, die Elemente miteinander assoziiert. Das Wörterbuchproblem ist das Problem, wie aus dem Schlüssel möglichst schnell der zugehörige Wert konstruiert wird. Die Lösung des Problems ist: Der Schlüssel wird als Zahl kodiert (Hashcode) und dient in einem Array als Index. An einem Index hängen dann noch die Werte mit gleichem Hashcode als Liste an. 6.1.6 Die abstrakte Klasse Dictionary Die Klasse Dictionary ist eine abstrakte Klasse, die Methoden anbietet, wie Objekte (also Schlüssel und Wert) miteinander assoziiert werden: public abstract Object put(Object key, Object value) // fügt den Schlüssel key mit dem verbundenen Wert value in das Wörterbuch // ein public abstract Object get(Object key) // // // // liefert das zu key gehörende Objekt zurück. Falls kein Wert mit dem Schlüssel verbunden ist, so liefert get() eine null. Eine null als Schlüssel oder Wert kann nicht eingesetz werden. In put() würde das zu einer NullPointerException führen. public abstract Object remove(Object key) // entfernt ein Schlüssel/Wertepaar aus dem Wörterbuch. Zurückgegeben wird // der assoziierte Wert. public abstract boolean isEmpty() // true, falls keine Werte im Wörterbuch public int size() gibt zurück, wie viele Elemente aktuell im Wörterbuch sind. public abstract Enumeration keys() // liefert eine Enumeration für alle Schlüssel public abstract Enumeration elements() // liefert eine Enumeration über alle Werte. 402 Programmieren in Java 6.1.7 Die Klasse Properties Die Properties Klasse ist eine Erweiterung von Hashtable. Ein Properties Objekt erweitert die Hashtable um die Möglichkeit, sich unter einem wohldefinierten Format über einen Strom zu laden und zu speichern. Erzeugen. public Properties() // erzeugt ein leeres Propertes Objekt ohne Worte. public Properties(Properties p) // erzeugt ein leeres Properties Objekt mit Standard-werten aus den // übergebenen Properties Laden einer Properties-Liste. public void load(InputStream is) Speichern einer Properties-Liste. public void store(OutputStream os, String kennung). An den Kopf der Datei wird eine kennung geschrieben (die im 2. Argument angegeben ist). Die Kennung darf „null“ sein. Enumeration. public void Enumeration propertyNames() Zeichenketten suchen. Die Methode getProperty(String s). public String getProperty(String s) // sucht in den Properties nach der Zeichenkette public String getProperty(String key, String default) // sucht in den Properties nach der Zeichenkette key. Ist dieser nicht // vorhanden, wird der String default zurückgegeben Eigenschaften ausgeben. Die Methode list() wandert durch die Daten und gibt sie auf einem PrintWriter aus: public void list(PrintWriter pw) // listet die Properties auf dem PrintWriter aus. Bsp.: Systemeigenschaften der Java-Umgebung import java.util.*; import java.io.*; public class SaveProp { public static void main(String args []) { try { Properties p1 = System.getProperties(); FileOutputStream propAusFile = new FileOutputStream("properties.txt"); p1.store(propAusFile,null); propAusFile.close(); Properties p2 = new Properties(); FileInputStream eing = new FileInputStream("properties.txt"); p2.load(eing); p2.list(System.out); } catch(IOException e) { System.err.println(e);} } } 403 Programmieren in Java 6.2 Collection API Die Java 2 Plattform hat Java erweitert um das Collection API. Anstatt Collection kann man auch Container (Behälter) sagen. Ein Container ist ein Objekt, das wiederum Objekte aufnimmt und die Verantwortung für die Elemente übernimmt. Im „util“-Paket befinden sich sechs Schnittstellen, die grundlegende Eigenschaften der Containerklassen definieren. Das in Java 1.2 enthaltene Collections Framework beinhaltet im Wesentlichen drei Grundformen: Set, List und Map. Jede dieser Grudformen ist als Interface implementiert. Die Interfaces List und Set sind direkt aus Collection abgeleitet. Es gibt auch noch eine abstrakte Implementierung des Interface, mit dessen Hilfe das Erstellen eigener Collections erleichtert wird. Bei allen Collections, die das Interface Collection implementieren, kann ein Iterator zum Durchlaufen der Elemente mit der Methode „iterator()“ beschafft werden. Zusätzlich fordert die JDK 1.2-Spezifikation für jede Collection-Klasse zwei Konstruktoren: - Einen parameterlosen Konstruktor zum Anlegen einer neuen Collection. - Ein mit einem einzigen Collection-Argument ausgestatteter Konstruktor, der eine neue Collection anlegt und mit den Elementen der als Argument übergebenen Collection auffüllt. 6.2.1 Die Schnittstellen Collection, Iterator, Comparator Das Interface Collection bildet die Basis der Collection-Klasse und –Interfaces des JDK 1.2. Alle Behälterklassen implementieren das Collection Interface und geben den Klassen damit einen äußeren Rahmen. 404 Programmieren in Java Das Interface Collection << interface >> Collection public void clear(); // Optional: Löscht alle Elemente in dem Container. Eird dies vom Container nicht unterstützt, // kommt es zur UnsupportedOperationException public boolean add(Object o); // Optional: Fügt ein Objekt dem Container hinzu und gibt true zurück, falls sich das Element // einfügen läßt. Gibt false zurück, falls schon ein Objektwert vorhanden ist und doppelte Werte // nicht erlaubt sind. public boolean addAll(Collection c); // fügt alle Elemente der Collection c dem Container hinzu public boolean remove(Object o); // Entfernen einer einzelnen Instanz. Rückgabewert ist true, wenn das Element gefunden und // entfernt werden konnte public boolean removeAll(Collection c); // Oprtional: Entfernt alle Objekte der Collection c aus dem Container public boolean contains(Object o); // liefert true, falls der Container das Element enthält // Rückgabewert ist true, falls das vorgegebene Element gefunden werden konnte public boolean containsAll(Collection c); // liefert true, falls der Container alle Elemente der Collection c enthält. public boolean equals(Object o); // vergleicht das angegebene Objekt mit dem Container, ob die gleichen Elemente vorkommen. public boolean isEmpty(); // liefert true, falls der Container keine Elemente enthält public int size(); // gibt die Größe des Containers zurück public boolean retainAll(Collection c); public Iterator iterator(); public Object[] toArray(); // gibt ein Array mit Elementen des Containers zurück public Object[] toArray(Object[] a); public int hashCode(); // liefert den Hashwert des Containers public String toString() // Rückgabewert ist die Zeichenketten-Repräsentation der Kollektion. Abb.: Das Interface Collection Die abstrakte Basisklasse AbstractCollection implementiert die Methoden des Interface Collection (ohne iterator() und size()). AbstractCollection ist die Basisklasse von AbstractList und AbstractSet. 405 Programmieren in Java Das Interface Iterator << interface >> Iterator public boolean hasNext(); // gibt true zurück, wenn der Iterator mindestens ein weiteres Element enthält. public Object next(); // liefert das nächste Element bzw. löst eine Ausnahme des Typs NoSuchElementException // aus, wenn es keine weiteren Elemente gibt public void remove(); // entfernt das Element, das der Iterator bei next() geliefert hat. Abb.: Das Interface Iterator Bei allen Collections, die das Interface Collection implementieren, kann ein Iterator zum Durchlaufen der Elemente mit der Methode „iterator()“ beschafft werden. Das Interface Comparator Vergleiche zwischen Objekten werden mit speziellen Objekten vorgenommen, den Comparatoren. Ein konkreter Comparator implementiert die folgende Schnittstelle. << interface >> java.util.Comparator public int compare(Object o1, Object o2) // vergleicht 2 Argumente auf ihre Ordnung public boolean equals(Object arg) // testet, ob zwei Objekte bzgl. des Comparator-Objekts gleich sind Abb. Das Interface Comparator 6.2.2 Die Behälterklassen und Schnittstellen des Typs List Behälterklassen des Typs List fassen eine Menge von Elementen zusammen, auf die sequentiell oder über Index (-positionen) zugegriffen werden kann. Wie Vektoren der Klasse Vector261 hat das erste Element den Index 0 und das letzte den Index „size() – 1“. Es ist möglich an einer beliebigen Stelle ein Element einzufügen oder zu löschen. Die weiter hinten stehenden Elemente werden dann entsprechend weiter nach rechts bzw. nach links verschoben. 261 seit Java 1.2 implementiert die Klasse Vector die Schnittstelle List 406 Programmieren in Java Das Interface List262 << interface >> List public void add(int index, Object element); // Einfügen eines Elements an der durch Index spezifizierten Position public boolean add(Object o); // Anhängen eines Elements ans Ende der Liste // Rückgabewert ist true, falls die Liste durch den Aufruf von add verändert wurde. Er ist false, // wenn die Liste nicht verändert wurde. Das kann bspw. der Fall sein, wenn die Liste keine // Duplikate erlaubt und ein bereits vorhandenes Element noch einmal eingefügt werden soll. // Konnte das Element aus einem anderen Grund nicht eingefügt werden, wird eine Ausnahme // des Typs UnsupportedOperationException, ClassCastException oder IllegalArgumentException // ausgelöst public boolean addAll(Collection c); // Einfügen einer vollständigen Collection in die Liste. Der Rückgabewert ist true, falls die Liste // durch den Ausfruf von add veränder wurde public boolean addAll(int index, Collection c) public void clear(); public boolean equals(Object object); public boolean contains(Object element); public boolean containsAll(Collection collection); public Object remove(int index) public boolean remove(Object element); public boolean removeAll(Collection c); // Alle Elemente werden gelöscht, die auch in der als Argument angebenen // Collection enthalten sind. public boolean retainAll(Collection c); // löscht alle Elemente außer den in der Argument-Collection enthaltenen public Object get(int index); public int hashCode(); public Iterator iterator(); public ListIterator listIterator(): public ListIterator listIterator(int startIndex); public Object set(int index, Object element); public List subList(int fromIndex, int toIndex); public Object[] toArray(); public Object[] toArray(Object[] a); Abb.: Das Interface List Auf die Elemente einer Liste läßt sich mit einem Index zugreifen und nach Elementen läßt sich mit linearem Aufwand suchen. Doppelte Elemente sind erlaubt. Die Schnittstelle List, die in ArrayList und LinkedList eine Implementierung findet, erlaubt sequentiellen Zugriff auf die gespeicherten gespeicherten Elemente. Das Interface List wird im JDK von verschiedenen Klassen implementiert: AbstractList ist eine abstrakte Basisklasse (für eigene List-Implementierungen), bei der alle Methoden die Ausnahme UnsupportedOperationException auslösen und diverse Methoden abstract deklariert sind. Die direkten Subklassen sind 262 Da ebenfalls das AWT-Paket eine Klasse mit gleichen Namen verwendet, muß der voll qualifizierte Name in Anwendungen benutzt werden. 407 Programmieren in Java AbstractSequentialList, ArrayList und Vector.java.util. AbstractList implementiert bereits viele Methoden für die beiden ListenKlassen263: abstract class AbstractList extends AbstractCollection implements List << Methoden >> public void add(int index, Object element) // Optional: Fügt ein Objekt an der spezifizierten stelle ein public boolean add(Object o) // Optional: Fügt das Element am Ende an public boolean addAll(int index, Collection c) // Optional: Fügt alle Elemente der Collection ein public void clear() // Optional: Löscht alle Elemente public boolean equals(Object o) // vergleicht die Liste mit dem Objekt public abstract Object get(int index) // liefert das Element an dieser Stelle int hashCode() // liefert HashCode der Liste int indexOf(Object o) // liefert Position des ersten Vorkommens für o oder –1, // wenn das Element nicht existiert. Iterator iterator() // liefert den Iterator. Überschreibt die Methode AbstractCollection, // obwohl es auch listIterator() für die spezielle Liste gibt. Die Methode // ruft aber listIterator() auf und gibt ein ListIterator-Objekt zurück Object remove(int index) // löscht ein Element an Position index. protected void removeRange(int fromIndex, int toIndex) // löscht Teil der Liste von fromIndex bis toIndex. fromIndex wird mitgelöscht, // toIndex nicht. public Object set(int index, Object element) // Optional. Ersetzt das Element an der Stelle index mit element. public List subList(int fromIndex, int toIndex) // liefert Teil einer Liste fromIndex (einschließlich) bis toIndex (nicht mehr dabei) Abb.: Die abstrakte Klasse AbstractList AbstractSequentialList bereitet die Klasse LinkedList darauf vor, die Elemente in einer Liste zu verwalten und nicht wie ArrayList in einem internen Array. LinkedList realisiert die doppelt verkettete, lineare Liste und implementiert List. ArrayList implementiert die Liste als Feld von Elementen und implementiert List. Da ArrayList ein Feld ist, ist der Zugriff auf ein spezielles Element sehr schnell. Eine LinkedList muß aufwendiger durchsucht werden. Die verkettete Liste ist aber deutlich im Vorteil, wenn Elemente gelöscht oder eingefügt werden. 263 Der Aufruf einer optionalen Methode, die von der Subklasse nicht implementiert wird, führt zur UnsupportedOperationException. 408 Programmieren in Java << interface >> Collection << interface >> List LinkedList ArrayList Vector << Konstruktor >> << Konstruktor >> public LinkedList(); public ArrayList(); public LinkedList(Collection collection); public ArrayList(Collection collection); public ArrayList(int anfangsKapazitaet); << Methoden >> public void addFirst(Object object); public void addLast(Object object); public Object getFirst(); public Object getLast(); public Object removeFirst(); public Object removeLast(); << Methoden >> protected void removeRange (int fromIndex, int toIndex) // löscht Teil der Liste von // fromIndex bis toIndex. fromIndex wird // mitgelöscht, toIndex nicht. Abb. LinkedList, ArrayList, Vector Das Interface ListIterator << interface >> ListIterator public boolean hasPrevious(); // bestimmt, ob es vor der aktuellen Position ein weiteres Element gibt, der Zugriff ist mit // previous möglich public boolean hasNext(); public Object next(); public Object previous(); public int nextIndex(); public int previousIndex(); public void add(Object o); // Einfügen eines neuen Elements an der Stelle der Liste, die unmittelbar vor dem nächsten // Element des Iterators liegt public void set(Object o); // erlaubt, das durch den letzten Aufruf von next() bzw. previous() beschaffene Element zu // ersetzen public void remove(); Abb.: Das Interface ListIterator ListIterator ist eine Erweiterung von Iterator. Die Schnittstelle fügt noch Methoden hinzu, damit an aktueller Stelle auch Elemente eingefügt werden können. Mit einem ListIterator läßt sich rückwärts laufen und auf das vorgehende Element zugreifen. 409 Programmieren in Java 6.2.3 Behälterklassen des Typs Set Ein Set ist eine Menge, in der keine doppelten Einträge vorkommen können. Set hat die gleichen Methoden wie Collection. Standard-Implementierung für Set sind das unsortierte HashSet (Array mit veränderlicher Größe) und das sortierte TreeSet (Binärbaum). Bsp. import java.util.*; public class SetBeispiel { public static void main(String args []) { Set set = new HashSet(); set.add("Gerhard"); set.add("Thomas"); set.add("Michael"); set.add("Peter"); set.add("Christian"); set.add("Valentina"); System.out.println(set); Set sortedSet = new TreeSet(set); System.out.println(sortedSet); } } 410 Programmieren in Java << interface >> Collection << interface >> Set public boolean add(Object element); public boolean addAll(Collection collection); public void clear(); public boolean equals(Object object); public boolean contains(Object element); public boolean containsAll(Collection collection); public int hashCode(); public Iterator iterator(); public boolean remove(Object element); public boolean removeAll(Collection collection); public boolean retainAll(Collection collection); public int size(); public Object[] toArray(); public Object[] toArray(Object[] a); HashSet public HashSet(); public HashSet(Collection collection); public HashSet(int anfangskapazitaet); public HashSet(int anfangskapazitaet, float ladefaktor); << interface >> SortedSet public Object first(); public Object last(); public SortedSet headSet(Object toElement); public SortedSet subSet(Object fromElement, Object toElement); public SortedSet tailSet(Object fromElement); public Comparator comparator(); TreeSet264 public TreeSet() public TreeSet(Collection collection); public TreeSet(Comparator vergleich); public TreeSet(SortedSet collection); Abb.: 264 implementiert die sortierte Menge mit Hilfe der Klasse TreeMap, verwendet einen Red-Black-Tree als Datenstruktur 411 Programmieren in Java Die Schnittstelle Set ist eine im mathematischen Sinne definierte Menge von Objekten. Die Reihenfolge wird durch das Einfügen festgelegt. Wie von mathematischen Mengen bekannt, darf ein Set keine doppelten Elemente enthalten. Besondere Beachtung muß Objekten geschenkt werden, die ihren Wert nachträglich ändern. Die kann ein Set nicht kontrollieren. Eine Menge kann sich nicht selbst als Element enthalten. Zwei Klassen ergeben sich aus Set: die abstrakte Klasse AbstractSet und die konkrete Klasse HashSet. Die Schnittstelle SortedSet erweitert Set so, daß Elemente sortiert ausgelesen werden können. Das Sortierkriterium wird durch die Hilfsklasse Comparator gesetzt. 412 Programmieren in Java 6.2.4 Behälterklassen des Typs Map Ein Map ist eine Menge von Elementen, auf die über Schlüssel zugegriffen wird. Jedem Schlüssel (key) ist genau ein Wert (value) zugeordnet. StandardImplementierungem sind HashMap, HashTable und TreeMap. Interface Map, SortedMap und implemetierende Klassen << interface >> Collection << interface >> Map public void clear(); public boolean containsKey(Object key); public boolean containsValue(Object value); public Set entrySet(); public Object get(Object key); public boolean isEmpty(); public Set keySet(); public Object remove(Object key); public int size(); public Collection values(); HashMap public HashMap(); public HashMap(Map collection); public HashMap(int anfangskapazitaet); public HashMap(int anfangskapazitaet, int ladefaktor); << interface >> SortedMap public Comparator comparator(); public Object firstKey(); public Object lastKey(); public SortedMap headMap(Object toKey); public SortedMap subMap(Object fromKey, Object toKey); public SortedMap tailMap(Object fromKey); Hashtable TreeMap public TreeMap(); public TreeMap(Map collection); public TreeMap(Comparator vergleich); public TreeMap(SortedMap collection); Abb.: Die Schnittstelle Map 413 Programmieren in Java Eine Klasse, die Map implementiert, behandelt einen assoziativen Speicher. Dieser verbindet einen Schlüssel mit einem Wert. Die Klasse Hashtable erbt von Map. Map ist für die implementierenden Klassen AbstractMap, HashMap, Hashtable, RenderingHints, WeakHashMap und Attributes das, was die abstrakte Klasse Dictionary für die Klasse Hashtable ist. Die Schnittstelle SortedMap Eine Map kann mit Hilfe eines Kriteriums sortiert werden und nennt sich dann SortedMap. SortedMap erweitert direkt Map. Das Sortierkriterium wird mit einem speziellen Objekt, das sich Comparator nennt, gesetzt. Damit besitzt auch der assoziative Speicher über einen Iterator eine Reihenfolge. Nur die konkrete Klasse TreeMap implementiert bisher eine SortedMap. Die abstrakte Klasse AbstractMap implementiert die Schnittstelle Map. Die konkrete Klasse HashMap implementiert einen assoziativen Speicher, erweitert die Klasse AbstractMap und implementiert die Schnittstelle Map. Die konkrete Klasse TreeMap Erweitert AbstractMap und implementiert SortedMap. Ein Objekt von TreeMap hält Elemente in einem Baum sortiert. 414 Programmieren in Java 6.2.5 Algorithmen Die Wahl einer geeigneten Datenstruktur ist der erste Schritt. Im zweiten Schritt müssen die Algorithmen implementiert werden. Die Java Bibliothek hilft mit einigen Standardalgorithmen weiter. Dazu zählen Funktionen zum Sortieren und Suchen in Containern und das Füllen von Containern. Zum flexiblen Einsatz dieser Funktionen haben die Java-Entwickler die Klasse Collections bereitgestellt. Collections bietet Algorithmen statischer Funktionen an, die als Parameter ein Collection Objekt erwarten. Leider sind viele Algorithmen nur auf List Objekte definiert, z.B. public static void shuffle(List list) // würfelt die Werte einer Liste durcheinander Bsp. import java.util.*; public class VectorShuffle { public static void main(String args[]) { Vector v = new Vector(); for (int i = 0; i < 10; i++) v.add(new Integer(i)); Collections.shuffle(v); System.out.println(v); } } public static void shuffle(List list, Random rnd) // würfelt die werte der Liste durcheinander und benutzt dabei den Random Generator rnd. Nur die Methoden min() und max() arbeiten auf allgemeinen Collection-Objekten. 6.2.5.1 Datenmanipulation Daten umdrehen. Die Methode reverse() dreht die Werte einer Liste um. Die Laufzeit ist linear zu der Anzahl der Elemente. public static void reverse(List l) // dreht die Elemente in der Liste um Listen füllen. Mit der fill()-Methode läßt sich eine Liste in linearer Zeit belegen. Nützlich ist dies, wenn eine Liste mit Werten initialisiert werden muß. public static void fill (List l, Object o) // füllt eine Liste mit dem Element o Daten zwischen Listen kopieren. Die Methode copy(List quelle, List ziel) kopiert alle Elemente von quelle in die Liste ziel und überschreibt dabei Elemente, die evtl. an dieser Stelle liegen. public static void copy(List quelle, List ziel) // kopiert Elemente von quelle nach ziel. Ist ziel zu klein, gibt es eine IndexOutOfBoundsException 415 Programmieren in Java 6.2.5.2 Größter und kleinster Wert einer Collection Die Methoden min() und max() suchen das größte und kleinste Element einer Collection. Die Laufzeit ist linear zur Größe der Collection. Die Methoden machen keinen Unterschied, ob die Liste schon sortiert ist oder nicht. public static Object min(Collection c) // public static Object max(Collection c) /* Falls min() bzw. max() auf ein Collection-Objekt angewendet wird, erfolgt die Bestimmung des Minimums bzw. Maximums nach der Methode compareTo der Comparable Schnittstelle. Byte, Character, Double, File, Float, Long, Short, String, Integer, BigInteger, ObjectStreamField, Date und Calendar haben diese Schnittstelle implementiert. Lassen sich die Daten nicht vergleichen, dann gibt es eine ClassCastException */ public static Object min(Collection c, Comparator vergl) // public static Object max(Collection c, Comparator vergl) 6.2.5.3 Sortieren Die Collection Klasse bietet zwei sort() Methoden an, die die Elemente einer Liste stabil265 sortieren. Die Methode sort() sortiert die Elemente in ihrer natürlichen Ordnung, z.B.: - Zahlen nach der Größe (13 < 40) - Zeichenketten alphanumerisch (Juergen < Robert < Ulli) Eine zweite überladene Form von sort() arbeitet mit einem speziellen Comparator Objekt, das zwei Objekte mit der Methode compare() vergleicht. public static void sort(List liste) // sortiert die Liste public static void sort(List liste, Comparator c) // sortiert die Liste mit dem Comparator c Die Sortierfunktion arbeitet nur mit List-Objekten. „sort()“ gibt es aber auch in der Klasse Arrays. Bsp.: Das folgende Programm sortiert eine Reihe von Zeichenketten in aufsteigender Folge. Es nutzt die Methode Arrays.asList() zur Konstruktion einer Liste aus einem Array266. import java.util.*; public class CollectionsSortDemo { public static void main(String args[]) { String feld[] = { "Regina","Angela","Michaela","Maria","Josepha", "Amalia","Vera","Valentina","Daniela","Saida", 265 Stabile Sortieralgorithmen beachten die Reihenfolge von gleichen Elementen, z.B. beim Sortieren von Nachrichten in einem Email-Programm, zuerst nach dem Datum und anschließend nach dem Sender, soll die Liste innerhalb des Datum sortiert bleiben. 266 Leider gibt es keinen Konstruktor für ArrayList, der einen Array mit Zeichenketten zuläßt. 416 Programmieren in Java "Linda","Elisa" }; List l = Arrays.asList(feld); Collections.sort(l); System.out.println(l); } } Die Java Bibliothek bietet nicht viel zur Umwandlung von Feldern („Array“) in dynamische Datenstrukturen. Eine Ausnahme bildet die Hilfsklasse Arrays, die die Methode asList() anbietet. Die Behälterklassen ArrayList und LinkedList werden über asList() nicht unterstützt, d.h. Über asList() wird zwar eine interne Klasse ArrayList benutzt, die eine Erweiterung von AbstractList ist, aber nur das notwendigste implementiert. Sortieralgorithmus. Es handelt sich um einen optimierten „Merge-Sort“. Seine Laufzeit beträgt N ⋅ log(N ) . Die sort() Methode arbeitet mit der toArray() Funktion der Klasse List. Damit werden die Elemente der Liste in einem Feld (Array) abgelegt. Schließlich wird die sort() Methode der Klasse Arrays genutzt und mit einem ListIterator wieder in die Liste eingefügt. Daten in umgekehrter Reihenfolge sortieren. Das wird über ein spezielles Comparator-Objekt geregelt, das von Collections über die Methode reverseOrder() angefordert werden kann. Bsp.: import java.util.*; public class CollectionsReverseSortDemo { public static void main(String args[]) { Vector v = new Vector(); for (int i = 0; i < 10; i++) { v.add(new Double(Math.random())); } Comparator comparator = Collections.reverseOrder(); Collections.sort(v,comparator); System.out.println(v); } } Eine andere Möglichkeit für umgekehrt sortierte Listen besteht darin, erst die Liste mit sort() zu sortieren und anschließend mit reverse() umzudrehen. 417 Programmieren in Java 6.2.5.4 Suchen von Elementen Die Behälterklassen enthalten die Methode contains(), mit der sich Elemente suchen lassen. Für sortierte Listen gibt es eine wesentlich schnellere Suchmethode: binarySearch(): public static int binarySearch(List liste, Object key) // sucht ein Element in der Liste. Gibt die Position zurück oder ein Wert kleiner 0, // falls key nicht in der Liste ist. public static int binarySearch(List liste, Object key, Comparator c) // Sucht ein Element mit Hilfe des Comparator Objekts in der Liste. Gibt die Position zurück oder // einen Wert kleiner als 0, falls der key nicht in der Liste ist. Bsp.: Das folgende Programm sortiert (zufällig ermittelte) Daten und bestimmt Daten in Listen mit Hilfe der binären Suche. import java.util.*; public class ListSort { public static void main(String [] args) { final int GR = 20; // Verwenden einer natuerliche Ordnung List a = new ArrayList(); for (int i = 0; i < GR; i++) a.add(new VglClass((int)(Math.random() * 100))); Collections.sort(a); Object finde = a.get(GR / 2); int ort = Collections.binarySearch(a,finde); System.out.println("Ort von " + finde + " = " + ort); // Verwenden eines Comparator List b = new ArrayList(); // Bestimmt zufaellig Zeichenketten der Laenge 4 for (int i = 0; i < GR; i++) b.add(Felder.randString(4)); // Instanz fuer den Comparator AlphaVgl av = new AlphaVgl(); // Sortieren Collections.sort(b,av); // Binaere Suche finde = b.get(GR / 2); ort = Collections.binarySearch(b,finde,av); System.out.println(b); System.out.println("Ort von " + finde + " = " + ort); } } 418 Programmieren in Java 6.3 Die Klasse StringTokenizer Die StringTokenizer-Klasse unterstützt das Aufteilen eines Strings in Teilworte bzw. Zeichen, sog. Tokens. Tokens werden mit Hilfe von Trennzeichen erkannt, die ein Token vom anderen trennen. Ein Token ist eine Zeichenkette, in der keine Trennzeichen vorkommen. In der Regel werden Whitespaces267 als Trennzeichen verwendet. Der StringTokenizer ist nicht an bestimmte Trennzeichen gebunden, sie können vielmehr frei gewählt werden. Nur in der Voreinstellung sind Tabulator, Leerzeichen und Zeilentrenner die Delimiter. Konstruktor: public StringTokenizer(String str) Ein StringTokenizer mit Whitespaces als Trennzeichen wird erzeugt. Der zu untersuchende String ist str. Sollen andere Zeichen als Trennzeichen angegeben werden, so müssen diese ebenfalls an den Konstruktor übergeben werden: public StringTokenizer(String str, String delimiters) Methoden: public String nextToken() throws NoSuchElementException gibt das jeweils nächste Token an public boolean hasMoreTokens() gibt Auskunft darüber, ob es noch weitere Tokens gibt. public int countTokens() gibt Auskunft darüber, wie viele Tokens in einem String vorhanden sind. public String nextToken(String neueDelimiter) bewirkt nach einem Aufruf die Veränderung der Trennzeichen. Das Enumerations-Interface ist in die StringTokenizer-Klasse implementiert: public boolean hasMoreElements() { return hasMoreTokens(); } public Object nextElement() { return nextToken(); } Bsp.: Ausgabe der Worte eines Satzes String satz = "Dies ist ein Satz"; StringTokenizer tokenizer = new StringTokenizer(satz); while (tokenizer.hasMoreTokens()) { System.out.println(tokenizer.nextToken()); } 267 Zu den Whitespaces gehören Leerzeichen, Tabulator, Zeilenvorschub und Zeilenumbruch. Wird beim Erzeugen eines Tokenizer keine Trennzeichen-Zeichenkette angegeben, so werden Whitespaces als Trennzeichen angenommen. 419 Programmieren in Java 6.4 Die Klasse Random Konstruktoren Random() // Erzeugt einen neuen Zufallszahlengenerator. Der Seed268 wird auf die aktuelle Zeit gesetzt. Random(long seed) // Erzeugt einen neuen Zufallszahlengenerator und benutzt den Parameter als Seed. Methoden public void setSeed(long seed) // setzt den Zufallswert neu Die Random-Klasse kann Zufallszahlen in vier verschiedenen Datentypen erzeugen: public int nextInt() // liefert die nächste Zufallszahl public long nextLong() // liefert die nächste Zufallszahö public float nextFloat() // liefert die nächste Zufallszahl zwischen 0.0 und 1.0 public double nextDouble() erzeugt eine Zufallszahl im double-Format zwischen 0.0 und 1.0269. 268 Startwert für jede Zufallszahl ist ein 48 Bit Seed. Das Woer „Seed“ kommt vom englischen Wort für Samen und deutet an, daß es bei der Generierung von Zufalsszahlen wie bei Pflanzen einen Samen gibt, der zu Nachkommen führt. Aus diesem Startwert ermitteln sich anschließend die anderen Zahlen durch lineare Kongruenzen. Dadurch sind die Zahlen nicht wirklich zufällig, sie gehorchen einem mathematischen Verfahren 269 wird von Math.random() verwendet. 420 Programmieren in Java 6.5 Datumsberechnungen 6.5.1 Die Klassen Date, Calendar, GregorianCalendar Die Klasse Date Die Date-Klasse repräsentiert ein bestimmtes Datum bzw. einen bestimmten Zeitpunkt. Ausgangspunkt ist der 1. Januar 1970 0:00 h Greenwich-Zeit. Date << Konstruktoren >> public Date() // erzeugt eine Datums-Angabe und initialisiert es mit der Zeit, die bei der Erzeugung gelesen // wurde public Date(long d) // erzeugt ein Datums-Objekt und initialisiertes mit der angegebenen Anzahl von Millisekunden // seit dem 1. Januar 1970, 00:00:00 GMT << Methoden >> public long getTime() // liefert die Anzahl der Millisekunden seit dem 1. Januar 1970, 00:00:00 GMT zurück public void setTime(long t) // setzt die Anzahl Millisekunden des Datums-Objekts neu. Die akuelle Zeit kann über // today.setTime(new java.util.Date()) gesetzt werden. public boolean before(Date d) public boolean after(Date d) // testet, ob das eigene Datum vor / nach dem Datum des Parameters ist. „true“, wenn getTime() für // beide den gleichen Wert ergibt und der Parameter nicht null ist. public int compareTo(Date d) // vergleicht zwei Datum-Objekte und gibt 0 zurück, falls beide die gleiche Zeit repräsentieren. // public int compareTo(Object o) // Ist das übergebene Objekt vom Typ Date, dann verhält sich die Funktion wie das zuvor // angegebene compareTo(). Anderenfalls wirft die Methode eine ClassCastException ... Abb.: Die Klasse Date Bsp.270: import java.util.Date; class DatumundZeit { public static void main(String args[]) { System.out.println(new Date() ); } } 270 pr65100 421 Programmieren in Java Die Klasse Calendar (abstract class java.util.Calendar implements Serializable, Cloneable) Sie wird eingesetzt zur Konvertierung zwischen verschiedenen Datumsformaten. Die Klasse Calendar besitzt nur einige statisch Funktionen, z.B. getInstance(). Mit der Klassenmethode getInstance() bekommt man ein nutzbares Format: public static Calendar getInstance() liefert einen Calendar in der Ausprägung von GregorianCalendar mit der dargestellten Zeitzone und Lokalisierung. Die Klasse GregorianCalendar Die abstrakte Klasse Calendar wird durch die Klasse GregorianCalendar implementiert. In der gegenwärtigen Implementierung deckt die Klasse den Zeitraum von 4716 v. Chr. bis zum Jahre 5.000.000. GregorianCalendar << Konstruktoren >> public GregorianCalendar() // erzeugt ein standardmäßiges GregorianCalendar-Objekt mit der aktuellen Zeit in der // voreingestellten Zeitzone und Lokalisierung public GregorianCalendar(int jahr, int monat, int tag) // erzeugt ein GregorianCalendar-Objekt in der voreingestellten Zeitzone und Lokalisierung. Das // Datum wird durch jahr, monat (0 <= monat <= 11) und tag festgelegt public GregorianCalendar(int jahr, int monat, int tag, int stunde, int minute) public GregorianCalendar(int jahr, int monat, int tag, int stunde, int minute, int sekunde) << Methoden >> public final void set(int feld, int wert) // setzt das Feld feld mit dem Wert wert public final void set(int jahr, int monat, int tag) // setzt die Werte für Jahr, Monat, Tag public final void set(int jahr, int monat, int tag, int stunde, int minute) public final void set(int jahr, int monat, int tag, int stunde, int minute, int sekunde) public final int get(int feld) // liefert den Wert für das Feld feld ... Abb.: Die Klasse GregorianCalendar (class java.util.GregorianCalendar extends Calendar) Das Abfragen und Setzen von Datumselementen erfolgt mit den überladenen Methoden set() und get(). Beide erwarten als ersten Parameter einen Feldbezeichner. Er gibt an, auf welches Datum / Zeitfeld zugegriffen werden soll. Die get()-Methoden liefern den Inhalt des angegebenen Felds. Die set()-Methoden schreiben den als zweiten Parameter übergebenen Wert in das Feld. Die nachfolgende Tabelle zeigt eine Übersicht der Feldbezeichner und der Wertebereiche. 422 Programmieren in Java Feldbezeichner ERA YEAR MONTH DAY_OF_MONTH WEEK_OF_YEAR WEEK_OF_MONTH DAY_OF_YEAR DAY_OF_WEEK Minimalwert 0(BC) 1 0 1 1 1 1 1 Maximalwert 1(AD) 5000000 11 31 54 6 366 7 DAY_OF_WEEK_IN_MONTH HOUR HOUR_OF_DAY MINUTE SECOND MILLISECOND AM_PM ZONE_OFFSET -1 0 0 0 0 0 0 -12*60*60*1000 6 12 23 59 59 999 1 12*60*60*1000 DST_OFFSET 0 1*60*60*1000 Erklärung Datum vor oder nach Christi Jahr Monat Tag Woche Woche des Monats Tag des Jahres Tag der Woche (1 = Sonntag, 7 = Samstag Tag der Woche im Monat Stunde von 12 Stunde von 24 Minute Sekunden Millisekunden Vor 12, nach 12 Zeitzonenabweichnung in Millisekunden Sommerzeitabweichnung in Millisekunden Abb.: Konstanten aus der Klasse Calendar Bsp.: Ausgabe wichtiger Datumsfelder271 import java.util.*; public class DateDemo { public static void main(String args[]) { GregorianCalendar cal = new GregorianCalendar(); cal.setTimeZone(TimeZone.getTimeZone("ECT")); cal.setTime(cal.getTime()); printCalendar(cal); cal.set(Calendar.DATE,12); cal.set(Calendar.MONTH,Calendar.MARCH); cal.set(Calendar.YEAR,1973); printCalendar(cal); } public static void printCalendar(Calendar cal) { String wochenTag = (new String[]{"Sonntag", "Montag","Dienstag","Mittwoch","Donnerstag", "Freitag","Samstag"}) [cal.get(Calendar.DAY_OF_WEEK)]; System.out.println(wochenTag + ", " + cal.get(Calendar.DATE) + "." + (cal.get(Calendar.MONTH)+1) + "." + cal.get(Calendar.YEAR) + ", " + cal.get(Calendar.HOUR_OF_DAY) + ":" + o(cal.get(Calendar.MINUTE)) + ":" + o(cal.get(Calendar.SECOND)) + " und " + cal.get(Calendar.MILLISECOND) + " ms "); System.out.println("Es ist die " + cal.get(Calendar.WEEK_OF_YEAR) + ". Woche im Jahr und "); System.out.println(cal.get(Calendar.WEEK_OF_MONTH) + ". Woche im Monat\n"); } public static String o(int i) 271 pr65100 423 Programmieren in Java { return (i >= 10) ? Integer.toString(i) : "0" + i; } } Die Klasse TimeZone (abstract class java.util.TimeZone implements Serializable, Cloneable) Die Klasse TimeZone repräsentiert eine Zeitzone inklusive Zeitverschiebung und benutzt dazu folgende statische Methoden: public static TimeZone getDefault() // gibt die Zeitzone für die Umgebung zurück public static String[] getAvailableIDs() // liefert alle verfügbaren IDs public static TimeZone getTimeZone(String ID) // liefert die Zeitzone für eine gegebene ID Bsp.: Gib eine Aufzählung aller unterstützten Zeitzonen272 an. import java.util.*; public class AlleZonen { public static void main(String args[]) { String s[] = TimeZone.getAvailableIDs(); Arrays.sort(s); for (int i = 0; i < s.length; i++) System.out.println(s[i]); } } 272 Unter Windows mit dem JDK 1.3 sind dies 321 Zeitzonen, pr65100 424 Programmieren in Java 6.5.2 Formatieren der Datumsangaben Die Klasse DateFormat DateFormat << Methoden>> public final String format(Date d) // formatiert das Datum in einen Datum / Zeit-String public static final DateFormat getDateInstance() public static final DateFormat getTimeInstance() public static final DateFormat getDateTimeInstance() // liefert einen Datum/Zeit-Formatierer mit dem vorgegebenen stil aus der Standardumgebung public static final DateFormat getDateInstance(int dateStil) public static final DateFormat getTimeInstance(int stil) // liefert einen Datum/Zeit-Formatierer mit den Stil stil und der Standardsprache public static final DateFormat getDateInstance(int stil, Locale lokal273) public static final DateFormat getTimeInstance(int stil, Locale lokal) // liefert einen Datum/Zeit-Formatierer und der Sprache lokal public static final DateFormat getDateTimeInstance(int dateStil, int timeStil) // gibt einen Datum/Zeit-Formatierer für die gesetzte Sprache im angegebenen // Formatierungsstil zurück public static final DateFormat getDateTimeInstance(int dateStil, int timeStil, Locale lokal) // gibt ein Datum/Zeit-Formatierer für die Sprache lokal im angegebenen Formatierungsstil zurück public Date parse(String fs) throws ParseException // „parst“ ein Datum oder einen Zeit-String ... Abb.: Die Klasse DateFormat (abstract class java.text.DateFormat extends Format implements Cloneable, Serializable Die Klasse DateFormat bietet die Methoden getDateInstance(), getTimeInstance(), getDateTimeInstance() mit den Paramtern DateFormat.SHORT, DateFormat.MEDIUM, DateFormat.LONG, DateFormat.FULL an, die die Zeit bzw. das Datum auf vier verschiedene Arten formatieren: Konstante SHORT MEDIUM LONG FULL Beispiel für das Datum 29.9.01 29.9.2001 22. September 2001 Samstag, 22. September 2001 Beispiel für die Zeit 21:51 21:51:45 21:53:20 GMT+02:00 21:53 Uhr GMT+02:00 Abb.: Konstanten aus DateFormat Da DateFormat abstrakt ist, ist erst die aus DateFormat abgeleitete Klasse SimpleDateFormat einsatzbereit. Bsp. mit Sprachen274: Das folgende Programm erzeugt die Ausgabe für Deutschland und anschließend für Italien. 273 Unter Locale sind für einige Sprachen Konstanten vordefiniert: ENGLISH, FRENCH, GERMAN, ITALIAN, JAPANESE, KOREAN, CHINESE, SIMPLIFIED_CHINESE, TRADITIONAL_CHINESE 425 Programmieren in Java import java.util.*; import java.text.*; public class SimpleDateFormatierer { public static void main(String args[]) { Calendar cal; cal = new GregorianCalendar(TimeZone.getTimeZone("ECT") ); cal.setTime(cal.getTime()); DateFormat format; format = DateFormat.getDateTimeInstance( DateFormat.FULL, DateFormat.MEDIUM); System.out.println(format.format(cal.getTime() ) ); format = DateFormat.getDateTimeInstance( DateFormat.FULL, DateFormat.MEDIUM, Locale.ITALY); System.out.println(format.format(cal.getTime() ) ); } } Die Klasse SimpleDateFormat Zum Formatieren eines Datum-Objekts muß zunächst ein Objekt von SimpleDateFormat erzeugt werden. Dieses bekommt dann evtl. Formatierungsanweisungen (über eine andere Methode oder über einen weiteren Konstruktor) mit und formatiert dann mit der format() das Datum. SimpleDateFormat << Konstruktoren >> public SimpleDateFormat() // erzeugt eine neues SimpleDateFormat-Objekt in der eingestellten Sprache public SimpleDateFormat(String fs) // erzeugt ein SimpleDateFormat-Objekt mit dem vorgegebenen Formatierungsstring in der // voreingestellten Sprache public SimpleDateFormat(String fs, Locale loc) // erzeugt ein SimpleDateFormat-Objekt mit dem vorgebenen Formatierungsstring in der Sprache // loc. << Methoden >> public void applyPattern(String fs) // setze den Formatieruns-String public void applyLocalizedPattern(String pattern) // sertzt den Formatierungs-string für die gegebenen Sprache public String toPattern() // liefert den Formatierungsstring public String toLocalizedPattern() // liefert den übersetzten Formatierungsstring Abb.: Die Klasse DateFormat) SimpleDateFormat (class java.text.SimpleDateFormat extends Zur individuellen Anpassung der Ausgabe kann ein Formatierungsstring angegeben werden. Diese Formatierungsanweisung wird entweder dem Konstruktor übergeben oder kann nachträglich mit der Methode applyPattern() geändert werden. 274 vgl. pr65200 426 Programmieren in Java Symbol G Y M MM MMM MMMM D H H M S S E EEEE D F W W A K K Z ‘ ‘' Bedeutung Ära Jahr Monat im Jahr Monat im Jahr m. Null Monat im Jahr (kurz) Monat im Jahr (lang) Tag im Monat Stunde (1-12) Stunde am Tag (0-23) Minute der Stunde Sekunde der Minute Millisekunde Tag der Woche (kurz) Tag der Woche (lang) Tag im Jahr Tag der Woche i. Mon. Woche im Jahr Woche im Monat am/pm-Text Stunde am Tg (1-24) Stunde (0-11) Zeitzone Zeichen für Text Einzelnes Hochkomma Präsentation Text Nummer Nummer Nummer Text Text Nummer Nummer Nummer Nummer Nummer Nummer Text Text Nummer Nummer Nummer Beispiel AD 1998 7 07 Sep September 26 9 0 13 22 257 Mi Mittwoch 304 3 13 Text Nummer Nummer Text Trennzeichen Literal AM 24 0 GMT+02:00 ‘ Abb.: Symbole im Formatstring zur Steuerung der Ausgabe 6.6 Formatieren mit Format-Objekten Zahlen, Datumsangaben und Text können auf verschiedene Art und weise formatiert werden. Unter Java wird das Formatierungsverhalten in einer abstrakten Klasse Format (abstract class java.text.Format implements Serializable, Cloneable) fixiert. Format stellt die Methoden format() und parseObject() bereit. Im JDK erweitern drei Klassen Format: DateFormat, MessageFormat und NumberFormat. Sie übernehmen die Ein-/Ausgabe für das Datum, allgemeine Botschaften (Nachrichten) Zahlen. Jede der Klassen implementiert die Methoden zur Ausgabe format() und zum Erkennen parseObject(). 427 Programmieren in Java 7. Ein- und Ausgabe In Java werden Ein-, Ausgabeoperationen über sog. Datenströme275 realisiert. Es stehen zur Behandlung des Datenstrommodells zwei abstrakte Klassen zur Verfügung: InputStream und OutputStream. Die beiden Klassen gehören zu dem Paket java.io276 , das bei jedem Programm mit Ein-/Ausgabeoperationen importiert werden muß. Einfache Strom-Methoden erlauben nur das Versenden von Bytes mit Hilfe von Datenströmen277. Ein nicht interpretierbarer Bytestrom kann von jeder beliebigen Quelle kommen. Quelle bzw. Ziel des Stroms sind völlig verschiedene Erzeuger bzw. Verbraucher von Bytes. Allgemeine Methoden, die von jeder beliebigen Quelle lesen können, akzeptieren ein Stromargument zur Bezeichnung der Quelle. Allgemeine Methoden zum Schreiben akzeptieren einen Strom zur Zielbestimmung. Filter haben zwei Stromargumente. Sie lesen vom ersten, verarbeiten die Daten und Schreiben ins zweite. Zum Senden/Empfangen verschiedener Datentypen gibt es die Schnittstellen DataInput und DataOutput. Sie legen Methoden zum Senden/Empfangen anderer Java-Datentypen fest. Mit Hilfe der Schnittstellen ObjectInput und ObjectOutput lassen sich ganze Objekte über einen Strom senden. Alle Methoden, die sich mit Ein- und Ausgabeoperationen beschäftigen, werden in der Regel mit throws IOException abgesichert. Diese Subklasse von Exception enthält alle potentiellen I/O-Fehler, die bei der Verwendung von Datenströmen auftreten können. Die I/O-Exceptions können direkt mit einem try-catch-Block aufgefangen oder an übergeordnete Methoden weitergegeben werden. 275 Der Begriff Strom kommt aus dem Betriebssystem Unix und bezieht sich auf „Pipes“. Eine Pipe ist ein nicht interprtierbarer Strom von Bytes, der zur Kommunikation zwischen Programmen (bzw. „gegabelten Kopien“ eines Programms) oder zum Lesen und Schreiben von verschiedenen Geräten und Dateien benutzt wird. Ein Strom ist ein Kommunikationspfad zwischen der Quelle und dem Ziel eines Informationsblocks. 276 Die Klasse java.io enthält eine große Anzahl von Klassen zur Ein-/Ausgabe. Die meisten dieser Klassen leiten sich von InputStream bzw. OutputStream ab. 277 Ein Strom von Bytes kann mit einem Wasserstrom verglichen werden. Wird aus einem Strom Wasser entnommen, dann wird er als Eingabestrom benutzt. Wird in einen Strom Wasser geschüttet, dann wird er als Ausgabestrom verwendet. Die Verbindung von Strömen kann mit dem Verbinden von Wasserschläuchen verglichen werden. 428 Programmieren in Java ByteArrayInputStream FileInputStream DataInput BufferedInputStream DataInputStream FilterInputStream InputStream Object PipedInputStream SequenceInputStream StringBufferedInputStream LineNumberInputStream PushbackInputStream RandomAccessFile ByteArrayOutputStream FileOutputStream OutputStream FilterOutputStream DataOutput PipedOutputStream Throwable Exception IOException Abb.: java.io 429 BufferedOutputStream DataOutputStream PrintStream EOFException FileNotFoundException InterruptedIOException UTFDataFormatException Programmieren in Java 7.1 Die abstrakten Klassen InputStream und OutputStream 7.1.1 InputStream Aufgabenbeschreibung Mit dieser Klasse können Leseoperationen eines Bytestroms verwirklicht werden. Woher die Bytes kommen und wie sie befördert werden, spielt keine Rolle. Sie müssen dem einlesenden Objekt nur zur Verfügung stehen. Die Aufgabe der Klasse InputStream ist die Repräsentation von Klassen, die Eingaben aus verschiedenen Quellen produzieren. Klasse Funktion ByteArrayInputStream Ein Puffer im arbeitsspeicher wird ald Eingabestrom benutzt. StringBufferInputStream Konvertiert einen String in einen InputStream FileInputStream Dient zum Lesen aus einer Datei PipedInputStream Produziert Daten für den PipedOutputStream (implentiert das „Pipe“-Konzept. SequenceInputStream Konvertiert zwei oder mehrere „InputStream“-Objekte in einen einzelnen InputStream. Abb.: Eingabestrom-Typen Quellen zu einem Eingabestrom können sein: 1. Ein Array von Bytes 2. Ein Zeichenketten-Objekt 3. Eine Datei 4. Eine „Pipe“ 430 Argument für den Konstruktor Nutzungsmöglichkeit Der Puffer aus dem Bytes geholt werden. Als Datenquelle. Verbunden mit einem FileInputStream-Objekt kann daraus ein nützliches Interface gestaltet werden. Ein String. Die zugrundeliegende Implementierung benutzt einen StringBuffer. Als Datenquelle. Verbunden mit einem FiIeInputStream-Objekt kann daraus ein nützliches Interface gestaltet werden. Ein String der den Dateinamen enthält oder ein File- bzw. FileDescriptor-Objekt Als Datenquelle. Verbunden mit einem FilterInputStream-Objekt kann daraus ein nützliches Interface gestaltet werden. PipedOutputStream. Als Datenquelle im Multithreading. Verbunden mit einem FilterInputStream-Objekt kann daraus ein nützliches Interface gestaltet werden. Zwei InputStream-Objekte oder eine Enumeration für einen Container von InputStreamObjekten. Als Datenquelle. Verbunden mit einem FilterInputStream-Objekt kann daraus ein nützliches Interface gestaltet werden. Programmieren in Java 5. Eine Folge von Strömen, die zu einem umfassenden, einzelnen Strom gesammelt werden können. 6. Anders beschaffene Quellen, z.B. eine Internet-Verbindung Die „read“-Methoden Zum Einlesen von Datenströmen gibt es die „read“-Methode. Die einfachste Form ist: public abstract int read() throws IOException. Ein einzelnes Byte wird aus dem Eingabestrom gelesen und ausgegeben. Falls der Bytestrom das Ende erreicht hat, wird „-1“278 ausgageben. Die Methode führt ein blockierendes Lesen aus, d.h.: Es wird auf Daten gewartet, falls sie nicht unmittelbar zur Verfügung stehen. Die Methode public int read(byte[] bytes) throws IOException füllt ein Datenfeld mit Bytes, die aus einem Strom gelesen wurden und gibt die Anzahl Bytes zurück. Bei Bedarf (z.B. im Datenstrom sind nicht genügend Bytes vorhanden) werden weniger Bytes gelesen, als das Datenfeld aufnehmen kann. public int read(byte[] bytes, int offset, int length) throws IOException füllt ein Datenfeld ab Position „offset“ mit bis zu length Bytes aus dem Strom. Es wird entweder die Anzahl der gelesenen Bytes oder –1 für das Dateiende ausgegeben. Weitere nützliche Methoden public int available throws IOException Allen „read“-Methoden ist gemeinsam: Sie warten auf das Ende aller angeforderten Eingaben. Es kann dabei zu längeren Blockaden beim Einlesen von Daten kommen. Die Methode „available“ gibt die Anzahl Bytes zurück, die ohne Blockieren gelesen werden kann. public long skip(long n) throws IOException Die „skip“-Methode benutzt „read“ zum Überspringen und blockiert deshalb unter den gleichen Bedingungen wie „read“. Die gibt die Anzahl Bytes zurück, die sie übersprungen hat, oder „-1“, falls das Ende des Stroms erreicht wurde public boolean markSupported() Die Methode gibt „true“ zurück, falls der Sttrom Markierungen unterstützt. public void mark(int readLimit) Die Methode markiert die aktuelle Position im Strom für eine spätere Rückkehr. public void reset() throws IOException Mit dieser Methode kann der Strom auf die Markierung zurücksetzen. public void close() throws IOException Diese Methode schließt die Verarbeitung von einem Strom. Die meisten Ströme werden automatisch bei der Garbage Collection geschlossen. 278 Rückgabe „-1“ bedeutet zum Unterschied von C nicht, daß ein Fehler aufgetreten ist. 431 Programmieren in Java 7.1.2 OutputStream Aufgabenbeschreibung Der Ausgabestrom ist ein Empfänger von Daten. Man findet Ausgabeströme fast nur in Verbindung mit Eingabeströmen. Die Aufgabe der Klasse OutputStream ist die Repräsentation von Klassen zur Festlegung der Datensenke. Das kann ein „Array von Bytes“, eine Datei oder eine „Pipe“ sein. Klasse Funktion ByteArrayOutputStream Kreiert einen Arbeits-speicherPuffer. Alle vom Strom gesendeten Daten werden hier plaziert. FileOutputStream Zum Schreiben einer Datei PipedOutputStream Information zum Schreiben ist Eingabe für den assoziierten PipedInputStream Argumente für den Konstruktur Nutzungsmöglichkeit Optional: zu initialisierende Puffergröße Zum Bestimmen des Datenziels. Verbunden mit einem FilterOutputStream-Objekt kann daraus ein nützliches Interface gestaltet werden. Eine Zeichenkette, die den Namen der Datei repräsentiert , bzw. ein Fileoder FileDescriptor-Objekt. Zum Bestimmen eines Datenziels. Verbunden mit einem FileOutputStream-Objekt kann daraus ein nützliches Interface gestaltet werden. PipedInputStream Zum Bestimmen des datenziels für „Multithreading“. Verbunden mit einem FilterOutputStreamObjekt kann daraus ein nützliches Interface gestaltet werden. Abb.: Ausgabestrom-Typen Die „write“-Methoden public abstract void write(int b) throws IOException Die Methode schreibt ein einzelnes Byte in den Ausgabestrom. public void write(byte[] bytes) throws IOException Die Methode schreibt den Inhalt des Datenfelds bytes in den Ausgabestrom. public void write(byte[] bytes, int offset, int length) throws IOException Die Methode schreibt „length“ Bytes ab Position „offset“ aus dem Datenfeld bytes. public void flush() throws IOException Diese Methode leert einen Ausgabestrom public void close() throws IOException Auch Ausgabeströme sollten am Ende einer Verarbeitung geschlossen werden. 432 Programmieren in Java 7.2 Gefilterte Ströme Die Klassen FilterInputStream und FilterOutputStream zum Verknüpfen von Strömen. Diese Klassen ermöglichen das Verknüpfen von Strömen, keine neuen Methoden. Der große Vorteil liegt in der Verbindung mit einem anderen Strom. Die Konstruktoren für den FilterInputStream und den FilterOutputStream haben deshalb InputStream- und OutputStream-Objekte als Parameter: protected FilterInputStream(InputStream ein) public FilterOutpuStream(OutputStream aus) Nützliche Subklassen von FilterInputStream sind: Klasse Funktion DataInputStream Ermöglicht das Einlesen von primitiven Typen BufferedInputStream Gepuffertes Einlesen LineNumberInputStream Verfolgt die zeilennummer im Eingabestrom. Die Methode getLineNumber() und setLineNumber(int) können verwendet werden Hat einen Byte großen InputStream „Pushback“-Puffer, das letzte Braucht der Java-Compiler gelesene Zeichen kann in den strom zurückgelegt werden. PushbackInputStream Konstruktor-Argumente Nutzungsmöglichkeit InputStream Enthält ein Interface für das lesen primitiver Typen InputStream mit optionale Angabe der Puffergröße InputStream Fügt eine Zeilennummerierung hinzu Abb.: „FilterInputStream“-Typen Nützliche Subklassen von FilterOutputStream sind: Klasse DataOutputStream PrintStream BufferedOutputStream Funktion Konstruktor-Argumente Nutzungsmöglichkeit Ausgabe primitiver Typen OutputStream Erlaubt das Schreiben primitiver Typen Produziert formatierte Ausgabe. OutputStream Formatierte Ausgabe Gepufferte Ausgabe, der Puffer OutputStream kann mit flush() geleert werden Gepufferte Ausgabe Abb.: „FilterOutputStream“-Typen Gepufferte Ströme Gepufferte Ströme werden mit der Klasse BufferedInputStream für die Eingabe und BufferedOutputStream für die Ausgabe realisiert: public BufferedInputStream(InputStream ein) public BufferedInputStream(InputStream ein, int puffergroesse) public BufferedOutputStream(OutputStream aus) public BufferedOutputStream(OutputStream aus, int puffergroesse) 433 Programmieren in Java Die Klasse BufferedInputStream implementiert die vollen Fähigkeiten der InputStream-Methoden, besitzt aber zusätzlich einen gepufferten Byte-Array (Cache für weitere Leseoperationen). Die Klasse BufferedOutputStream implementiert die vollen Fähigkeiten der OutputStream-Methoden, besitzt aber zusätzlich einen gepufferten Byte-Array. Die BufferedInputStream-Klasse versucht mit einem einzigen read-Aufruf soviel Daten wie möglich in ihre Puffer einzulesen. Die BufferedOutputStream-Klasse ruft nur dann die write-Methode auf, wenn ihr Puffer voll ist oder wenn flush aufgerufen wird. Datenströme Die Filter DataInputStream und DataOutputStream sind nützliche Filter des java.io-Pakets. Sie ermöglichen Schreiben und Lesen primitiver Typen in Java auf maschinenunabhängige Weise. DataInputStream implementiert eine DataInput-Schnittstelle. Diese Schnittstelle definiert Methoden zum Lesen von primitiven Datentypen in Java (sowie noch ein paar weitere Methoden). DataOutputStream implementiert eine DataOutput-Schnittstelle mit AusgabeMethoden, die den Eingabe-Methoden der DataInput-Schnittstelle entsprechen. Die Konstruktoren zu den DataInputStream- und DataOutputStream-Klassen sind Stromfilter-Konstruktoren, die den zu filternden Strom als Parameter verwenden: public DataInputStream (InputStream ein) public DataOutputStream(OutputStream aus) Die Schnittstelle DataInput. Sie enthält Methoden zum Lesen primitiver Typen: public public public public public public public public public public public boolean readBoolean() throws EOFException, IOException byte readByte() throws EOFException, IOException int readUnsignedByte() throws EOFException, IOException char readChar() throws EOFException, IOException short readShort() throws EOFException, IOException int readUnsignedShort() throws EOFException, IOException int readInt() throws EOFException, IOException long readLong() throws EOFException, IOException float readFloat() throws EOFException, IOException double readDouble() throws EOFException, IOException String readUTF() throws EOFException, IOException279, UTFDataFormatException Ausgeworfene Ausnahmen sind vom Typ IOException oder EOFException. EOFException wird ausgeworfen, wenn das Ende des Stroms erreicht wird. Die Ausnahme läßt sich überall einsetzen, wo bisher auf „-1“ überprüft wurde. Zum Lesen einer durch Zeilenumschaltung (\r, \n) begrenzten Zeile aus einer Textdateigibt es die Methode: public String readLine() throws IOException. „\r, \n, \r\n“ werden entfernt, bevor die Zeile als Zeichenkette wiedergegeben wird. Bsp.: Einlesen von der Standardeingabe und Ausgabe 279 UTF (Unicode Transmission Format) ist ein spezielles Format zum Kodieren von 16-Bit-Unicode-Werten 434 Programmieren in Java Mit der Methode public int skipBytes(int anzahlBytes) wird eine Anzahl Bytes übersprungen. Die Schnittstelle DataOutput. Duch diese Schnittstelle sind folgende Methoden zur Ausgabe primitiver Typen definiert: public public public public public public public public void void void void void void void void writeBoolean(boolean b) throws IOException writeByte(int b) throws IOException writeChar(int c) throws IOException writeShort(int c) throws IOException writeInt(int i) throws IOException writeFloat(float f) throws IOException writeDouble(double d) throws IOException writeUTF(String s) throws IOException Mit den Methoden public void writeBytes(String s) throws IOException public void writeChars(String s) throws IOException kann eine Zeichenkette als eine Reihe von Bytes oder Zeichen abgelegt werden. Die PrintStream-Klasse Der „System.out“-Strom mit den Methoden „System.out.print“ bzw. „System.out.println“ ist eine Instanz von PrintStream. „System.in“ ist ein InputStream. Erstellen eines „PrintStream“-Objekts. Das PrintStream-Objekt muß in einen existierenden Ausgabestrom angehangen werden. Über einen optionalen Parameter kann automatisch die Methode flush() aufgerufen werden: public PrintStream(OutputStream aus) public PrintStream(OutputStream aus, boolean autoFlush) Methode für die Ausgabe von Objekten. public public public public public public public public public public public public public public public public public public public public void void void void void void void void void void void void void void void void void void void void flush() close() write(int b) print(Object o) print(String s) print(boolean c) print(char[] puffer) print(char c) print(int i) print(float f) print(double d) println(Object f) println(String s) println(boolean c) println(char[] puffer) println(char c) println(int i) println(float f) println(double d) println() 435 Programmieren in Java 7.3 Die Klasse File Ein File-Objekt kann sich auf eine Datei oder ein Verzeichnis beziehen und läßt sich auf verschiedene Arten erstellen. public File(String pfadname) erstellt eine File-Instanz, die in pfadname angegeben wurde. public File(String pfadname, String dateiname) erstellt eine File-Instanz, die sich aus dem in dateiname angegebenen Dateinamen und dem in pfadnamen aungegebenen Pfad zusammensetzt. public File(File verzeichnis, String dateiname) erstellt eine File-Instanz, die sich aus dem in dateiname angegebenen Dateinamen und dem in verzeichnis angegebenen Verzeichnis zusammensetzt. Die Klasse File (class java.io.File implements Serializable, Comparable) umfaßt Methoden zum Bearbeiten von Dateien bzw. Verzeichnissen. Nachdem ein File-Objekt erzeugt wurde, können Methoden zum Zugriff auf die einzelnen Bestandteile des Dateinamens aufgerufen werden: public String getName(); // liefert den Namen der Datei public String getPath(); // liefert den kompletten Namen (Datei oder Verzeichnis) public String getAbsolutePath(); // liefert den absoluten Pfadnamen für das File-Objekt public String getParent(); Die Methoden public boolean isFile() public boolean isDirectory() überprüfen, ob eine Datei oder ein Verzeichnis vorliegt. Mit public boolean canRead() public boolean canWrite() wird Lese- bzw. Schreiberlaubnis überprüft. Die Methode public long lastModified() zeigt an, wann die Datei bzw. das Verzeichnis geändert wurde. Mit public boolean exists() wird die physikalische Existenz einer Datei oder eines Verzeichnisses überprüft. Über public String getName() wird der Name einer Datei bzw. eines Verzeichnisses ermittelt. Wurde ein File-Objekt für ein Verzeichnis konstruiert, stehen folgende Methoden zur Verfügung: public String[] list(); liefert einen Array von Strings, der für jeden gefundenen Verzeichniseintrag ein Element enthält. Die Liste enthält den Namen aller Dateien und Unterverzeichnisse mit Ausnahme von „.“ und „..“. Die einfache Funktion list() liefert nur relative 436 Programmieren in Java Pfade (Dateiname oder Verzeichnisname). Komplette File-Objekte beschafft die Methode listFiles), die ihre vollständige Pfadangabe kennen. Der vollständige Pfad kann dann mit getName() erfragt werden. In einer Variante von list() kann die Auswahl der Verzeichniseinträge eingeschränkt werden. Es muß aber dann ein Objekt übergeben werden, das das Interface FilenameFilter implementiert. Dieses besitzt die Methode accept280, die für jede gefundene Datei aufgerufen wird und entscheidet, ob sie in die Liste aufgenommen werden soll oder nicht. Zusätzlich gibt es die statische Methode listRoots, mit der eine Liste der Wurzeln verfügbarer Dateisysteme beschafft werden kann. public static File[] listRoots() liefert die verfügbaren Wurzeln der Dateisysteme oder null, falls diese nicht festgestellt werden konnten. Neben dem Zugriff auf Verzeichniseinträge gibt es Methoden281 für Löschen, Umbenennen von Dateien bzw. Verzeichnissen und für das Neuanlegen von Verzeichnissen. public boolean mkdir() // Anlegen des spezifizierten Verzeichnisses public boolean mkdirs() // auch "Vater"-Verzeichnisse werden automatisch angelegt. public boolean renameTo(File ziel) public boolean delete(); // löscht die durch das File-Objekt bezeichnete Datei Da es bei URL-Objekten häufig vorkommt, daß eine Datei die Basis ist, wurde ab Version 1.2 von Java die Methode toURL() aufgenommen: public URL toURL() throws MalformedURLException // liefert ein URL-Objekt vom File-Objekt Es muß ein File-Objekt erzeugt werden. Anschließend erzeugt toURL() ein URLObjekt, das das Protokoll „file“ trägt und danach eine absolute Pfadangabe zur Datei bzw. zum Verzeichnis enthält. 280 public boolean accept(File verzeichnis, String dateiName) Alle Methoden geben true zurück, wenn sie ihre Aufgabe erfolgreich ausführen konnten; anderenfalls geben sie false zurück 281 437 Programmieren in Java 7.4 Die RandomAccessFile-Klasse Für den Zugriff auf Random-Access-Dateien stellt das Paket java.io die Klasse RandomAccessFile zur Verfügung. Es kann nur auf Dateien zugegriffen werden, auch das durch die „Streams“ realisierte Filter-Konzept gibt es in Random-AccessDateien nicht. Öffnen, Neuanlegen und Schließen. Das Öffnen von Random-Access-Dateien erfolgt mit den Konstruktoren: public RandomAccessFile(File datei, String mode) throws FileNotFoundException Bei der Übergabe des File-Objekts wird die durch dieses Objekt spezifizierte Datei geöffnet. public RandomAccessFile(String name, String mode) throws FileNotFoundException Bei der Übergabe des String-Parametres „name“ wird die Datei mit diesem Namen geöffnet. Der zweite Parameter mode bestimmt die Art des Zugriffs: - „r“: Öffnen nur zum Lesen - „rw“: Öffnen zum Schreiben und Lesen. Ein reiner Schreibmodus wird nicht unterstützt. Es gibt in der Klasse RandomAccessFile keine explizite Differenzierung zwischen Öffnen der Datei und Neuanlegen. Implizit gilt: Eine Datei wird neu angelegt, wenn beim Öffnen im Modus „w“ nicht vorhanden ist. Existiert die Datei bereits, wird sie unverändert geöffnet, und es gibt keine Möglichkeit, ihren Inhalt zu löschen oder die Dateilänge auf einen bestimmten Wert zusetzen. Das Schliessen erfolgt durch Aufruf der parameterlosen Methode close. Positionieren des Dateizeigers. Jeder Schreib- und Lesezugriff erfolgt an der Position, die durch den aktuellen Inhalt des Satzzeigers bestimmt wird und positioniert den Zeiger um die Anzahl gelesener bzw. geschriebener Bytes weiter. Die Klasse RandomAccessFile stellt eine Reihe von Methoden zum Zugriff auf den Satzzeiger zur Verfügung. Die Methode seek. Die RandomAccessFile-Klasse besitzt alle Methoden, die in den Schnittstellen DataInput und DataOutput verfügbar sind. Mit public void seek(long dateiPosition) throws IOException kann man an jede beliebige Position der Datei springen. Mit der Methode public long getFilePointer() throws IOException kann die derzeitige Dateiposition282 bestimmt werden. Weiterhin stehen lesende und schreibende Zugriffsmethoden zur Verfügung. 282 Der Dateipositionswert in den Methoden seek und getFilePointer() ist die Anzahl der Bytes vom Anfang bis zum Ende der Datei. 438 Programmieren in Java RandomAccessFile <<Konstruktoren>> public RandomAccessFile(File datei, String zugriffsmodus) throws FileNotFoundException; // erzeugt einen Random-Access-Datei-Strom vom File-Objekt. // Ob aus der datei gelesen oder geschrieben wird, bestimmt // ein String, der den Modus angibt: „r“, „w“ oder „rw“. public RandomAccessFile(String datei, String zugriffsmodus) throws FileNotFoundException; << Methoden >> public long getFilePointer() throws IOException; publc void seek(long pos) throws IOException; // setzt den Dateizeiger – bezogen auf den Dateianfang – auf eine // bestimmte Stelle, an der der nächste Lese- und Schreibzugriff // stattfindet public int skipBytes(int n) throws IOException; public long length() throws IOException; public final boolean readBoolean() throws IOException; public final byte readByte() throws IOException; public final char readChar() throws IOException; public final double readDouble() throws IOException; public final float readFloat() throws IOException; public final int readInt() throws IOException; public final long readLong() throws IOException; public final short readShort() throws IOException; public final String readUTF() throws IOException; public final void readFully(byte b[]) throws IOException; public final void readFully(byte[], int off, int laenge) throws IOException; public final String readLine() throws IOException; public final int readUnsignedByte() throws IOException; public final int readUnsignedShort() throws IOException; public int read() throws IOException; public int read(byte b[]) throws IOException; public int read(byte b[], int off, int laenge) throws IOException; public final void writeBoolean(boolean w) throws IOException; public final void writeByte(int w) throws IOException; public final void writeBytes(String s) throws IOException; public final void writeChar(int w) throws IOException; public final void writeChars(String s) throws IOException; public final void writeDouble(double w) throws IOException; public final void writeFloat(float w) throws IOException; public final void writeInt(int w) throws IOException; public final void writeLong(long w) throws IOException; public final void writeShort(int w) throws IOException; public final void writeUTF(String str) throws IOException; public void write(int b) throws IOException; public void write(byte b[]) throws IOException; public void write(byte b[], int off, int laenge) throws IOException; Abb.: Die Klasse RandomAccessFile 439 Programmieren in Java 7.5 Die Klasse StreamTokenizer Die Klasse StreamTokenizer arbeitet im Gegensatz zum StringTokenizer aus dem Paket java.util auf einem Datenstrom, genauer auf einem Reader. Sie beachtet keine Unicode-Eingabe, sondern nur Zeichen aus dem Bereich von \u0000 bid \u00FF. Wahrend des Parsens werden bestimmte Merkmale aus dem Text erkannt, so u. a. Identifier (etwa Schlüsselworte), Zahlen, Strings in Anführungszeichen und verschiedene Kommentararten (C-Stil oder C++-Stil). Der Erkennungsvorgang wird anhand einer Syntaxtabelle überprüft. Diese Tabelle enthält bspw. die Zeichen, die ein Schlüsselwort identifizieren oder die Zeichen, die Trennzeichen sind. Jedes gelesene Zeichen wird dann keinem, einem oder mehreren Attributen zugeordnet. Diese Attribute fallen in die Kategorie Trennzeichen, alphanumerische Zeichen, Zahlen, Hochkomma bzw. Anführungszeichen. Zur Benutzung der Klasse wird zunächst ein StreamTokenizer-Objekt erzeugt und dann die Syntaxtabellen initialisiert. Das Überlesen von Kommentarzeilen wird durch st.slashSlashComments(true); st.slashStarComments(true); // Kommentar /* Kommentart */ gesteuert. Die erste Methode überliest im Eingabestrom alle Zeichen bis zum Return. Die zweite Methode überliest alles bis zum Stern / Slash. Beim Lesen des Datenstroms mit nextToken() kann über bestimmte Flags erfragt werden, ob im Stream ein Wort (TT_WORD) eine Zahl (TT_NUMBER), das Ende der Datei (TT_EOF) oder das Ende der Zeile (TT_EOL) vorliegt. Wichtig ist eolIsSignificant(true) zu setzen, da andernfalls der StreamTokenizer nie ein TT_EOL findet. Wurde ein Wort erkannt, dann werden alle Zeichen in Kleinbuchstaben konvertiert. 440 Programmieren in Java class java.io.StreamTokenizer << Konstruktor >> StreamTokenizer(Reader r) << Methoden >> public void resetSyntax // Reinitialisiert die Syntaxtabelle des Tokenizers, so dass kein Zeichen eine Soderstellung // geniesst. public void wordChars(int low, int hi) // Zeichen im Bereich von low bis hi werden als Trennzeichen erkannt public void whitespaceChars(int low, int hi) // Zeichen im Bereich von low bis hi werden als Trennzeichen erkannt. public void ordinaryChars(int low, int hi) // Zeichen im Bereich von low bis hi geniessen keine Sonderbehandlung, werden als // normale zeichen behandelt. public void ordinaryChar(int ch) // Das Zeichen besitzt keine zusätzliche Funktion, ist bspw. kein Kommentarzeichen, // trennsymbol oder Nummernzeichen public void parseNumbers() // Nummern sollen vom Tokenizer erkannt werden. In der Syntaxtabelle gelten die 12 // Zeichen 0, 1, 2, 3, 4, 5, 6., 7, 8, 9, ., - als numerisch. Liegt ein Gleipunktzahl an, // so wird der Vorkommateil in nval abgelegt und das Token ergibt im Attribut ttype // den Wert TT_NUMBER public void commentChar(int ch) // Gibt das Zeichen an, das einen einzeiligen Kommentar einleitet public void slashStarComments(boolean flag) // Der Tokenizer soll Kommentare im C-Stil (/* .. */) erkennen oder nicht public void slashSlashComments(boolean flag) // Der Tokenizer soll Kommentare im C++-Stil (// ..) erkennen oder nicht public void lowerCaseMode(boolean fl) // liegt in ttype ein Token vom Typ TT_WORD vor, so wird das automatisch in // Kleinschreibweise konvertiert, falls fi gleich true ist public int nextToken() throws IOException // liefert das nächste Token im Strom. Der Typ des Token wird im Attribut ttype hinterlegt. // Zusätzliche Informationen befinden sich im Attribut nval (Nummer) oder sval (Zeichenkette). // In der Regel wird solange geparst, bis das Token TT_EOF zurückgegeben wird. public void pushBack() // Der Aufruf von nextToken() liefert den aktuellen wert im Attribut ttype und ändert nval oder sval // nicht public int lineno() // liefert die Zeilennummer Abb.: Die Klasse StreamTokenizer 441 Programmieren in Java 7.6 Klassen für spezielle nützliche Ströme Die LineInputStream-Klasse Die SequenceInputStream-Klasse Die PushbackInputStream-Klasse 442 Programmieren in Java 7.7 Java 1.1 IO-Ströme 7.7.1 Grundlagen Bis zur Version 1.0 des JDK gab es nur Byte-Streams in Java. Das ergab Schwierigkeiten bei der Umwandlung zwischen Bytes und 16 Bit langen UnicodeZeichen, die innerhalb von Java benutzt werden. Im JDK 1.1. wurden daher „Character“-Streams auf der Basis von 16 Bit langen Unicode-Zeichen eingeführt. Die Kompatibilität wurde durch Brückenklassen gewährleistet, die „Character“Streams in „Byte“-Streams überführen (und umgekehrt). Quellen und Senken: „Java 1.0“-Klassen InputStream OutputStream FileInputStream FileOutputStream StringBufferInputStream ByteArrayInputStream ByteArrayOutputStream PipedInputStream PipedOutputStream Korrespondierende Klassen in Java 1.1 Reader Konverter: InputStreamReader Writer Konverter: OutputStreamWriter FileReader FileWriter StringReader StringWriter CharArrayReader CharArrayWriter PipedReader PipedWriter Abb. Wichtige Klassen zur Eingabe In Filterklassen kann nur eine etwas gröbere Zuordnung von Klassen aus Java 1.0 zu Java 1.1 angegeben werden. So ist „BufferedOutputStream“ eine Subklasse von FilterOutputStream, BufferedWriter ist dagegen keine Subklasse von FilterWriter. Filter: Klassen in Java 1.0 FilterInputStream FilterOutputStream BufferedInputStream BufferedOutputStream DataInputStream PrintStream LineNumberInputStream StreamTokenizer PushbackInputStream Korrespondierende Klassen in Java 1.1 FilterReader FilterWriter (abstrakte Klasse ohne Subklassen) BufferedReader (mit readLine) BufferedWriter DataInputStream, bei readLine() BufferedReader verwenden PrintWriter LineNumberReader StreamTokenizer PushbackReader Abb. Wichtige Klassen zur Ausgabe Einige Klassen bleiben von der Umstellung unberührt: DataOutputStream, File, RandomAccessFile, SequenceInputStream. 443 Programmieren in Java 7.7.2 Die abstrakte Klassen Reader und ihre Ableitungen Basis aller sequentiellen Eingaben ist die abstrakte Klasse Reader, die eine Schnittstelle für streambasierte Eingaben zur Verfügung stellt: public abstract class java.io.Reader { protected Reader(); public abstract void close() throws IOException; // schliesst den Eingabestrom public void mark(int readAheadlimit) throws IOException; // markiert eine bestimmte Position innerhalb eines Eingabestroms public boolean markSupported(); // überprüft, ob Markieren unterstützt wird public int read() throws IOException; // liest das nächste Zeichen aus dem Eingabestrom und liefert es als // "int". Der Rückgabewert –1 kennzeichnet das Ende des Eingabestroms public int read(char cbuf[]) throws IOException; // übernimmt eine Reihe von Zeichen in den als Parameter angegebenen // Array public abstract int read(char cbuf[], int off, int len) throws IOException; // übernimmt eine Reihe von zeichen in einen Bereich des Puffers, der // durch Versatz (Offset) und die gewünschte Länge gekennzeichnet ist public long skip(long n) throws IOException; // überliest Zeichen im Eingabestrom public boolean ready() throws IOException; // liefert True, falls der nächste Aufruf von read erfolgen kann, ohne // daß die Eingabe beendet ist public void reset() throws IOException; // setzt den Lesezeiger an die markierte Stelle zurück } Von der abstrakten Basisklasse Reader können keine Instanzen abgeleitet werden. Es gibt eine Reihe aus Reader abgeleitete Klassen, die die Verbindung zur konkreten Eingabe bzw. zum konkreten Eingabegerät herstellen. Klasse InputStreamReader FileReader PushbackReader BufferedReader LineNumberReader StringReader CharArrayReader PipedReader Bedeutung Abstrakte Klasse für alle Reader, die einen ByteStrom in einen Character-Strom umwandeln Konkrete Ableitung von InputStreamReader zum Einlesen einer Datei Eingabefilter mit der Möglichkeit zur Rückgabe von Zeichen Reader zur Eingabepufferung und zum Lesen kompletter Zeilen Ableitung von BufferedReader mit der Fähigkeit zum Zählen von Zeilen Reader zum Einlesen von Zeichen aus einem String Reader zum Einlesen von Zeichen aus einem Zeichen-Array Reader zum Einlesen von Zeichen aus einem PipedWriter Abb.: Von Reader abgeleitete Klassen InputStreamReader Diese Klasse konvertiert Byte- in Character-Streams. 444 Programmieren in Java FileReader Sie ermöglicht die Eingabe einer Datei. Die Klasse FileReader geht direkt aus einem InputStreamReader hervor. Konstruktoren. Mit ihnen ermöglicht FileReader das Öffnen von Dateien: public FileReader(String dateiName) throws FileNotFoundException; Bei Übergabe der Zeichenkette dateiName wird die Datei mit dem angegebenen Namen zum Lesen geöffnet. Falls sie nicht vorhanden ist, kommt es zur Ausnahme des Typs FileNotFoundException public FileReader(File datei) throws FileNotFoundException; erwartet ein File-Objekt zur Spezifikation einer zu öffnenden Datei. public FileReader(FileDescriptor fd); erwartet ein File-Deskriptor-Objekt, das eine bereits geöffnete Datei angibt. StringReader Diese Klasse erlaubt das Lesen von Zeichen aus einem String. CharArrayReader Diese Klasse erlaubt das Lesen von Zeichen aus einen „Zeichen-Array“ BufferedReader Diese Klasse dient zur Pufferung von Eingaben. BufferedReader implementiert die vollen Fähigkeiten der Methoden von Reader. Diese Klasse verwendet dazu gepufferte Zeichen-Arrays. Konstruktoren: public BufferedReader(Reader ein) public BufferedReader(Reader ein, int gr) Der erste Parameter ist ein Reader-Objekt, auf das ein BufferedReader aufgesetzt werden soll. Der optionale Parameter gr gibt die Größe des internen Puffer an. Fehlt er, so wird eine für die meisten Situationen angemessene Standardeinstellung verwendet. Zeilenweises Lesen: public String readLine throws IOException. Rückgabewert ist ein String mit dem Zeicheninhalt (ohne Begrenzungszeichen) oder „null“, falls das Ende von „Stream“ erreicht wurde. LineNumberReader Diese Klasse ist eine Ableitung von BufferedReader, die zusätzlich noch die Anzahl der Eingabezeichen beim Einlesen zählen kann. Mit „public int getLineNumber()“ wird der aktuelle Stand des Zeilenzählers abgefragt. Mit public void setLineNumber(int LineNumber) kann der aktuelle Stand des Zeilenzählers verändert werden. 445 Programmieren in Java 7.7.3 Die abstrakte Klasse Writer und ihre Ableitungen Basis aller sequentiellen Eingaben ist die Klasse Writer, die eine Schnittstelle für „stream“-basierte Ausgaben zur Verügung stellt: public abstract class java.io.Writer { protected Writer(); // Oeffnen und Vorbereiten des Ausgabestroms public abstract void close() throws IOException; // Schliessen des Ausgabestroms public abstract void flush() throws IOException; // Leeren der Ausgabe von einem gepufferten Cache public void write(int c); // Grundform mit einem einzigen Ausgabeparameter vom typ int, // der als Byte in den Ausgabestrom geschrieben wird public void write(char einPuffer[]) throws IOException; // Varianten von write, die ein Array von Bytes oder ein String-Objekt // erwarten und dieses durch Aufruf von write() ausgeben public abstract void write(char einPuffer[], int off, int len) throws IOException; public void write(String str) throws IOException; public void write(String str, int off, int len) throws IOException; } Von der abstrakten Basisklasse Writer können keine Instanzen abgeleitet werden. Es gibt eine Reihe aus „Writer“ abgeleitete Klassen, die die Verbindung zur konkreten Ausgabe(gerät) herstellen: Klasse OutputStreamWriter FileWriter FilterWriter PrintWriter BufferedWriter StringWriter CharArrayWriter PipedWriter Bedeutung Abstrakte Basisklasse für alle Writer, die einen Character-Stream in einen Byte-Stream umwandeln Konkrete Ableitung von OutputStreamWriter zur Ausgabe einer Datei Abstrakte Basisklasse für die Konstruktion von Ausgabefiltern Ausgabe aller Basistypen im Textformat Writer mit Ausgabepufferung Writer zur Ausgabe in einem String Writer zur Ausgabe im Zeichen-Array Writer zur Ausgabe in einem PipedReader Abb.: Aus Writer abgeleitete Klassen Von den abgeleiteten Klassen wird erwartet: Überlagerung der Methoden flush(), close() und write(char einPuffer[], int offset, int laenge). Zun Schachteln von Ausgabe-Streams gibt es die aus Writer abgeleiteten Klassen: BufferedWriter, PrintWriter und FilterWriter. Falls die "write"-Methode eines derart geschachtelten "Writer" aufgerufen wird, gibt sie Daten nicht mehr direkt an den internen "Writer", sondern führt Filterfunktionen aus. OutputStreamWriter Diese Klasse übernimmt eine Konvertierung zwischen Character- und Byte-Streams. 446 Programmieren in Java FileWriter ermöglicht die Ausgabe in eine Datei. Sie implementiert die abstrakten Eigenschaften von Writer. Die Klasse FileWriter bietet 4 Konstruktoren für das Öffnen von Dateien an: public FileWriter(String dateiName) throws IOException Falls dateiName eine bereits vorhandene Datei bezeichnet, wird sie geöffnet und ihr bisheriger Inhalt gelöscht, anderenfalls wird eine neue Datei mit diesem Namen angelegt. Sukzessive Aufrufe von write schreiben dann in diese Datei. public FileWriter(String dateiName, boolean append) throws IOException Wird der Parameter append mit dem Wert true an den Konstruktor übergeben, dann werden die Ausgabezeichen an die Datei angehängt, falls sie bereits existiert. public FileWriter (File datei) throws IOException public FileWriter (FileDescriptor fd) Parameter ist hier ein File-Objekt bzw. ein FileDescriptor-Objekt Die Klasse StringWriter Sie besitzt zwei Konstruktoren: public StringWriter() bestimmt einen StringWriter in der Standardgröße eines StringBuffer-Objekts. Der interne Puffer wächst automatisch, falls fortgesetzte Aufrufe von „write“ das eroderlich machen public StringWriter(int initialGroesse) Zugriffe: Die schreibenden Zugriffe auf die Puffer erfolgen mit den von Writer bekannten „write“-Methoden. Für den Zugriff auf den Inhalt des Puffers gibt es die Methoden public StringBuffer getBuffer() und public String toString(). CharArrayWriter Sie schreibt Zeichen in ein Character-Array, das automatisch bei fortgesetzten Aufrufen von „write“ erweitert wird. Die Klasse besitzt zwei Konstruktoren: public CharArrayWriter() public CharArrayWriter(int initialGroesse) Zugriffe: Schreibende Zugriffe erfolgen mit den von „Writer“ bekannten „write“Methoden. Für den Zugriff auf das Array gibt es die Methoden public String toString() und public char[] toCharArray(). Mit der Methode public void reset() wird der interne Puffer geleert. Die Größe des Zeichen-Array wird über public int size() ermittelt. Durch Aufruf von public void writeTo(Writer aus) throws IOException kann der Inhalt des Array an einen anderen Writer übergeben werden und so mit einem einzigen Funktionsaufruf in eine Datei geschrieben werden. BufferedWriter Die Klasse puffert „Stream“-Ausgaben über einen internen Puffer, in dem die Ausgaben von write zwischengespeichert werden. Falls der Puffer gefüllt ist oder die Methode flush aufgerufen wird, werden alle gepufferten Ausgaben in den „Stream“ geschrieben. Das Puffern der Ausgabe ist immer dann nützlich, wenn die Ausgabe in eine Datei geschreiben werden soll. BufferedWriter besitzt zwei Konstruktoren: public BufferedWriter(Writer aus) public BufferedWriter(Writer aus, int groesse) 447 Programmieren in Java In beiden Fällen wird ein existierender Writer übergeben, an den alle gepufferten Ausgaben weitergereicht werden. Ist die Größe des Puffers nicht explizit angegeben, legt BufferedWriter einen Standardpuffer an. Die „write“-Methoden von BufferedWriter entsprechen denen der Klasse Writer. Zusätzlich gibt es eine Methode newLine mit der eine Zeileumschaltung in den Stream geschrieben werden kann. PrintWriter Diese Klasse stellt Ausgabemethoden für alle primitiven Datentypen und für Objektgruppen zur Verfügung. Konstruktoren. public PrintWriter(Writer aus) instanziert ein PrintWriter-Objekt durch Übergabe eines Writer-Objekts, auf das die Ausgabe umgelenkt werden soll. public PrintWriter(Writer aus, boolean autoflush) Mit dem Parameter autoflush wird angegeben, ob nach der Ausgabe „einer Zeilenschaltung“ automatisch die Methode flush() aufgerufen werden soll. Ausgabe primitiver Typen (und Objekttypen): Sie wird realisiert über eine Reihe überladener Methoden mit dem Namen „print“. Zusätzlich gibt es alle Methoden in der Variante println, bei der automatisch an das Ende der Ausgabe ein Zeilenvorschub (Zeilenumschaltung) angehängt wird. Println() existiert auch parameterlos zur Ausgabe einer einzelnen Zeilenumschaltung. public public public public public public public public public void void void void void void void void void print(boolean b) print(char z) print(char s[]) print(double d) print(float f) print(int i) print(long l) print(Object obj) print(String s) 448 Programmieren in Java 7.7.4 Demonstrationsprogramm zur Ein-/ Ausgabe ab Java Version 1.1 Das Demonstrationsprogramm soll folgende Ein-/Ausgabe zeigen: 1. Zeilenweises Lesen 2. Eingabe vom Speicher 3. Formatierte Speicher-Eingabe 4. Zeilennummerierung und Dateiausgabe 5. Speichern und Wiedergewinnen von Daten import java.io.*; public class EinAusDemo { public static void main(String[] args) { try { // 1. Zeilenweises Lesen BufferedReader ein1 = new BufferedReader( new FileReader(args[0])); String s1, s2 = new String(); while ((s1 = ein1.readLine()) != null) s2 += s1 + '\n'; ein1.close(); // Lesen Standardeingabe BufferedReader stdin = new BufferedReader( new InputStreamReader(System.in)); System.out.print("Gib eine Zeile an: "); System.out.println(stdin.readLine()); // 2. Eingabe vom Speicher StringReader ein2 = new StringReader(s2); int zch; while ((zch=ein2.read()) != -1) System.out.print((char) zch); // 3. Formatierte Speicher-eingabe try { DataInputStream ein3 = new DataInputStream( new StringBufferInputStream(s2)); while (true) System.out.print((char) ein3.readByte()); } catch(EOFException e) { System.out.println("Ende des Stroms"); } // 4. Zeilennummerierung und Datei-Ausgabe try { LineNumberReader zn = new LineNumberReader( new StringReader(s2)); // BufferedReader ein4 = new BufferedReader(zn); PrintWriter aus1 = new PrintWriter( new BufferedWriter( new FileWriter("EinAusDemo.aus"))); while ((s1 = zn.readLine()) != null) aus1.println("/* " + zn.getLineNumber() + " */" + s1); // System.out.println("/* " + zn.getLineNumber() + " */" + s1); aus1.close(); } catch(EOFException e) { System.out.println("Ende des Stroms"); 449 Programmieren in Java } // 5. Speichern und Wiedergewinnen von Daten try { DataOutputStream aus2 = new DataOutputStream( new BufferedOutputStream( new FileOutputStream("Daten.txt"))); aus2.writeDouble(3.14159); aus2.writeBytes("Das war Pi"); aus2.close(); DataInputStream ein5 = new DataInputStream( new BufferedInputStream( new FileInputStream("Daten.txt"))); BufferedReader ein5br = new BufferedReader( new InputStreamReader(ein5)); System.out.println(ein5.readDouble()); System.out.println(ein5br.readLine()); } catch(EOFException e) { System.out.println("Ende des Stroms"); } } catch(FileNotFoundException e) { System.out.println("Datei nicht gefunden: " + args[1]); } catch(IOException e) { System.out.println("IO Exception"); } } } 450 Programmieren in Java 8. Serialisierung 8.1 Grundlagen Serialisierung283 ist die Fähigkeit ein Objekt, das im Hauptspeicher der Anwendung existiert, in ein Format zu konvertieren, das es erlaubt, das Objekt in eine Datei zu schreiben oder über eine Netzwerkverbindung zu transportieren. Auch der umgekehrte Weg gehört dazu: Rekonstruktion eines in serialisierter Form vorliegenden Objekts in das interne Format der laufenden Java-Maschine. 8.1.1 Das Interface Serializable Serialisierbare Objekte können in Dateien gespeichert oder über Netzwerke übertragen werden. Dazu müssen sie in ein Byteformat umgewandelt werden können. Das Markierungsinterface Serializable kennzeichnet eine Klasse als serialisierbar. Ähnlich wie bei Cloneable sind dazu keine Methoden zu implementieren, es müssen aber auch alle referenzierenden Klassen serialisierbar sein. Falls dies nicht der Fall ist, wird eine NotSerializableException erzeugt. Voraussetzung für das Serialisieren ist die Implementierung der Schnittstelle Serializable. Das Interface enthält keine Implementierung, sondern dient nur dazu, durch die Implementierungs-Hierarchie die Fähigkeit zum Schreiben anzuzeigen. 283 häufig auch mit dem Begriff Persistenz gleichgesetzt. Persistenz bezeichnet das dauerhafte Speichern von Daten auf einem externen Datenträger, so daß sie auch nach dem Beenden des Programms erhalten bleiben. 451 Programmieren in Java 8.1.2 Die Klasse ObjectOutputStream Zur Serialisierung eines Objekts ( - oder allgemeiner Daten bzw. Primitive - ) benötigt man einen OutputStream. Am besten eignet sich ein FileOutputStream dafür, da meistens die Daten in einer Datei284 gesichert werden sollen. FileOutputStream erweitert die Klasse OutputStream. Eine Verbindung zwischen Datei und ObjektStrom wird durch die Klasse ObjectOutputStream (class java.io.ObjectOutputStream extends OutputStream implements DataOutput, ObjectOutput, ObjectStreamConstants) geschaffen. ObjectOutputStream implementiert das Interface ObjectOutput, z.B. die Funktion writeObject() zum Schreiben von Objekten. OutputStream { abstract } ObjectOutputStream << Konstruktoren > public ObjectOutputStream(OutputStream out) throws IOException // erzeugt einen ObjectOutputStream, der in den angegebenen OutputStream // schreibt << Methoden >> public final void writeObject(Object obj) throws IOException // schreibt das Objekt. Die implementierende Klasse weiß, wie // das Objekt zu schreiben ist public void write(int b) throws IOException // Ein Bayte wird geschrieben public void write(byte b[]) throws IOException // schreibt ein Array von Bytes ... public void writeBytes(String daten) throws IOException; public void writeChars(String daten) throws IOException; public void writeByte(int daten) throws IOException; public void writeShort(int daten) throws IOException; public void writeChar(int daten) throws IOException; public void writeInt(int daten) throws IOException; public void writeLong(long daten) throws IOException; public void writeFloat(float daten) throws IOException; public void writeDouble(double daten) throws IOException; ... public void flush() throws IOException // Noch gepufferte Daten werden geschrieben public void close() throws IOException // Stream wird geschlossen. ... Abb.: Die Klasse OblectOutputStream einschl. einiger Methoden des Interface ObjectOutput 284 Der Dateiname wird häufig so gewählt, daß er mit dem Präfix „ser“ endet. 452 Programmieren in Java Die Methode public final void writeObject(Object obj) throws IOException schreibt folgende Daten in den OutputStream: - Die Klasse des als Argument übergebenen Objekts - Die Signatur der Klasse - Alle nicht statischen, nicht transienten Membervariablen des übergebenen Objekts einschl. der aus Elternklassen geerbten Membervariablen. Bsp.: Serialisierung eines „Zeit-“ Objekts. Das Zeit-Objekt ist eine Instanz der Klasse „Zeit“, die eine Uhrzeit (Stunde, Minute) kapselt. import java.io.*; public class Zeit implements Serializable { private int stunde; private int minute; public Zeit(int stunde, int minute) { this.stunde = stunde; this.minute = minute; } public String toString() { return stunde + ":" + minute; } } Mit Hilfe eines Objekts vom Typ ObjectOutputStream kann ein Time-Objekt serialisiert werden. import java.io.*; import java.util.*; public class Pr81100 { public static void main(String args[]) { try { FileOutputStream fs = new FileOutputStream("test1.ser"); ObjectOutputStream os = new ObjectOutputStream(fs); Zeit z = new Zeit(10,20); os.writeObject(z); os.close(); } catch(IOException e) { System.err.println(e.toString()); } } } Nach dem Schließen des Streams steht das serialisierte Objekt in „test1.ser“. 453 Programmieren in Java 8.1.3 Die Klasse ObjectInputStream An einen Eingebestrom kommt man über einen InputStream. Da die Daten häufig aus einer Datei kommen ist dies häufig ein FileInputStream, der mit einem ObjectInputStream verknüpft wird. Die Methode readObject() liest das Objekt, findet heraus, was für ein Typ es ist und holt, falls nötig, auch noch Objekte, auf die verwiesen wird. InputStream { abstract } ObjectInputStream << Konstruktoren > public ObjectInputStream(InputStream in) throws IOException, StreamCorruptedException << Methoden >> public final Object readObject() throws OptionalDataException ClassNotFoundException, IOException // Liest ein Objekt und gibt es zurück public boolean readBoolean() throws IOException public byte readByte(i) throws IOException public short readShort() throws IOException public char readChar() throws IOException public int readInt() throws IOException public long readLong() throws IOException public float readFloat() throws IOException public double readDouble() throws IOException . ... Abb.: Die Klasse ObjectInputStream Analog zum Interface ObjectOutput gibt es hier das Interface ObjectInput (interface java.io.ObjectInput extends DataInput) 454 Programmieren in Java << interface >> ObjectInput << Methoden >> public Object readObject() throws ClassNotFoundException, IOException public int read() throws IOException // liest ein byte aus dem datenstrom. Dieses ist –1, wenn das Ende erreicht ist public int read(byte b[]) throws IOException // liest ein Array in den Puffer. Das Ende wird durch –1 angezeigt public int read(byte b[], int off, int laenge) throws IOException // liest ein Array von Bytes in den Puffer b an die Stelle off genau len Bytes public long skip (long n) throws IOException // überspringt n Bytes im Eingabestrom. public int available() throws IOException // Gibt die Anzahl der Zeichen zurück, die ohne Blockade gelesen werden public void close() throws IOException // schließt den Eingabestrom ... Abb.: Das Interface ObjectInput Das Deserialisieren kann mann sich etwa so vorstellen: 1. Anlegen einen neuen Objekts des zu serialisierenden Typs, Vorbelegen der Membervariablen mit Defaultwerten, Aufruf des Defaultkonstruktor der ersten nicht serialisierbaren Superklasse. 2. Lesen der serialsierten Daten und Zuweisen der Daten zu den entsprechenden Membervariablen des angelegten Objekts. Das durch Deserialisieren erzeugte Objekt hat anschließend dieselbe Struktur und denselben Zustand, den das serialsierte Objekt hatte285. Da der Rückgabewert von readObject() vom Typ Object ist, muß das erzeugte Objekt in den tatsächlichen Typ (oder eine seiner Oberklassen) umgewandelt werden. Bsp.: Deserialisieren des im vorherigen Beispiel serialisierten und in die Datei „test1.ser“ geschriebenen Zeit-Objekts. import java.io.*; import java.util.*; public class Pr81101 { public static void main(String args[]) { try { FileInputStream fs = new FileInputStream("test1.ser"); ObjectInputStream is = new ObjectInputStream(fs); Zeit z = (Zeit) is.readObject(); System.out.println(z.toString()); is.close(); } catch(ClassNotFoundException e) { System.err.println(e.toString()); } catch(IOException e) { System.err.println(e.toString()); } } } 285 Abgesehen von den nicht serialsierten Membervariablen des Typs static oder transient. 455 Programmieren in Java Beim Deserialisieren von Objekten können einige Fehler passieren. Damit ein Aufruf von readObject() erfolgreich ist, müssen mehrere Kriterien erfüllt sein: - das nächste Element des Eingabestroms ist tatsächlich ein Objekt (kein primitiver Typ). - Das Objekt muß vollständig und fehlerfrei aus der Eingabedatei lesen lasse. - Es muß eine Konvertierung auf den gewünschten Typ erlauben, also entweder zu derselben oder einer daraus abgeleiteten Klasse gehören. - Der Bytecode für die Klasse des zu serialsierenden Objekts muß vorhanden sein. Er wird beim Serialisieren nicht mitgespeichert, sondern muß dem Empfängerstrom wie üblich als kompilierter Bytecode zur Verfügung stehen. - Die Klasseninformation des serialisierten Objekts und die im deserialisierenden Programm als Bytecode vorhandene Klasse müssen zueinander kompatibel sein. 8.2 Tiefe Objektkopien Klassen könen die Methode clone() überschreiben und so eine Kopie der Werte liefern. Die Standarimplementierung ist jedoch so festgelegt, daß die Kopie flach ist. Das bedeutet: Referenzen auf Objekte, die vom klonenden Objekt ausgehen, werden beibehalten und diese Objekte nicht extra kopiert. Eine tiefe Kopie kann folgendermaßen erzeugt werden: Das zu klonende Objekt ist zu serialisieren und dann wieder auszupacken. Die zu klonenden Objekte müssen nur das Serializable-Interface implementieren. 456 Programmieren in Java 9. Netzwerkprogrammierung Das Paket java.net enthält Klassen zur Programmierung von TCP/IP286Netzwerkzugriffen. Dieses (Internet-) Protokoll stellt die Basis aller Java-Netzzugriffe dar. Darauf aufbauend gibt es weiter entwickelte Netzwerkprotokolle wie RMI für entfernte Methodenaufrufe und verteilte Anwendungen und Enterprise JavaBeans für verteilte Objekte. 9.1 Adressen, Ressourcen und URLs 9.1.1 Die Klasse InetAddress Zur Adressierung von Rechnern im Netz wird die Klasse InetAddress des Pakets java.net verwendet. Eine InetAddress enthält sowohl eine IP-Adresse287 als auch den symbolischen Namen des jeweiligen Rechners288. Die beiden Bestandteile können mit den Methoden getHostName() und getHostAddress() abgefragt werden. Mit Hilfe von getAddress() kann die IP-Adresse auch direkt als „byte“Array (mit 4 Elementen) beschafft werden. Zur Generierung eines InetAddress-Objekts stehen zur Verfügung: public static InetAddress getByName(String host) throws UnknownHostException // erwartet einen String mit der IP-Adtesse oder dem Namen als Argument public static InetAddress getLocalHost() throws UnknownHostException // liefert ein InetAdress-Objekt für den eigenen Rechner Das folgende Programm289 ermittelt zu einer IP-Adresse den symbolischen Namen des zufehörigen Rechners bzw. zum symbolischen Namen eine IP-Adresse, import java.net.*; public class WerBinIch { public static void main(String args[]) { if (args.length != 1) { System.err.println("Verwendung: java WerBinIch <host>"); System.exit(1); } try { // Hole die angeforderte Adresse 286 Als Protokoll bezeichnet man die Menge aller Regeln, die nötig sind, um einen kontrollierten und eindeutigen Verbindungsaufbau, Datenaustausch und Verbindungsabbau gewährleisten zu können. Die derzeit in Java verfügbareb Netzwerkfähigkeiten basieren auf den Internet-Protokollen TCP/IP (bzw. TCP/UDP) 287 IP steht für Internet Protocol. Die 32-Bit-lange IP-Adresse besteht aus einer Netzwerk-ID und einer Host-ID. Die Host-ID gibt die Bezeichnung des Rechners innerhalb seines eigenen Netzwerks an, die Netwerk-ID liefert die Bezeichnung des Rechners. 288 Anstelle der IP-Adresse können bei Anwendungsprotokollen symbolische Namen verwendet werden. Sie werden mit Hilfe von Namen-Servern in die zugehörige IP-Adresse übersetzt , bevor die Verbindung aufgebaut wird. Das Domain Name System (DNS) ordnet numerischen IP-Adressen sprechende Namen zu. 289 vgl. pr91100 457 Programmieren in Java InetAddress addr = InetAddress.getByName(args[0]); System.out.println(addr.getHostName()); System.out.println(addr.getHostAddress()); } catch(Exception e) { System.err.println(e.toString()); System.exit(1); } } } 9.1.2 Die Klasse URL Im WWW werden Ressourcen über URLs (Universal Ressource Locator) identifiziert. Eine URL besteht aus: einem Protokollnamen, z.B. http290 (HTML), file (lokale Dteien), ftp (Dateitransfer), rmi (Remote Method Invocation), inop(Inter ORB Protocol) oder jdbc (Java Database Connectivity), dem ein Doppelpunkt und zwei Schrägstriche folgen. Nach dem Doppel-Slash kommt die Angabe: //user291:password292@host293:port294/url-path295. Einige Teile können bei einer URL ausgelassen werden. So sind „user:password@“, „password“, „port“ und „/url-path“ optional. Sind Benutzername und Paßwort gegeben, so folgt ein „At“-Zeichen @. Paßwort und Benutzername durfen nicht Doppelpunkt, At-Zeichen oder Slash enthalten. Fehlt die Angabe des Rechners, wird der aktuelle Rechner (localhost) benutzt, bei Ports bekannte Standardnummern. Schließlich folgt eine Bezeichnung der Ressource, typischerweise unter Angabe eines Pfads. Java implementiert das Konzept eines Uniform Resource Locator durch eine eigene Klasse URL, die sich im Paket java.net befindet. Aus einer gegebenen Zeichenreihe erstellt Java einen geeigneten URL, den Man zum Einrichten einer URLConnection nutzen kann. Die Verbindung ermöglicht eine Interaktion, die vom Protokoll der betreffenden Ressource definiert wird. Erzeugen von URL-Objekten. Am einfachsten ist es, über eine String-Repräsentation der URL-Adresse zu gehen, z.B.: URL fhURL = new URL("http://www.fhregensburg.de/"); Diese URL wurde mit dem Konstruktor public URL(String urlAddr) throws MalformedURLException erzeugt. Ein anderer Konstruktor ist 290 Mit dem Hypertext Tranfer Protocol wird auf Inhalte des Web zugegriffen. Die URL für Dienste im Web beginnt mit http. 291 Optionaler Benutzername 292 optionales Paßwort, ein Paßwort ohne Benutzername kann nicht angegeben werden 293 Auf die Angabe des Protokolls folgt in der Regel der Name der Domäne oder die IP-Adresse des Servers. Name und IP-Adresse sind in der Regel gleichwertig, da von einem besonderen Dienst der Name in eine IPAdresse umgesetzt wird. 294 Eine Verbindung zu einem Rechner geschieht immer durch eine Art Tür, die Port genannt wird. Die PortNummer läßt den Server die Dienste kategorisieren. Jeder Dienst bekommt eine andere Portnummer, damit sie sich unterscheiden lassen. Normalerweise horcht der HTTP-Server auf Port 80. 295 Auf den Servernamen folgt die Angabe der Datei, auf die über HTTP oder FTP zugegriffen werden soll. Da sie in einem Verzeichnis liegt, beschreibt url-path den Weg zur Datei. Ist keine Datei vorhanden und endet die Angabe der URL mit einem Slash „/“, dann versucht der Web-Server auf eine der Dateien index.html bzw. index.htm zuzugreifen. 458 Programmieren in Java public URL(URL urlObj, String … ) throws MalformedURLException // erzeugt relativ zur URL ein neues URL-Objekt. Bsp.: Zugiff auf die Homepage der FH Regensburg import java.net.*; import java.io.*; public class OeffneURLStrom { public static void main(String args[]) { try { String s; URL fhURL = new URL("http://www.fh-regensburg.de/"); BufferedReader ein = new BufferedReader( new InputStreamReader(fhURL.openStream())); while ((s = ein.readLine()) != null) System.out.println(s); ein.close(); } catch(MalformedURLException e) { System.out.println("MalformedURLException: " + e); } catch(IOException e) { System.out.println("IOException: " + e); } } } Die Klasse URL besitzt auch Konstruktoren, die die Angabe der Komponenten von der Adresse (also Zugriffsart, Hostname und Dateiadresse getrennt) akzeptieren: public URL(String protocol, String host, int port, String file) throws MalformedURLException public URL(String protocol, String host, String file) throws MalformedURLException Jeder der Konstruktoren wirft eine MalformedURLException, wenn der Parameter im Konstruktor entweder null ist oder er ein unbekanntes Protokoll beschreibt. Zugriff auf Daten über eine URL. Es gibt zwei Möglichkeiten über eine URL bzw. über eine URLConnection. Beide Wege benutzen Streams. Jedes URL-Objekt besitzt die Methode openStream(), die einen InputStream zum Weiterverarbeiten liefert. final InputStream openStream() throws IOException // öffnet eine Verbindung zum Server und liefert einen InputStream zurück URLConnection openConnection() throws IOException // liefert ein URLConnection-Objekt, das die Verbindung zum entfernten // Server vertritt. openConnection() wird vom Protokoll-Handler immer // dann aufgerufen, wenn eine neue Verbindung geöffnet wird. Verweist die URL auf eine Textdatei, dann erweitert man den InputStream zu einem BufferedReader, da dieser die readLine()-Methode besitzt. Bsp.: Eine Antwort (HTML-Seite) auf eine Suchfrage import java.io.*; import java.net.*; public class GoogleSucher { public static void main(String args[]) throws IOException, MalformedURLException { String s = ""; 459 Programmieren in Java if (args.length == 0) s = "Stephan Meier in Regensburg"; else for (int i = 0; i < args.length; i++) s += args[i] + " "; s.trim(); s = "p=" + URLEncoder.encode(s); // Methoden der Klasse URLEncoder machen aus einem String eine URL System.out.println(s); URL u = new URL("http://de.google.yahoo.com/bin/query_de?" + s); BufferedReader ein = new BufferedReader( new InputStreamReader(u.openStream())); String zeile, antwort = null; while ((zeile = ein.readLine()) != null) antwort += zeile + "\n"; System.out.print(antwort); } } URLs in Applets. Die Klasse Applet hat zwei Methoden mit denen man eine BasisURL erzeugen kann, ohne eine feste Adresse im Programm anzugeben: - Die Methode getDocumentBase() gibt ein URL-Objekt zurück, welches das Verzeichnis repräsentiert, das die Webseite mit dem Applet enthält. - Die Methode getCodeBase() gibt ein URL-Objekt zurück, das den Ordner repräsentiert, in dem sich die .class-Datei der Hauptklasse des Applet befindet. Die Applet-Klasse bietet eine Methode mit dem Namen getImage() an, mit der ein Bild in ein Image-Objekt geladen werden kann. Es gibt zwei Möglichkeiten, diese Methode zu verwenden: - Die Methode getImage(), aufgerufen mit einem Argument (ein Objekt vom Typ URL), lädt das Bild mit dieser URL - Die Methode getImage(), aufgerufen mit zwei Argumenten (der Basis-URL und einem String, der den relativen Pfad oder Dateinamen des aktuellen Bilds ausgibt). Bsp.: import java.applet.Applet; import java.awt.*; import java.net.*; public class AppletURL extends Applet { Image bild; public void init() { URL u1 = getDocumentBase(); System.out.println(u1); try { URL u2 = new URL(u1,"images/scratch1.gif"); System.out.println(u2); bild = getImage(getCodeBase(),"images/B04240900.jpg"); } catch (MalformedURLException e) { System.out.println(e); } } public void paint(Graphics schirm) { int iBreite = bild.getWidth(this); int iHoehe = bild.getHeight(this); schirm.drawImage(bild,10,10,iBreite/4,iHoehe/4,this); } } 460 Programmieren in Java URL << Konstruktoren >> public URL(String url) throws MalformedURLException public URL(String protokoll, String host, String pfad) throws MalformedURLException public URL(String protololl, String host, int portnummer, String pfad) throws MalformedURLException // erzeugt ein URL-Objekt mit gegebenen Protokoll, Hostnamen, Portnummer und Datei // Ist die Portnummer –1, so wird der Standard-Port verwendet, z:B für WWW Port 80 << Methoden >> public final Object getContent() throws IOException // public final InputStream openStream() throws IOException // öffnet eine Verbindung zum Server und liefert einen Inputstream zurück. Die Me// thode ist eine Ankürzung für openConnection.getInputStream() public URLConnection openConnection() throws IOException // liefert ein URLConnection-Objekt, das die Verbindung zum entfernten Objekt // vertritt.. openConnection() wird vom Protokoll-Handler immer dann aufgerufen, // wenn eine neue Verbindung geöffnet wird. public String getProtocol() // liefert das Protokoll der URL public String getHost() // liefert den Hostnamen der URL, falls das möglich ist. // Für das Protokoll „file“ ist das ein leerer String public int getPort() // liefert die Portnummer. Ist sie nicht gesetzt, liefert die Methode eine –1. public String getFile() // gibt den Dateinamen der URL zurück Abb.: Die Klasse java.net.URL URL toURL() throws MalformedURLException liefert ein URL-Objekt zu einem File-Objekt. Es muß ein File-Objekt erzeugt sein, anschließend erzeugt toURL() ein URL-Objekt (mit dem Protokoll "file" und den absoluten Pfadangaben zur Datei bzw. zum Verzeichnis). 461 Programmieren in Java 9.1.3 Die Klasse URLConnection und abgeleitete Klassen Eine URL-Connection erhält man durch Aufruf der Methode openConnection() eines URL-Objekts. Mit diesem Objekt lassen sich Inhalte der Ressource lesen sowie Informationen über die Art des Objekts ermitteln. Je nach Art des Protokolls wird die abstrakte Klasse URLConnection durch verschieden konkrete Klassen implementiert, z.B. HttpURLConnection, AppletResourceConnection, FileURLConnection oder FtpURLConnection. Die Subklassen implementieren die Protokolle, mit denen die Verbindung zum Inhalt aufgebaut wird. Subklassen bedienen sich dabei der Klasse URLStreamHandler mit den der eigentliche Inhalt ausgelesen wird. URLConnection { abstract } << Konstruktor >> protected URLConnection(URL url) << Methoden >> public Object getContent() throws IOException, UnkownServiceException // liefert den Inhalt, auf den die URL verweist. Der Inhalt eines URL-Objekts lässt sich mit getContent() vom Server beschaffen. Für HTML-Dateien liefert getContent() ein Objekt vom Typ sun.net.www.MeteredStream, für normale Textdateien ein sun.www.content.txt.PlainTextInputStream-Objekt. Für Texte und HTMLSeiten kann man mit Hilfe eines InputStream-Objekts die Datei (zeilenweise) lesen. Mit Object o = u.getContent(); System.out.println("Ich erhielt " + o.getClass().getName()); kann festgestellt werden, was für ein Handler-Objekt eine URL-Klasse für den Datenstrom einsetzt. getContent() erkennt anhand der Endung bzw. den ersten Bytes den Typ der Datei. Dann konvertiert der Content Handler die Bytes seines Datenstroms in ein Java Objekt. 462 Programmieren in Java 9.2 Kommunikation in Netzwerken Das Common GatewayInterface (CGI) CGI ist die Beschreibung einer Schnittstelle über die externe Programme mit Informations-Servern, meistens Web-Servern, Daten austauschen. CGI-Programme können in allen möglichen Programmiersprachen verfaßt sein. Häufig sind es Shelloder Perl-Skripte. Die CGI-Programme werden von einem Browser durch eine URL angesprochen. Der Browser baut eine Verbindung zum Server auf. Dieser erkennt an Hand des Pfads der URL, ob es sich um eine normale Web-Seite oder um ein Skript handelt. Falls es ein Skript ist, dann führt der Server das Skript aus, das eine HTML-Datei erzeugt. Diese wird übertragen und im Browser dargestellt. Sockets298 Als Socket bezeichnet man ein streambasierte Programmierschnittstelle zur Kommunikation zweier Rechner in einem TCP/IP-Netz. Das Übertragen von Daten über eine Socket-Verbindung besteht aus: 1. Verbindungsaufbau 2. Lesen bzw. Schreiben der Daten 3. Verbindungsabbau Werden Rechner verbunden, so implementiert jeder Rechner einen Socket. Derjenige, der Daten empfängt (Client), öffnet eine Socket-Verbindung zum Horchen und derjenige, der sendet, öffnet eine Verbindung zum Senden (Server). Damit der Empfänger den Sender auch hören kann, muß dieser durch eine eindeutige Adresse als Server ausgemacht werden. Er bekommt also eine IP-Adresse im Netz und eine ebenso eindeutige Port-Adresse. Der Port ist so etwas wie eine Zimmernummer: Die Adresse bleibt dieselbe, aber in jedem Zimmer sitzt einer und macht seine Aufgaben. Für jeden Dienst (Service), den ein Server bereitstellt, gibt es einen Port299. Eine Port-Nummer ist eine 16-Bit-Zahl und in die Gruppen System und Benutzer eingeteilt. Sockets aus der Sicht einer Client- bzw. Server-Anwendung werden durch die beiden Klassen Socket und ServerSocket repräsentiert. Bsp300.: 1. Ein einfacher Server, der alles „nachplappert“, was der Client sendet. 2. Ein einfacher Client, der Zeilen zum Server sendet und einliest 298 Sockets wurden zu Beginn der achtziger Jahre für die Programmiersprache C entwickelt. Diese Adressen werden von der IANA (Internet Assigned Number Authority) vergeben. Die IANA ist der zentrale Koordinator für die IP-Adressen, Domain Namen, MIME Typen und viele andere Parameter, u.a.a. Portnummern. 300 pr92100 299 463