Java 2 Kompendium
Ralph Steyer
Java 2
Professionelle Programmierung mit J2SE Version 1.3
Markt+Tec Technik Verlag
Die Deutsche Bibliothek – CIP-Einheitsaufnahme Ein Titeldatensatz für diese Publikation ist bei Der Deutschen Bibliothek erhältlich. Die Informationen in diesem Buch werden ohne Rücksicht auf einen eventuellen Patentschutz veröffentlicht. Warennamen werden ohne Gewährleistung der freien Verwendbarkeit benutzt. Bei der Zusammenstellung von Texten und Abbildungen wurde mit größter Sorgfalt vorgegangen. Trotzdem können Fehler nicht vollständig ausgeschlossen werden. Verlag, Herausgeber und Autoren können für fehlerhafte Angaben und deren Folgen weder eine juristische Verantwortung noch irgendeine Haftung übernehmen. Für Verbesserungsvorschläge und Hinweise auf Fehler sind Verlag und Herausgeber dankbar. Alle Rechte vorbehalten, auch die der fotomechanischen Wiedergabe und der Speicherung in elektronischen Medien. Die gewerbliche Nutzung der in diesem Produkt gezeigten Modelle und Arbeiten ist nicht zulässig. Fast alle Hardware- und Softwarebezeichnungen, die in diesem Buch erwähnt werden, sind gleichzeitig auch eingetragene Warenzeichen oder sollten als solche betrachtet werden. Umwelthinweis: Dieses Buch wurde auf chlorfrei gebleichtem Papier gedruckt. Die Einschrumpffolie – zum Schutz vor Verschmutzung – ist aus umweltverträglichem und recyclingfähigem PE-Material.
10 9 8 7 6 5 4 3 2 1 05 04 03 02 01
ISBN 3-8272-6039-6 © 2001 by Markt+Technik Verlag, ein Imprint der Pearson Education Deutschland GmbH, Martin-Kollar-Straße 10–12, D-81829 München/Germany Alle Rechte vorbehalten Einbandgestaltung: Grafikdesign Heinz H. Rauner, Gmund Lektorat: Jürgen Bergmoser,
[email protected] Herstellung: Elisabeth Egger,
[email protected] Satz: reemers publishing services gmbh, Krefeld (www.reemers.de) Druck und Verarbeitung: Bercker, Kevelaer Printed in Germany
Java 2 Kompendium 5 Inhaltsübersicht Vorwort . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 25 Hinweise zum Buch. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 27 Kapitel 1 What's new? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29 Kapitel 2 Schnuppertour Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55 Kapitel 3 Das JDK. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 87 Kapitel 4 Unterschiede zwischen Java-Applikakkationen und -Applets . . . . . . . . . . 137 Kapitel 5 Java-Hintergründe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151 Kapitel 6 Java – die Hauptbestandteile der Sprache. . . . . . . . . . . . . . . . . . . . . . . . . 183 Kapitel 7 Grundlagen der Applet-Erstellllung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341 Kapitel 8 Multithreading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393 Kapitel 9 Grafik und Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417 Kapitel 10 Das AWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 517 Kapitel 11 Swing & Co . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .623 Kapitel 12 Debugging und Ausnahmebehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . 667 Kapitel 13 Ein- und Ausgabe in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .697 Kapitel 14 Java-Sicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 755 Kapitel 15 Andere Sprachen in Verbindung mit Java. . . . . . . . . . . . . . . . . . . . . . . . . 777 Kapitel 16 Weiterführende Themen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 829 Anhang A. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 905 Anhang B. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 955 Anhang C. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1075 Anhang D. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1101 Stichwortverzeichnis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1105 Java 2 Kompendium
5
Inhaltsverzeichnis
V o r w or t . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25
Hinweise zum Buch. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
27
Kapitel 1
W h at ' s n ew ? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
1.1
Java 1.2, Java 2.0, JDK 1.2 und JDK 1.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
29
1.2
JavaSoft und Sun . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
33
1.3
Die Java-Neuerungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Kompatibilität und das Java-Plug-In . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Interoperabilität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Anpassung einer Applikation an verschiedene Länderstandards . . . . . . . . . . . . . . . . . . . . Erweiterung der Sicherheitsschnittstelle von Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Verschlüsselung in Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Abstract Window Toolkit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Java-Archive . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Datenbankunterstützung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Verteilte Programmierung und Object Serialization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Java IDL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . JavaBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Servlets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Inner Classes und Anonymous Classes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Native Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Reference Objects . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Multimedia-Unterstützung in Java 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Java-2D-API-Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Swing JDK 1.2/1.3 und die Java Accessibility API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Drag&Drop . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Java-Standardklassen und die Datei classes.zip . . . . . . . . . . . . . . . . . . . . . . . . . . . . Veränderungen bei den Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Neues Lizenzierungsmodell für Java 2.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34 34 39 40 40 41 42 43 44 44 45 45 46 46 47 47 47 48 49 50 51 51 53
1.4
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
54
Java 2 Kompendium
7
Inhaltsverzeichnis Kapitel 2
Schnup nuppertour Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
55
2.1
Was ist Java? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Internet. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein kurzer Abriss der Internet-Geschichte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . HTML . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Organisation des Internets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Adressierung im Internet. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die IP-Nummern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das DNS-Konzept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Uniform Resource Locator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
56 56 58 62 64 65 66 68 71
2.2
Die Geschichte von Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Erstes Java-Auftreten, HotJava und das JDK 1.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Was macht Java so einzigartig? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Herkunft von Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73 75 77 79
2.3
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
86
Kapitel 3
D as J D K . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
87
3.1
Bezug des JDK. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
88
3.2
Installation des JDK. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . classes.zip, src.zip, src.jar, rt.jar & Co. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Verzeichnisstruktur innerhalb des JDK-Verzeichnisses . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Java-Laufzeitumgebung jre. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Das Java-Plug-In . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
91 93 94 96 96
3.3
Die JDK-Dokumentation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 102
3.4
Fehlervorbeugung und Fehlerbehebung beim JDK . . . . . . . . . . . . . . . . . . . . . . . . . . . Pfad-Angaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . CLASSPATH . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Verwendung von mehreren JDK-Versionen unter Windows . . . . . . . . . . . . . . . . . . . . . . . . »Class not found« und andere Fehlermeldungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Die Java-Umgebungsvariablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Unbekannte Fehler. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
103 103 105 109 112 116 116
3.5
Die wichtigsten JDK-Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ein paar allgemeine Hinweise zu den JDK-Programmen . . . . . . . . . . . . . . . . . . . . . . . . . . Der Appletviewer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Java-Compiler Javac . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Der Java-Interpreter. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
117 117 121 125 130
3.6
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 136
8
Java 2 Kompendium
Inhaltsverzeichnis Kapitel 4
Unterschiede zwischen Java-Applikat kationen un und -Applets . . . . . . . . . . . . . . . . . . . . . 137
4.1
Die technischen Unterschiede . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 139
4.2
Eine erste Beispielanwendung und ein erstes Applet. . . . . . . . . . . . . . . . . . . . . . . . . 141 Eine erste Applikation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 141 Übergabe von Aufrufargumenten an ein Java-Programm . . . . . . . . . . . . . . . . . . . . . . . . . 144 Ein erstes Applet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 146
4.3
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 149
Kapitel 5
Java-Hintergründe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
5.1
Was ist OOP?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 151
5.2
Die Definition von Objektorientierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 Objektmethoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 153 Who's calling?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155
5.3
Klassen und Instanzen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157 Superklasse und Subklasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 159 Metaklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160 Abstrakte Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 160
5.4
Vererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 161 Mehrfachvererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 162
5.5
Überschreiben. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
5.6
Polymorphismus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 164
5.7
Binden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 Frühes Binden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 165 Spätes Binden. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 166
5.8
Die Grundprinzipien der objektorientierten Programmierung . . . . . . . . . . . . . . . . . . . 166
5.9
Objektorientierte Analyse und objektorientiertes Design . . . . . . . . . . . . . . . . . . . . . . 167 Die objektorientierte Analyse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 167 Das objektorientierte Design. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 168
5.10
Die Objektorientierung von Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Java-Klassen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Polymorphismus und Binden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Schnittstellen und Pakete statt Mehrfachvererbung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 169 Überladen, Überschreiben und Überschatten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 170 Globale Vereinbarungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172 Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 172
Java 2 Kompendium
9
Inhaltsverzeichnis 5.11
Plattformunabhängigkeit von Java und die JVM . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 Die binäre Plattformunabhängigkeit von Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 173 Die JVM-Architektur. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 174 Die Datentypen der JVM. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 179 Die Java-Plattformunabhängigkeit auf Quellebene . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
5.12
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 180
Kapitel 6
Java – die Haupt uptbestandt ndteile der Sprache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183
6.1
Token. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 183 Der Unicode-Zeichensatz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 185 Die UTF-8-Codierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Kleiner Vorgriff auf die Datentypen von Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 188 Die Java-Token-Details . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 189 Literale . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 195 Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 201 Operatoren-Priorität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 221 Kommentare . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 222
6.2
Typen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 223 Operationen mit den Datentypen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 226
6.3
Datenfelder (Arrays) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 235 Deklarieren von Datenfeldern . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 236 Erstellen von Datenfeldobjekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 237 Dynamische Arrays . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 238 Speichern und Zugreifen auf Datenfeldelemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 239 Collections . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 244
6.4
Ausdrücke, Operatoren und Casting . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245 Ausdrücke. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245 Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 248 Typkonvertierungen und der Casting-Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 250 Der Instanceof-Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258
6.5
Anweisungen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 258 Blöcke und Anweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 260 Leere Anweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 Bezeichnete Anweisung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 261 Deklarationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 262 Ausdrucksanweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 264
10
Java 2 Kompendium
Inhaltsverzeichnis Auswahlanweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 265 Iterationsanweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 269 Java-Sprunganweisungen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 272 Schutzanweisung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 276 Unerreichbare Anweisung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 6.6
Klassen und Objekte . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 277 Allgemeines zu Klassen in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 278 Allgemeine Klassendeklaration . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 Klassen-Modifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 279 Der Klassenname . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 Superklassen und das Schlüsselwort extends . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 285 Designregeln für Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 286 Innere und anonyme Klassen bzw. Schnittstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 287 Adapterklassen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289 Konstruktoren und der new-Operator . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 289 Das Speichermanagement . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 296 Das Schlüsselwort this. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 299
6.7
Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 302 Deklaration einer Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303 Die Zugriffsspezifizierung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 303 Die Methodenmodifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 305 Die Rückgabewerte von Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 308 Der Methodenname. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 309 Die Parameterliste . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 310 Der Methodenkörper . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311 Methoden überladen und überschreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 311
6.8
Schnittstellen und das Schlüsselwort implements . . . . . . . . . . . . . . . . . . . . . . . . . . 317 Erstellung einer Schnittstelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318 Namensregeln für Schnittstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 318 Die Erweiterung anderer Schnittstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319 Der Körper einer Schnittstelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 319 Überschreiben und Verwenden von Schnittstellenmethoden . . . . . . . . . . . . . . . . . . . . . . . 322 Verwenden von Feldern einer Schnittstelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 323 Beispiele mit Schnittstellen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 324
Java 2 Kompendium
11
Inhaltsverzeichnis 6.9
Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325 Instanzvariablen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326 Klassenvariablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 326 Klassenvariablen versus Instanzvariablen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 327 Lokale Variablen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 329 Die Java-Konstanten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
6.10
Der Zugriff auf Java-Elemente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 330
6.11
Pakete und die import-Anweisung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331 Die Verwendung von Paketen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 331 Erstellung eines Paketes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 335 Das anonyme Default-Paket . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 336 Zugriffslevel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
6.12
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 337
Kapitel 7
Grundlagen de der Applet-Erstellun llung ung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 341
7.1
Die Vorbereitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 342
7.2
Grundlagen der Einbindung von Java-Applets in HTML-Seiten. . . . . . . . . . . . . . . . . . 343 HTML-Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 343
7.3
Konkrete Referenzierung eines Java-Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349 Die <APPLET>-Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 349 Die Parameter des <APPLET>-Tags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 355 Die <EMBED>-Syntax . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 358 Die
-Syntax. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 360 Welches Tag ist sinnvoll? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 362 Die ursprüngliche HotJava-Syntax. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363
7.4
Die interne Arbeitsweise eines Applets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 363 Erstellung und Grundmethoden eines Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 364 Ein Musterapplet als Schablone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 366
7.5
Die Applet-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 367 Ausgabe in einem Applet mit der paint()-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368 Übergabewerte für ein Applet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 368 Bilder importieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 373 Importieren und Abspielen von Sound . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375 Die Interaktion des Applets mit der Umgebung und den Lebenszyklus eines Applets verwalten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 375 Methoden zur Ereignisbehandlung in Applets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 378
12
Java 2 Kompendium
Inhaltsverzeichnis 7.6
Multithreading bei Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 387
7.7
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 390
Kapitel 8
Multithread eading. ng . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 393
8.1
Klassen Thread-fähig machen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394 Die Erweiterung der Klasse Thread . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 394 Implementation von Runnable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 403
8.2
Thread-Gruppen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406 Eine Thread-Gruppe erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 406 Hinzufügen eines Threads zu einer Thread-Gruppe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 407
8.3
Dämonen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 409
8.4
Schutzmaßnahmen bei Multithreading . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 412 Methoden synchronisieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413 Vorsicht, Deadlock! . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 413 Synchronisierte Blöcke. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 414 Thread-sicherer Zugriff auf Datenstrukturen und Objekte . . . . . . . . . . . . . . . . . . . . . . . . . 415
8.5
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 415
Kapitel 9
Grafik und und Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 417
9.1
Zeichnen, Update und neu zeichnen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 418
9.2
Punkte, Linien, Kreise und Bögen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420 Das Koordinatensystem . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 420 Eine Linie zeichnen – die drawLine()-Methode. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 421 Ein Rechteck zeichnen – die drawRect()-Methode . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 422 Ein gefülltes Rechteck zeichnen – fillRect() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423 Löschen eines Bereichs – clearRect() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 423 Kopieren eines Bereichs – copyArea(). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 424 Ein 3D-Rechteck zeichnen – die draw3dRect()-Methode . . . . . . . . . . . . . . . . . . . . . . . . . 425 Ein gefülltes 3D-Rechteck zeichnen – fill3dRect() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 425 Abgerundete Rechtecke – drawRoundRect() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 Abgerundete gefüllte Rechtecke – fillRoundRect() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 426 Zeichnen von Polygonen – drawPolygon() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 427 Zeichnen von gefüllten Polygonen – fillPolygon() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 430 Zeichnen von Kreisen und Ellipsen – drawOval() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 431 Zeichnen von gefüllten Kreisen und Ellipsen – fillOval() . . . . . . . . . . . . . . . . . . . . . . . . . . 432 Zeichnen von Bögen – drawArc() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 433 Zeichnen von gefüllten Bögen – fillArc() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434
Java 2 Kompendium
13
Inhaltsverzeichnis 9.3
Farbangaben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 434 Die Color-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 435 Farben setzen – setColor() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 436 Hintergrundfarben und Vordergrundfarben pauschal setzen – setBackground() und setForeground(). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 438 Ein Apfelmännchen als Java-Applet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 439 Abrufen von Farbinformationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 444 Textausgabe über den Zeichnen-Modus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445 Erstellen von Fontobjekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 445 Abfragen der zur Verfügung stehenden Fonts . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448 Informationen über einen speziellen Font abfragen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 448
9.4
Die Java-Zeichenmodi. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 450 Der Paint-Modus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 450 Der XOR-Zeichenmodus . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 450
9.5
Zeichnen von Bildern. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451 Laden von Bildern – getImage() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 451 Anzeigen von Bildern – drawImage() . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 452 Der Imageobserver und der MediaTacker . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 455
9.6
Animationen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 457 Aufbau eines Animationsrahmens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 458 Abspielen einer Animation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 458 Flimmereffekte in Animationen reduzieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 468 Clipping. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 474 Double-Buffering . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 476
9.7
Das 2D-API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 478 Java-2D-Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 479 Zeichnen unter dem Java-2D-API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 480 Komplexere Zeichnenoperationen im 2D-API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 487 Text unter Java 2D. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 490
9.8
Bilder unter Java 2D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493 Anzeige von Bildern unter Java 2D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493 Transparenz und Bilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 493 Rendering als Bestandteil von Java 2D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 499 Kontrolle der Ausgabequalität . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 500 Transformation von 2D-Objekten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 501 Neue Formen kreieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502
14
Java 2 Kompendium
Inhaltsverzeichnis Stroking Paths. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502 Füllen von Formen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502 Komposition von Bildern. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 502 Transparenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 503 Text und Fonts unter Java 2D. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 503 Farbmanagement unter Java 2D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506 2D-Bildbearbeitung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 506 Offscreen-Puffer unter Java 2D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507 2D-Graphics Devices . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 507 9.9
Die Java-2D-API-Referenz . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 508
9.10
Sound und Töne in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 510 Das Abspielen und Stoppen eines Audio-Clips – play() . . . . . . . . . . . . . . . . . . . . . . . . . . . 511 Die Sound-Möglichkeiten der JDK-Demos. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 511 Das Java Media Framework . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 514
9.11
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 515
Kapitel 10
Das AWT . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 517
10.1
Was ist das Abstract Window Toolkit? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 517
10.2
Woraus besteht das AWT? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 521 Was sind AWT-Komponenten? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 522 Was versteht man unter AWT-Container? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523 Container und Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523 Der Aufbau der AWT-Klassenhierarchie . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523 Was ist ein AWT-Layoutmanager? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 523
10.3
Container. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 525 Panels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 526 Frames . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 527 Dialoge . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 533
10.4
Schaltflächen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 538
10.5
Labels . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 543
10.6
Kontrollkästchen und Optionsfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 545 Kontrollkästchen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 546 Optionsfelder. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 548 Reaktionen auf Kontrollfelder und Radiobuttons . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 550
Java 2 Kompendium
15
Inhaltsverzeichnis 10.7
Auswahlmenüs und Listenfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 554 Einzeilige Listenfelder. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 555 Mehrzeilige Listenfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 559
10.8
Textbereiche und Textfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 566 Textbereiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 566 Textfelder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 567 Gemeinsame Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 569 Spezielle TextField-Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 570 Spezielle TextArea-Methoden . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 571
10.9
Schieber und Bildlaufleisten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 572
10.10
Zeichenbereiche . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 578
10.11
Menüs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 580
10.12
Eine Schablone für alle Fälle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 589
10.13
Layoutmanager . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 590 Layout-Regeln . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 592 Die Layoutmanager im Detail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 593
10.14
Die Eventmodelle 1.0 und 1.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 605 Der AWT-Event-Handler 1.0 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 606 Tastaturereignisse des AWT-Event-Handlers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 607 Mausereignisse des AWT-Event-Handlers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 608 Zentrale Aspekte des 1.0-Eventhandlings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 608 Das Eventhandling 1.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 611 Der Umstieg vom 1.0-Eventmodell auf das 1.1-Modell. . . . . . . . . . . . . . . . . . . . . . . . . . . 618
10.15
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 621
Kapitel 11
Swing & Co . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 623
11.1
Das Swing-API. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 623
11.2
Swing und AWT im Vergleich . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 624 Einige Swing-Probleme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 626
11.3
Swing in der Praxis . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 627 Umschalten von Look and Feel . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 627 Swing und Applets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 629
11.4
Eigenständige Applikationen und Swing . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 638 Eine Swing-Applikations-Schablone . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 639
16
Java 2 Kompendium
Inhaltsverzeichnis 11.5
Swing-Komponenten im Detail . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 643
11.6
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 666
Kapitel 12
Debug bugging und und Ausnahmebehandlung ung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 667
12.1
Debugging. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 668 Fehler im Vorfeld abfangen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 668 Vernünftige Sicherungsmaßnahmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 669 Fehler finden und beseitigen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 669 Fehlerlokalisierung ohne Debugger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 670 Fehlerlokalisierung mit Debugger . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 672
12.2
Ausnahmebehandlung und andere Fehlerbehandlungen . . . . . . . . . . . . . . . . . . . . . . 675 Die individuell programmierte Fehlerbehandlung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 676 Die Ausnahmebehandlung von Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 677 Benutzerdefinierte Exceptions. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 687 Die RuntimeException . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 693
12.3
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 694
Kapitel 13
Ein- und und Ausgabe in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 697
13.1
Allgemeines zur Ein- und Ausgabe unter Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 697
13.2
Die Klasse InputStream. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701 Methoden zum Einlesen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 701 Blockaden vorher abfragen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 702 Überspringen von Daten. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 703 Positionen beim Lesen markieren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 703 Ressourcen freigeben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 704
13.3
Die Klasse OutputStream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 704 Methoden zum Schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 705 Den gepufferten Cache ausgeben. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 706 Ressourcen freigeben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 707
13.4
Byte-Datenfeldströme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 707
13.5
Der StringBufferInputStream . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 708
13.6
Gefilterte Ströme. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 708
13.7
Gepufferte Ströme. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 709
13.8
Datenströme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710 Die DataInput-Schnittstelle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 710
Java 2 Kompendium
17
Inhaltsverzeichnis Die DataOutput-Schnittstelle. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 712 Die DataInputStream- und DataOutputStream-Klassen . . . . . . . . . . . . . . . . . . . . . . . . . . . 712 13.9
Die PrintStream-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 713
13.10
Pipe-Ströme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 714
13.11
Objektströme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 715
13.12
Einige spezielle Utility-Ströme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 716 Die LineNumberInputStream-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 716 Die SequenceInputStream-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 716 Die PushbackInputStream-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 717 Die StreamTokenizer-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 717
13.13
Die File-Klasse. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 718 Überprüfung, ob Datei oder Verzeichnis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 718 Lese- und Schreiberlaubnis überprüfen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 719 Die letzte Änderung überprüfen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 719 Die Existenz eines Objekts überprüfen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 719 Pfadkontrolle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 719 Namen und Pfad einer Datei oder eines Verzeichnisses ermitteln . . . . . . . . . . . . . . . . . . . 719 Eine Datei oder ein Verzeichnis umbenennen. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 720 Dateien löschen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 720 Verzeichnisse erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 720 Den Inhalt eines Verzeichnisses angeben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 720
13.14
Dateiströme – FileInputStream und FileOutputStream . . . . . . . . . . . . . . . . . . . . . . . . 721 Die RandomAccessFile-Klasse . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 722
13.15
Praktische Java-Beispiele mit Datenströmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 723
13.16
Drucken unter Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 744 Drucken unter dem JDK 1.1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 744 Drucken unter dem SDK 2 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 748
13.17
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 751
Kapitel 14
Java-Sicherheit . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 755
14.1
Das Java-Sicherheitskonzept . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 755 Die Missbrauchsfähigkeiten einer Sprache . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 756 Viren . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 760 Unkontrollierte Systemzugriffe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 760 Verifizierung des Bytecodes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 761 Der Verifier . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 761
18
Java 2 Kompendium
Inhaltsverzeichnis Der Classloader. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 762 Der Sicherheitsmanager. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 763 Digitale Unterschriften eines Applets und Verschlüsselung in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765 Native Code . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 765 Weitere unerwünschte Aktionen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766 14.2
ActiveX-Sicherheitskonzept im Vergleich . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766 Was sind ActiveX-Controls? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 766 Sicherheitskonzept von ActiveX . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 769
14.3
Bekannte Sicherheitslücken der Java-Vergangenheit . . . . . . . . . . . . . . . . . . . . . . . . 771 Der Bytecode-Bug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 772 Schwarze Witwen und der DNS-Bug . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 773 Der CLASSPATH-Bug. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 773 Weitere Bugs . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 774 Informationen zur Sicherheit. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 775
14.4
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 775
Kapitel 15
Andere Sprachen in Ve Verbindu ndung mit Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 777
15.1
C/C++ – Unterschiede und Gemeinsamkeiten sowie die Einbindung . . . . . . . . . . . . 777 Unterschiede und Gemeinsamkeiten . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 777
15.2
Die Verbindung von Java und nativem Code. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 784 Konventioneller Einbau von C-Code in Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 785 Das Java Native Interface (JNI) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 790 JNI-Grundlagen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 791 Datenaustausch zwischen Java und C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 793 Java-C++-Verbindung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 794 Alternativen zum direkten Einbinden von nativen Methoden . . . . . . . . . . . . . . . . . . . . . . . 795
15.3
Verbindung von Java zu JavaScript und anderen Scriptsprachen . . . . . . . . . . . . . . . 795 Kleiner Exkurs zu JavaScript-Interna . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 797 Das Objektmodell von JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 807 Verbindung von Java und JavaScript. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 811 Applets dynamisch schreiben . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 823 Verbindung mit weiteren Techniken . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 826
15.4
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 827
Java 2 Kompendium
19
Inhaltsverzeichnis Kapitel 16
Weiterführ ührende nde Th Themen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 829
16.1
Reflection und Serialization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 829 Reflection . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 830 Serialization. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 831
16.2
JavaBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 832 Wie passen JavaBeans in Java? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 833 Beans erstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 838 Spezielle Anwendungen von JavaBeans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 841 Beans einer IDE bereitstellen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 842
16.3
Verteilte Systeme – RMI, IDL und CORBA . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 842 Java IDL oder wie ist RMI in Bezug auf CORBA zu sehen? . . . . . . . . . . . . . . . . . . . . . . . . 844 Entwicklung einer CORBA-Anwendung in Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 846
16.4
Netzwerkzugriffe, Sockets und Servlets . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 853 Java-Servlets, JSP und SSI. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 853 Allgemeine Netzwerkzugriffe. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 870 Sockets. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 874
16.5
Datenbanken und JDBC. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 880 Was sind relationale Datenbanken?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 881 Was ist SQL?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 883 JDBC versus ODBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 883 Grundaufbau von JDBC und des JDBC-Managers . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 884 Woraus besteht das JDBC-API? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 885 Die JDBC-Treiber. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 888 Schematischer Aufbau einer Datenbank-Applikation. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 889 Praktischer Einsatz von JDBC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 891
16.6
Zusammenfassung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 904 Anhang A . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 905
A.1
Ergänzungen zu den JDK-Tools. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 905 Erweiterte Basisprogramme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 905 Internationalization Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 927 Security Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 928 Java IDL- und RMI-Tools . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 936
20
Java 2 Kompendium
Inhaltsverzeichnis A.2
IDEs für Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 938 JCreator für Windows . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 938 Forte 2.0 for Java . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 941 JBuilder . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 948 Kawa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 951 Anhang B . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 955
B.1 B.2
Das Java-1.3-API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 956 Beschreibung der Pakete . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 959 java.applet . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 960 java.awt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 960 java.awt.color . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 966 java.awt.datatransfer . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 967 java.awt.dnd . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 967 java.awt.event . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 968 java.awt.font . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 970 java.awt.geom. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 971 java.awt.im . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 972 java.awt.im.spi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 973 java.awt.image . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 973 java.awt.image.renderable . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 976 java.awt.print . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 976 java.beans . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 977 java.bean.beancontext . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 978 java.io. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 979 java.lang . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 983 java.lang.ref . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 988 java.lang.reflect. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 988 java.math . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 989 java.net. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 989 java.rmi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 991 java.rmi.activation . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 992 java.rmi.dgc . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 992 java.rmi.registry. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 993 java.rmi.server . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 993 java.security . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 995 java.security.acl. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 997
Java 2 Kompendium
21
Inhaltsverzeichnis java.security.cert . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 997 java.security.interfaces. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 998 java.security.spec . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 998 java.sql . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 999 java.text . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1000 java.util . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1002 java.util.jar. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1004 java.util.zip . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1004 javax.accessibility . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1005 javax.naming . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1006 javax.naming.directory . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1007 javax.naming.event, javax.naming.ldap und javax.naming.spi . . . . . . . . . . . . . . . . . . . . . . 1007 javax.rmi und javax.rmi.CORBA. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1008 javax.sound.midi . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1008 javax.sound.midi.spi, javax.sound.sampled und javax.sound.sampled.spi . . . . . . . . . . . . . 1009 javax.swing & Co . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1009 javax.swing.event. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1014 javax.swing.undo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1016 Die CORBA-Pakete. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1016 Nicht enthalten – das Servlets-API . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1017 B.3
Die veralteten Elemente des Java-API 1.3 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1017 Klassen (deprecated) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1018 Interfaces (deprecated). . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1018 Exceptions (deprecated) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1018 Felder (deprecated) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1018 Methoden (deprecated) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1019 Constructors (deprecated) . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1025
B.4
HTML-Elemente und -Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1025 HTML-Tags . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1025 Die HTML 4.0-Attribute . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1030
B.5
JavaScript- Token . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1070 Anhang C . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1075
C.1
JAR-Archive oder wie arbeiten Komprimierungstechnologien? . . . . . . . . . . . . . . . . . 1075 Komprimierungsverfahren und Programme . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1075 Der Huffman-Algorithmus. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1078
22
Java 2 Kompendium
Inhaltsverzeichnis Selbstextrahierende Programme. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1082 Wie groß ist die Reduzierung der Datenmenge?. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1083 C.2
Wie funktionieren Verschlüsselungsverfahren? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1083 Verschlüsselungs- bzw. Kryptographieprogramme. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1084 Einige Verschlüsselungsverfahren. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1086 PGP oder wie Java verschlüsselt . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1089 Die heutigen Standard-Algorithmen . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1089 Eine kleine Abschlussbemerkung zum Thema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1090
C.3
Von der Dezimalrechnung abweichende Rechenarten . . . . . . . . . . . . . . . . . . . . . . . . 1091 Binärrechnung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1091 Oktalrechnung. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1092 Hexadezimalrechnung . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1093
C.4
Die Theorie des Zweierkomplements bei ganzen Zahlen . . . . . . . . . . . . . . . . . . . . . . 1095
C.5
Farbangaben in Java und JavaScript . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1097 Die direkte Angabe eines Farbnamens . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1097 Die Farbangabe mittels RGB-Werten in Hexadezimalform. . . . . . . . . . . . . . . . . . . . . . . . . 1097
C.6
Erläuterung der Kurzschreibweisen von Operatoren . . . . . . . . . . . . . . . . . . . . . . . . . 1098
C.7
Java-Quellen im Internet. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1099 Anhang D . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1101
D.1
Neuerungen der Java-2-Plattform . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1101 Stichwortverzeichni hnis. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1105
Java 2 Kompendium
23
Vorwort
Wíllkommen zu Java! Vielleicht stellen Sie sich gerade die Frage: »Was kann ich mit Java machen?« Eine einfache Antwort: Fast alles! Zumindest, wenn Sie an Programmierung denken. Grafische Animationen, Chats im Rahmen von Webseiten, Spiele, Verschlüsselungsprogramme, interaktive Webseiten, Musik, Textverarbeitungen, kleine und große Internet-Applets, riesige Programmpakete, Programme zur Bildverarbeitung, neuartige Benutzeroberflächen, Übersetzungsprogramme, Bestellsysteme, Lernanwendungen, Uhren, Office-Programme und noch viel mehr. Mit anderen Worten: Mit der Programmiersprache Java kann man jedes denkbare Programm schreiben. Zuerst profitieren natürlich das Internet und das WWW davon – auf allen möglichen WWW-Seiten werden Java-Applets eingesetzt. Aber auch jenseits des Internets gibt es einen unerschöpflichen Fundus an potenziellen Anwendungen. Sie finden mittlerweile zahlreiche in Java erstellte Applikationen auf den verschiedensten Computersystemen. Das Einsatzgebiet von Java ist jedoch nicht nur auf reine Computersysteme beschränkt, sondern erstreckt sich insbesondere auch auf die Programmierung von einer Vielzahl verwandter Geräte, die einen Prozessor beinhalten. Das sind einmal naheliegende Beispiele wie Mobiltelefone, insbesondere solche, die den neuen UMTS-Standard – Universal Mobile Telecommunication System (universelles mobiles Telekommunikationssystem) – unterstützen sollen und Videorecorder, aber auch viele andere Geräte des täglichen Gebrauchs, von denen einige auch erst in der Zukunft eine Zielplattform für eine umfangreiche Programmierung bieten. Sei es der voll programmierbare Kühlschrank, der mit einem Warenmanagementsystem selbst seinen Inhalt verwaltet und bei Bedarf selbstständig eine Bestellung per Internet aufgibt oder die Mikrowelle, die aus dem Auto heraus via Handy und Internet angeschaltet werden kann. Diese und viele andere Geräte sind keine Fiktion, sondern bereits Realität, und es ist nur noch eine Frage der Zeit, wann sie zu selbstverständlichen Gebrauchsgegenständen werden. Und mit Java steht die Technik bereit, den damit verbundenen Anforderungen der Programmierung zu begegnen.
erstellt von ciando
Auf jeden Fall ist Java »hipp«, ist heiß. Fast alle wichtigen Firmen der Computerbranche setzen seit längerer Zeit verstärkt auf Java. Zwar hatte sich die anfängliche Euphorie um Java zwischenzeitlich etwas gelegt, aber mittlerweile ist die Bedeutung von Java wieder erheblich gestiegen (nicht zuletzt Java 2 Kompendium
25
Vorwort durch die Möglichkeiten bei der Programmierung von intelligenten Geräten des täglichen Gebrauchs). Java hat sich als eine feste Größe in der EDV etabliert und wenn die Visionäre Recht behalten, wird Java bereits in naher Zukunft in fast allen datenbasierenden Geräten zu finden sein. Die immer stärker werdende Nachfrage nach Java-Programmierern zeigt zudem, wie viel Potenzial die Wirtschaft in Java sieht. Seit der Version 1.0 wurden Java und das zugehörige Entwicklungspaket JDK (Java Development Kit) immer weiter verbessert. Vor allem kann man seit dem JDK 1.2 davon ausgehen, dass Kinderkrankheiten der ersten beiden Versionen 1.0/1.1 (vor allem der Version 1.0) der Vergangenheit angehören. Das JDK 1.3 als aktuelle Basis der Java-2-Plattform hat sich insbesondere noch einmal der Performance von Java-Programmen angenommen und Java ist damit zu einer der leistungsfähigsten Entwicklerplattformen überhaupt geworden. Vor allem ist Java aber spannend und eine der innovativsten Entwicklungen der EDV-Welt seit vielen Jahren. Viel Spaß beim Erforschen, Erlernen und Programmieren wünscht Ihnen Ralph Steyer HTTP://WWW.RJS.DE
26
Java 2 Kompendium
Hinweise zum Buch
Um sich die Arbeit mit dem Buch und den Beispielen zu erleichtern, sollten Sie folgende Hinweise beachten: Beachten Sie die Tipps, Warnungen und Hinweise außerhalb des Fließtextes, ebenso besonders hervorgehobene Textpassagen. Sie finden die Beispiele und die dort verwendeten Dateien (Bilder, Datenbanken usw.) allesamt auf der Buch-CD. Aus didaktischen Gründen ist jedoch ein explizites Abtippen der Quelltexte immer sinnvoller. Aus satztechnischen Gründen ist es in einigen Listings notwendig, Strings (in Hochkommata eingeschlossene Texte) auf mehrere Zeilen aufzuteilen. Das darf im Editor bei der Eingabe des Quelltextes aber nicht gemacht werden. Mit anderen Worten – Strings müssen unbedingt in eine Zeile geschrieben werden. Zwischen den Versionen von Java bestehen erhebliche Differenzen und teilweise Inkompatibilitäten. Darauf wird an diversen Stellen im Buch hingewiesen. Die Entwicklungen rund um Java sind permanent im Fluss. Deshalb sollten Sie immer aktuelle Entwicklungen im Internet verfolgen und neueste Programme und Dokumentationen aus dem Internet bei Bedarf parat haben.
Java 2 Kompendium
27
1
What's new?
Lassen Sie uns gleich damit beginnen, uns an die faszinierende Welt von Java heranzutasten. Bevor wir jedoch zur eigentlichen Java-Schnuppertour kommen, sollen für die Leser, die Java bereits kennen, kompakt die Neuerungen von Java 2.0 und dem JDK 1.2/1.3 aufgeführt werden. In diesem Abschnitt wird eine gewisse Vertrautheit mit den wichtigsten Begriffen von Java und seinem Umfeld vorausgesetzt. Wer sich mit Java noch nicht auskennt, kann gerne später auf diesen Abschnitt zurückkommen.
1.1
Java 1.2, Java 2.0, JDK 1.2 und JDK 1.3
Java 2.0! Erheblich verspätet, aber dafür mit umso lauterem Getöse ging Java nach dem fulminanten 1.0-Einstieg und den Zwischenversionen 1.1 und 1.2 (Beta) Anfang 1999 in die zweite Runde. Im Dezember 1998 hatte Sun Microsystem (der »Eigentümer« von Java) endlich die bereits Anfang 1998 angekündigte und mehrfach verschobene Finalversion des JDK 1.2 (Java Development Kit) – der Java-Entwicklungsumgebung – freigegeben. Kurz danach gab Sun auf der Java Business Expo bekannt, dass es nicht nur ein neues JDK, sondern ein vollständiges Plattform-Update unter dem Namen Java 2.0 gibt. Diese Namenspolitik überraschte zum damaligen Zeitpunkt doch ziemlich, denn es schien bis dahin klar, dass das vollständige Update der bis dahin vertriebenen JDK-Finalversion 1.1 und seiner ergänzenden Tools als Version 1.2 in Umlauf kommt. So ganz überzeugt von seiner Namenspolitik scheint auch Sun nicht gewesen zu sein, denn das erste JDK von Java 2 – Kern der Java-2.0-Plattform – wurde innerhalb der Java-2.0-Plattform als JDK 1.2 eingeführt und auch das neue JDK von Java 2 läuft als Version 1.3. Sogar Publikationen zur Gesamtplattform wurden von Sun Microsystems noch Anfang 1999 unter dem Titel »Java 1.2« veröffentlicht. Aber um es gleich vorab festzuhalten – die Veränderungen des Java-APIs (API ist die Abkürzung für Application Programming Interface -Programmier- und Anwendungsschnittstelle), die sich noch zwischen den Betaversionen des JDK 1.2 und der als Final freigegebenen Version ergeben haben, erzwangen eine solche Namenspolitik fast unweigerlich. Es war leider so, dass beiJava 2 Kompendium
29
Kapitel 1
What's new? spielsweise das API der dritten Betaversion 1.2 und das 1.2/2.0-Final-API extrem viele Unterschiede aufwiesen. Der Oberbegriff »Java 2« macht den Break zu den Vorgängerversionen (insbesondere den inkompatiblen Betaversionen) deutlich. Mit der Einführung des JDK 1.2 und der Java-2-Plattform hatte Sun endlich einen Stand geschaffen, von dem aus es weitgehend um die Verbesserung der Stabilität (ein rigoroses Qualitätssicherungsprogramm) und Performance (Vervollkommnung der virtuellen Maschine) ging. Davor lag der Hauptfokus auf dem Ausbau und der Entwicklung von neuen Features und der Beseitigung von Schwachstellen. Zwischen dem JDK 1.2 und dem Mitte 2000 eingeführten JDK 1.3 (Codename Kestrel) hingegen sind die Unterschiede nicht mehr so gravierend, weshalb Java 2 ohne gravierende Probleme als Obermenge für beide JDK-Welten fungieren kann. Vor allem sind die Veränderungen von dem JDK 1.3 fast nur als echte Erweiterungen und Verbesserungen zu sehen, die keine gravierenden Inkompatibilitäten zum JDK 1.2 nach sich ziehen. Dementsprechend werden das JDK 1.3 wie auch das JDK 1.2 und die dazwischen liegenden Versionen JDK 1.2.1 und 1.2.2 zur Java-2-Plattform gezählt. Die vielleicht bedeutendste Erweiterung des JDK 1.3 ist die Einführung der HotSpotTechnologie. Diese beinhaltet im Wesentlichen eine so genannte adaptive Compilertechnologie, bei der interpretierte Programmteile bei Bedarf zur Ausführungszeit des Programms in Maschinencode übersetzt werden kann. Dazu zählt ebenso eine geschwindigkeitsoptimierte virtuelle Maschine für den Client und die zugehörigen, mit dem JDK 1.3 neu eingeführten neuen Optionen bei einigen Programmen des JDK. Der Mischmasch der Versionen führt bei vielen Betrachtern zu Verwirrung. Deshalb zur Klärung – wir werden uns in dem Buch mit Java 2.0 und vor allem dem dazu gehörenden JDK 1.3 beschäftigen, aber die Aussagen gelten auch meist für das JDK 1.2. Allgemein wird das gesamte Konzept zur Entwicklung von Applikationen für Java 2 mit dem JDK 1.3 unter »Java 2 SDK« zusammengefasst (SDK steht für Software Development Kit). Wesentlicher Unterschied zwischen der vollständigen Java-Plattform und dem JDK ist, dass die Plattform weitere APIs, Programme, Tools und Konzepte enthält. Hierzu zählen das JSDK (Java Servlet Development Kit), das bis zur Beta 4-Version des JDK 1.2 noch zu diesem gezählt hatte, oder auch andere Tool-Pakete, die die Funktionalität des JDK erweitern. Die aktuelle Java-2-Plattform und das Java 2 SDK (Java 2 Software Development Kit) beinhalten unter anderem folgende Basiselemente: Sichere und signierte (d.h. eindeutig identifizierbare) Applets Ein Collections Framework, d.h. eine einheitliche Architektur für die Darstellung und Manipulation von Collections, unabhängig von den Details ihrer Repräsentation
30
Java 2 Kompendium
Java 1.2, Java 2.0, JDK 1.2 und JDK 1.3
Kapitel 1
JavaBeans, d.h. Komponenten, die in Java erstellt sind und innerhalb von Java-Entwicklungsumgebungen verwendet werden können Die Unterstützung von Ein- und Ausgabe Verfahren zur Internationalisierung Umfangreiche Mechanismen zur Arbeit im Rahmen von Netzwerken Reflection, ein Verfahren, um in Java-Code Informationen über Felder, Methoden und Konstruktoren von geladenen Klassen verfügbar zu machen Sound- und Multimedia-Unterstützung Referenz-Objekte für den limitierten Zugriff auf die Garbage Collection (das Speichermanagement von Java) Object Serialization, um Objekte speichern und anschließend wieder seriell auslesen zu können Zugriff auf JAR-Files (Java-Archive) – ein besonderes Komprimierungsformat für Java Das Java Native Interface (JNI) für die Verbindung zu C/C++-Sourcen Sprach- und Utility-Packages Remote Method Invocation (RMI) für die Arbeit mit verteilten Anwendungen und CORBA (Common Object Request Broker Architecture), den Standard, der die Kommunikation zwischen Objekten und Programmen regelt Unterstützung von mathematischen Vorgängen mit großen Zahlen und hoher Genauigkeit Identifikation von Package-Versionen Plattformneutraler Zugriff auf Systemressourcen Der Extension Mechanism, um einfachen Zugriff auf Pakete zu gewährleisten, die nicht zu den Java-Standard-Packages gehören Die Version 1.3 des Java-2-SDK und der Java-2-Laufzeitumgebung beinhaltet diverse Verbesserungen zur Beschleunigung der Performance Das Konzept der zur Java-2-Plattform gehörenden Java Foundation Classes (JFC) beinhaltet folgende Details: Das Abstract Window Toolkit (AWT) zur Erstellung von einfachen grafischen Benutzeroberflächen Swing-Komponenten zur Erweiterung des AWTs Unterstützung von 2D-Graphics und -Imaging
Java 2 Kompendium
31
Kapitel 1
What's new? Das Input Method Framework für die Zusammenarbeit zwischen texteditierenden Komponenten und Eingabemethoden in den unterschiedlichsten Sprachen und verschiedensten Eingabemedien (Tastatur, Sprache oder auch handschriftlich per Schrift und sensitivem Display) Das Java-Accessibility-API, womit Java-Applikationen mit unterstützenden Technologien wie Spracherkennungssystemen oder Eingabebildschirmen interagieren können Drag&Drop-Datentransfer zum Datenaustausch zwischen Java- und nativen Applikationen, zwischen verschiedenen Java-Applikationen oder auch nur innerhalb einer einzelnen Java-Applikation Zu den erweiterten Features des Java-2-SDKs zählen die folgenden Konzepte: Als Erweiterung des Remote Method Invocation (RMI) die RMI-IIOPTechnik und Interface Definition Language (IDL) für die Erstellung von CORBA-kompatiblen Schnittstellen. RMI-IIOP ist eine neu eingeführte Technologie, die Java RMI und Java IDL verbindet und CORBA-kompatible Schnittstellen direkt in Java erstellen lässt. In dem neuen SDK verwendet Java IDL eine neue Version des Java-to-IDLCompilers. Dieser Compiler unterstützt einen neuen CORBA-Standard. Java Database Connectivity (JDBC) für den leichten und plattformneutralen Zugriff auf Datenbanken unter Java Das Java Naming and Directory Interface (JNDI) für die Angabe von Ressourcen (Verzeichnisse, Drucker usw.) über einen plattformunabhängigen Namen aus einer Java-Applikation heraus. Die JNDI-Architektur basiert auf einem eigenen API und einem SPI (Service Provider Interface). Neu in der aktuellen Java-2-Plattform ist, dass das vorher nur als Standarderweiterung vorhandene Interface nun voll integriert ist. JNDI beinhaltet u.a. Support für LDAP-v3-Erweiterungen sowie das Lightweight Directory Access Protocol (LDAP), den CORBA Object Services Naming Service (COS) und das Java Remote Method Invocation Registry (RMI). Das Java-2-SDK beinhaltet zudem einen Tool Support, der folgende Konzepte enthält: Die Java Platform Debugger Architecture (JPDA) zur Bereitstellung von drei Schnittstellen, die von Debuggern in Entwicklungsumgebungen verwendet werden können. Das Java Virtual Machine Debugger Interface definiert die Dinge, die eine JVM (Java Virtual Machine) zur Unterstützung bereitstellen muss. Das Java Debug Wire Protocol definiert die geforderten Formate und das Java Debug Interface die Informationen und Anworten auf User-Ebene.
32
Java 2 Kompendium
JavaSoft und Sun
Kapitel 1
Das Java Virtual Machine Profiler Interface (JVMPI) als eine experimentelle, nicht standardisierte Schnittstelle zum Profiling (Anpassen von Programmen an unterschiedliche Bedürfnisse) Die wesentlichen Neuerungen der Java-2-Plattform gegenüber den Vorgängerversionen konzentrieren sich auf die Bereiche Sicherheit, Interoperabilität mit anderen Systemen, Plattformneutralität, Geschwindigkeitssteigerung, Internationalisierung und Vereinfachung der Entwicklungstätigkeit. Prinzipiell sollte man dabei festhalten, dass schon im Schritt von 1.0.x auf die Final 1.1.x-Versionen erhebliche Veränderungen stattfanden. Leider gab es aber auch viele Veränderungen im Laufe der vier Betaversionen des JDK 1.2, weshalb diese Betaversionen in vielen Bereichen nicht mehr mit dem Finalrelease 1.2 (und natürlich erst recht nicht dem JDK 1.3) übereinstimmen. Gerade der Wechsel von der Beta-3-Version auf die wenig beachtete Beta-4-Version des JDK 1.2 beinhaltete zahlreiche und sehr massive Modifikationen, insbesondere auf Ebene des API, aber auch bei Tools. Viele Umsteiger von einem JDK 1.1.x oder einer der drei ersten Betaversionen 1.2 hatten deshalb ziemliche Umstellungsprobleme. Die wichtigsten dieser Reformen und Erweiterungen von Java und des JDK bis zu der aktuellen Version sollen im Rahmens dieses Buchs näher erläutert werden. Dabei sind die Änderungen im Detail natürlich so umfangreich, dass wir sie hier nicht allesamt ansprechen können. Glücklicherweise sind eine Menge von Veränderungen auch so speziell, dass »normale« Programmierer davon kaum berührt werden.
1.2
JavaSoft und Sun
»Verantwortlich« für Java ist Sun Microsystems (http://java.sun.com) bzw. JavaSoft – eine Tochterfirma der Firma Sun Microsystems. Wir werden im Folgenden nicht so genau zwischen Sun und JavaSoft trennen, da diese organisatorischen Strukturen für Sie nur am Rande interessant sein dürften. Wie dem auch sei – Sun und JavaSoft halten im Internet permanent die aktuellsten Informationen um Java bereit. Die wichtigsten Informationen kann man in folgende Bereiche klassifizieren: Neues und Aktuelles zu Java, Veränderungen u.ä. (http://java.sun.com/ products/jdk/1.3/relnotes.html bzw. http://java.sun.com/products/ jdk/1.3/docchanges.html) Bugs (http://java.sun.com/products/jdk/1.3/bugs.html) Tipps für Entwickler und FAQs (häufig gestellte Fragen) (http:// java.sun.com/products/jdk/faq.html) Kompatibilitätsfragen (http://java.sun.com/products/jdk/1.3/compatibility.html)
Java 2 Kompendium
33
Kapitel 1
What's new? Downloadmöglichkeiten für diverse Produkte (http://java.sun.com/ products/jdk/1.3/index.html bzw. http://java.sun.com/products/) Die
offizielle
Java-Sprachspezifikation
(http://java.sun.com/docs/
books/jls/html/index.html)
Die Spezifikation der virtuellen Maschine (http://java.sun.com/docs/ books/vmspec/2nd-edition/html/VMSpecTOC.doc.html) Informationen zur Java-Laufzeitumgebung und dem Java-Plug-In (http://java.sun.com/products/jdk/1.3/jre/)
1.3
Die Java-Neuerungen
Behandeln wir zuerst die Neuerungen, die Java im weiteren Sinn betreffen.
1.3.1
Kompatibilität und das Java-Plug-In
Im Allgemeinen gilt, dass Entwicklungen, die mit einer älteren Version von Java erstellt wurden, ebenso in der virtuellen Maschine der Folgeversionen ohne Probleme laufen sollten. Dies gilt sowohl auf binärer Ebene als auch auf Ebene des Quelltextes (allerdings definitiv ohne Gewährleistung durch Sun). Ausnahmen sind explizit diejenigen Java-Programme, die mit einer der Java-Vorgängerversionen erstellt wurden und die auf Klassen zurückgreifen, in welchen sicherheitsrelevante Löcher festgestellt wurden. Auch einige Java-Programme, die auf Klassen mit Implementations- oder Designbugs zurückgreifen, können unter neuen Versionen unter Umständen nicht mehr laufen. Solche Applikationen müssen bei einer Portierung von älteren auf eine neuere JVM neu kompiliert werden. Bei der Neukompilierung von älterem Java-Quellcode werden dann ziemliche Schwierigkeiten auftreten, wenn dort Java-Pakete importiert werden, die in dem neuen Konzept nicht mehr vorhanden sind (das ist zwar selten, aber möglich) oder verlagert und/oder umbenannt wurden (das ist leider sehr oft der Fall). Beispielsweise sind die Pakete des gesamten Swing-Konzeptes ab dem JDK 1.2 gegenüber der bisherigen Konzeption (Version 1.1 und sogar Beta 1.2) vollständig verlagert und umbenannt worden. In solchen Fällen muss in dem Java-Quelltext die import-Struktur dem neuen API natürlich angepasst werden. Sie finden dazu im Anhang eine Tabelle. Insgesamt gab es in der 1.1.1-Version als erste richtige Finalversion nach dem 1.0.x-Standard eine ganze Menge an neuen Funktionalitäten. Innerhalb der einzelnen Zwischenvarianten der 1.1-Versionen fanden dahingegen keine echten Erweiterungen statt, sondern weitestgehend nur Fehlerbereinigungen und interne Optimierungen.
34
Java 2 Kompendium
Die Java-Neuerungen
Kapitel 1
Die erste öffentliche JDK-Version 1.2 Beta wurde während des Jahreswechsels 1997/98 vorgestellt und enthielt doch einige Überraschungen. Insbesondere wurden einige 1.1-Entwicklungen bzw. Features der Vorgänger wieder zu den Akten gelegt, weil sie nicht ganz die Erwartungen erfüllt hatten (ein Sicherheitstool und einige Optionen). Dies bedeutet, dass in der Sicherheitsfunktionalität von Java eine gewisse Inkompatibilität zwischen Java 1.1.x und den Folgeversionen besteht. Zwischen der JDK-Version 1.2 und 1.3 wurden dann kaum noch solche Änderungen durchgeführt, die zu echten Inkompatibilitäten führen können. Applets Eine gewisse Schwierigkeit bei Java-Applets ist die Integration von JavaVersionen jenseits von Java 1.0.2 (!) in die gängigen Browser. Applets, die neuere APIs nutzten, setzen einen entsprechend kompatiblen Browser oder Viewer voraus. Dies war (und ist!) ein nicht ganz unbeträchtliches Problem, denn viele der heute noch im Internet verwendeten Browser unterstützen maximal Java 1.0.2, können also mit auf dem 1.1-API (oder Folgeversionen) basierenden Funktionalitäten nichts anfangen. Die meisten Programme, die Java unterstützen, wurden erst Anfang 1998 auf die Unterstützung von Java 1.1.3 upgedatet. So kann der Netscape Communicator in den Versionen 4.03-4.05 nur mit Java-1.1-Funktionalität umgehen, wenn er mit dem JDK-1.1-Patch ausgestattet ist. Ab der Version 4.06 ist JDK-1.1-Unterstützung dann teilweise (!) integriert. Der Microsoft Internet Explorer kann ab der Version 4.01 mit Java-1.1-Funktionalität umgehen (mit den üblichen Einschränkungen durch die Auffassung von Microsoft, was Java genau sein soll). Java 2.0 und das JDK 1.2 bzw. 1.3 mit den zugehörigen Klassen erzeugen nun aber erneut das Problem. Zusammenfassend muss man festhalten: Es gibt eine Vielzahl von Problemen, wenn Sie Applets mit Funktionalitäten erstellen, die über die Version 1.0.2 hinausgehen. Die ganze Problematik bedeutet dennoch nicht unbedingt, dass Sie für die Erstellung von Applets mit einem alten JDK arbeiten müssen. Sofern Applets mit einem beliebigen JDK erstellt und kompiliert wurden und nur auf Java-Standard-1.0.2 basierende Funktionalitäten nutzten, sollten sie auch in nur diesen Standard unterstützenden Browsern/Containern dargestellt werden können. Diese Abwärtskompatibilität wird allerdings von Sun nicht ausdrücklich garantiert. Das Java-Plug-In Die Java-2.0-Plattform beugt – im Gegensatz zu den Vorgängerversionen – Inkompatibilitäten mit Browsern vor und beinhaltet ein Java-Plug-In für Webbrowser (ehemals bekannt als Activator), das automatisch von der Installationsroutine des JDK bzw. der Laufzeitumgebung mit installiert wird. Mittels dieses Tools kann die Java-Laufzeitumgebung eines Browsers durch eine beliebige andere Java-Laufzeitumgebung ersetzt werden. Damit
Java 2 Kompendium
35
Kapitel 1
What's new? ist im Prinzip immer eine vollständig zu einer beliebigen JVM kompatible Laufzeitumgebung für Applets vorhanden. Das Java-Plug-In wurde erstmals mit dem JDK 1.2 bzw. der zugehörigen Laufzeitumgebung JRE 1.2 ausgeliefert und installierte unter Windows in das Startmenü eine Aufrufmöglichkeit.
Abbildung 1.1: Das Java-Plug-In, das mit dem JDK 1.2 ausgeliefert wurde
Im JDK 1.3 hat sich die Situation insoweit verändert, als dass das Java-PlugIn unter Windows nun über die Systemsteuerung zur Verfügung steht. Abbildung 1.2: Das Java-Plug-In des JDK 1.3 lässt sich über die Systemsteuerung starten
Nachdem Sie das Java-Plug-In gestartet haben, können Sie das als Laufzeitmanager konzipierte Tool über verschiedene Registerblätter konfigurieren.
36
Java 2 Kompendium
Die Java-Neuerungen
Kapitel 1
Am wichtigsten ist wohl, dass man in dem Registerblatt ADVANCED bzw. ERWEITERT verschiedene Java-Laufzeitumgebungen (alle auf Ihren Rechner vorhandenen) einstellen kann, die als Java-Plug-In für Browser fungieren. Es ist also nicht nur möglich, zwischen verschiedenen bei Ihnen aktuell auf dem Rechner installierten Java-Laufzeitumgebungen zu wechseln. Sie können darüber hinaus jederzeit die neueste Java-Laufzeitumgebung auf Ihrem Rechner installieren und diese steht dann theoretisch denjenigen Browsern zur Verfügung, die eine externe Java-Laufzeitumgebung verwenden können. Abbildung 1.3: Einstellen der Laufzeitumgebung
Leider funktioniert das Plug-In nicht bei allen Browsern und sie können sich nicht unbedingt darauf verlassen, dass die Browser mit dem 1.3-API oder sogar schon viel früher eingeführten Techniken wie Java 2D oder Swing tatsächlich zurechtkommen. Insbesondere Sicherheitsrestriktionen der Browser hebeln neuere Techniken immer wieder aus (Firewalls, keine Installationen von Plug-Ins auf Clientrechnern erlaubt, allgemeine Sicherheitsbedenken usw.). Die hauptsächlichen Probleme mit dem Java-Plug-In beruhen aber darauf, dass Sie bei Anwendern nicht voraussetzen können, dass das Plug-In und vor allem eine passende andere Java-Laufzeitumgebung auch wirklich installiert ist1. Ein Problem kann darin bestehen, dass die Verwendung des Java-Plug-Ins die Einbindung von Applets in Webseiten mittels des oder <EMBED>-Tags voraussetzt. Meist wird jedoch das einfachere <APPLET>Tag verwendet, und dann kann das Java-Plug-In nicht genutzt werden. Es gibt zwar von Sun einen vollständig in Java geschriebenen HTML-Konverter (zu laden von der Sun-Plug-In-Homepage http://java.sun.com/products/ 1
Dies betrifft z.B. Anwender mit einem Internet Explorer, die nicht zusätzlich eine JRE von Sun installiert haben (das ist meist der Fall).
Java 2 Kompendium
37
Kapitel 1
What's new? plugin), der aus einer Webseite mit dem <APPLET>-Tag eine solche mit -Tag (für den Internet Explorer) bzw. <EMBED>-Tag (für den Net-
scape Navigator) macht. (Defaulteinstellung ist, dass beide Versionen in die Webseite eingefügt werden). Dies hilft jedoch keinem Anwender unmittelbar, wenn er eine Seite mit einer <APPLET>-Referenz aus dem Netz lädt. Abbildung 1.4: Die Plug-In-Downloadseite von Sun
Auf Seite 35 finden Sie den Einsatz des Java-Plug-Ins konkret besprochen. Zusammenfassend kann man sagen, dass es theoretisch möglich ist, in einem moderneren Browser Applets zu verwenden, die die gleichen APIs nutzen wie eine eigenständige Java-Applikation. Das setzt aber beim Anwender erhebliche Faktoren voraus:
38
1.
Es muss eine passende Java-Laufzeitumgebung vorhanden sein. Die Java-Umgebung, die vom Browser mitgeliefert wird, reicht in der Regel nicht aus. Entweder ist also die Laufzeitumgebung des Betriebssystems ausreichend oder der Anwender muss explizit eine Laufzeitumgebung nachinstalliert haben. Das kann man bei den meisten Anwendern definitiv nicht voraussetzen.
2.
Das Java-Plug-In muss vorhanden sein oder zumindest bei Bedarf nachinstalliert werden können. Letzteres wird in professionell gemanagten Netzwerken mit Firewall und zentraler Verwaltung natürlich verhindert. Aber auch im Bereich der Standalone-Rechner verhindern viele
Java 2 Kompendium
Die Java-Neuerungen
Kapitel 1
fortgeschrittene Anwender diese Nachinstallation (Sicherheits- und Stabilitätsbedenken, unerwünschter Ressourcenverbrauch auf dem eigenen Rechner, hohe Downloadkosten usw.). 3.
Der Ersteller einer Webseite muss das veraltete und – im Vergleich zum <APPLET>-Tag – recht komplizierte <EMBED>-Tag zum Einbinden von Applets verwenden, wenn er den Navigator unterstützen will. Der Internet Explorer verlangt gar das -Tag, das die meisten Anwender wegen seiner ActiveX-Verbindung generell nicht akzeptieren. Sollen beide Browservarianten unterstützt werden, müssen (wie in unserem Beispiel gezeigt) beide Varianten in einer Webseite notiert und getrennt werden. Und damit hat man ältere Browser und einige Browser von anderen Herstellern explizit ausgeschlossen.
In einem Intranet können die genannten Faktoren leicht vorausgesetzt werden, weshalb dort Java-Applets voll ausgereizt werden können. Im Internet wird es noch einige Zeit dauern, bis die Voraussetzungen akzeptabel sind. Was heißt deprecated? Über die Entwicklung von Java wurden von Sun immer wieder Java-Elemente als »deprecated« bezeichnet, was übersetzt in etwa »missbilligt«, »veraltet« oder »verworfen« bedeutet2. Dies bedeutet aber nicht, dass Sie diese Elemente vergessen können oder nicht mehr verwenden dürfen. Diese Methoden lassen sich durchaus noch anwenden und sogar mit neueren Methoden – mit der nötigen Vorsicht – mischen. Und nicht nur das – es gibt Situationen, in denen zwingend als deprecated gekennzeichnete Techniken verwendet werden müssen! Insbesondere bei der Entwicklung von JavaApplets werden Sie normalerweise gezwungen sein, das als veraltet gekennzeichnete Eventmodell 1.0 zu verwenden. Wir werden im Abschnitt über die Ereignisbehandlung von Java sowie den Applets noch genauer darauf eingehen.
1.3.2
Interoperabilität
Es gibt mittlerweile zahlreiche Java-APIs, nicht nur von Sun, sondern von diversen Herstellern, die sich mit Java beschäftigen. Um das Zusammenspiel zwischen diesen verschiedenen APIs zu gewährleisten (die so genannte Interoperabilität), wurde das Java Collection Framework, eine formalisierte Schnittstelle zum Einbinden von neuen Java-Technologien, definiert. Dieses gehört in der Java-2-Plattform zu den Kerntechnologien.
2
Das gleiche Verfahren findet man auch bei HTML.
Java 2 Kompendium
39
Kapitel 1
What's new?
1.3.3
Anpassung einer Applikation an verschiedene Länderstandards
Eine der wichtigsten Neuerungen im Sprung von den 1.0.x-Versionen aufwärts war die mögliche Anpassung einer Java-Applikation an verschiedene Länderstandards. Eine Applikation kann so beispielsweise landesspezifische Schreibweisen berücksichtigen, wie zum Beispiel die Zeit oder das Datum. Des Weiteren ist es nun erlaubt, Texte in Menüs und Dialogen in verschiedenen Sprachen auszugeben. Der 16 Bit große Unicode kann vollständig angezeigt werden, soweit dieser bisher mit Zeichen gefüllt ist (da ist noch viel Platz). Dieser Vorgang, der von Sun mit Internationalisierung beschrieben wird, betrifft im Wesentlichen das java.text-Package und das java.ioPackage. Das zugehörige, in der Version 1.1 neu hinzugekommene Programm native2ascii konvertiert native-verschlüsselte 8-Bit-Zeichen in Unicode.
1.3.4
Erweiterung der Sicherheitsschnittstelle von Java
Java ist eine der sichersten Programmiersprachen bzw. Plattformen der EDV-Welt. Aber natürlich nicht perfekt. In der Geschichte von Java wurden – wie bei jeder vergleichbaren Umgebung – immer wieder Sicherheitslücken aufgedeckt. Die immer wieder nachgeschobenen kleinen Releasewechsel beseitigten nach und nach bekannt gewordene Sicherheitslücken (so etwa 23 der 24 in der Version 1.1 im Frühsommer 97 durch Forscher der University of Washington im Rahmen des Kimera Project entdeckten Sicherheitsrisiken) und sonstige Bugs. Eine wichtige Erweiterung der ersten Java-Plattform betraf die Sicherheitsschnittstelle von Java. Diese verbindet ab der Version 1.1 so genannte Low-Level- und High-Level-Sicherheitsfunktionalität. So können ab diesem Release Applets und Daten mit einer digitalen Unterschrift versehen werden. Das Konzept wurde im Laufe der Updates noch erweitert um abstrakte Schnittstellen für die Verwaltung von Schlüsseln, das Zertifikats-Management und die Zugriffskontrolle. Spezifische APIs zur Unterstützung von X.509 v3-Zertifikaten und anderer Zertifikatformate sowie eine umfangreiche Funktionalität im Bereich der Zugriffskontrolle sind weitere Sicherheits-Highlights der neuen JDK-Versionen. Seit der JDK 1.1-Version ist im JDK-Paket ein Tool enthalten, mit dem Sie so genannte Java-Archive (Erweiterung .jar) signieren können. JARDateien sind neu in der Version 1.1 eingeführt worden und fassen einzelne Java-Dateien zu einem Paket zusammen – später mehr dazu. Der Appletviewer erlaubt jedem aus dem Netz geladenen Applet in Form einer JARDatei, die als vertrauenswürdig eingestuft und mit diesem Tool entsprechend signiert wurde, mit denselben Rechten auf dem lokalen Rechner zu
40
Java 2 Kompendium
Die Java-Neuerungen
Kapitel 1
laufen wie eine lokale Applikation. Dies hat extrem weitreichende Konsequenzen, denn ein solches Applet ist nicht mehr Bestandteil des »Laufstalls«, in den das Java-Sicherheitsmodell Applets normalerweise zwingt. Das zugehörige Java-Sicherheitsprogramm der JDK-Version 1.1 heißt javakey. Dieses Tool ist als eine der bedeutendsten Neuerungen in der Version 1.2 durch die Programme keytool und jarsigner ersetzt worden. Im JDK 1.3 wurden die beiden Tools nochmals um eine neue Option erweitert (-provider). Daneben wurden bereits im JDK 1.2 die Sicherheitsprogramm jar und policytool hinzugefügt. Das JDK 1.2 erweiterte den Umfang der Tools, die von dem Sicherheitsmodell von Java betroffen sind, in der Finalversion um ein weiteres Programm (oldjava). Dies ist zwar nur ein Interpreter, aber dessen Hauptfunktion ist es, Applikationen zu unterstützen, die über einen eigenen Security Manager verfügen, der auf dem originalen Sicherheitsmodell von Java 1.0/1.1 basiert. Dieser wird unter der Java-2-Plattform eine Exception auswerfen und nicht starten. Solche Applikationen können mit dem Programm oldjava als Interpreter jedoch weiterverwendet werden.
1.3.5
Verschlüsselung in Java
Die Sicherheitsschnittstelle von Java bietet seit der Version 1.1 eine Unterstützung für die so genannte Public-Key-Verschlüsselung. Bekanntestes Beispiel für eine Public-Key-Verschlüsselung ist wohl PGP (Pretty Good Privacy). Die Public-Key-Verschlüsselung ist eine relativ neue und extrem zuverlässige Verschlüsselungsvariante, die mit zwei Schlüsseln arbeitet: einem privaten Schlüssel, der nur beim Sender verbleibt, und einem öffentlichen Schlüssel, der verschickt wird. Diesen zweiten Schlüssel darf jeder kennen, denn er wird ausschließlich zum Kodieren einer Nachricht benutzt. Zum Dekodieren kann er nicht verwendet werden. Die Folge ist, dass bis jetzt nur der Empfänger des Schlüssels an den Sender des Schlüssels eine kodierte Botschaft senden kann, die ausschließlich dieser dann mit seinem privaten Schlüssel dekodieren kann. Will der potenzielle Sender eine kodierte Nachricht verschicken, muss er also erst von dem potenziellen Empfänger den öffentlichen Schlüssel erhalten. Dieses Verfahren hat den riesigen Vorteil, dass der Dekodierungsschlüssel niemals verteilt werden muss und es äußerst sicher ist. Nachteil ist andererseits unleugbar, dass man einen großen Aufwand mit dem Verschicken und Verwalten der verschieden Kodierungs-Schlüssel betreiben muss. Durch die Kombination der digitalen Unterschrift und der Public-Key-Verschlüsselung können selbst sensible Daten in einer Java-Applikation verarbeitet und im Internet bereitgestellt werden.
Java 2 Kompendium
41
Kapitel 1
What's new? Zu der Funktionsweise von Verschlüsselungsverfahren im Allgemeinen finden Sie im Anhang einen Beitrag, der auf einige Grundlagen eingeht.
1.3.6
Das Abstract Window Toolkit
Als ein großer Nachteil von Java in der Version 1.0 wurde oft angeführt, dass Java durch seine Plattformunabhängigkeit für alle Architekturen und Betriebssysteme Vorsorge treffen muss und deshalb sehr oft nur den kleinsten gemeinsamen Nenner aller Fähigkeiten der einzelnen Plattformen nutzen kann. Java-Applets können immer nur die Möglichkeiten verwenden, die in ihrer internen Laufzeitbibliothek verankert sind (z.B. die Grafik-, Kommunikations-, Multithreading-Klassen bzw. die Klassen für die grafische Benutzeroberfläche). Da die Klassenbibliothek für den Entwurf einer grafischen Benutzeroberfläche – das AWT (Abstract Window Toolkit) – in der Version 1.0 relativ klein und allgemein gehalten wurde, konnten die Applets zu dieser Zeit nur eine Auswahl der unter Windows bekannten Elemente bieten. Hauptargument der Kritiker war, dass auf der WINTEL-Plattform (Windows und INTEL) basierende Computer die verbreitetsten Rechner im Internet sind (über 80 % der angeschlossenen Rechner) und Java für weniger als 20 % der Internetteilnehmer die vollständige Windowsfunktionalität aufgab. Das AWT hat in der Version 1.1 sehr weitreichende Erweiterungen erfahren. Dazu zählen eine einheitliche Druckerschnittstelle, damit Applikationen darüber plattformunabhängig drucken können (selbst dies war in der Version 1.0 noch nicht gegeben), aber auch schnelleres und einfacheres Scrolling, bessere Grafikmöglichkeiten sowie flexiblere Font-Unterstützung. Eine Unterstützung von Pop-up-Menüs und der Zwischenablage waren weitere wichtige Erweiterungen. Das 1.2/1.3-API hat gerade hier noch einmal zugelegt und viele weitere neue Funktionalitäten und Erweiterungen folgen lassen. Das neue Paket beinhaltet die erste vollständige Implementation der Java Foundation Classes (JFC), einem Satz von APIs und vorkompilierten Komponenten. Die JFC schließt nun Java 2D zur Erstellung von 2D-Grafiken, UI Components (Swing Package), Zugriffsmöglichkeiten auf diverse Hilfstechnologien (Accessibility), Drag&Drop und Application Services ein. Insbesondere das Swingkonzept erlaubt statt der davor recht rustikalen Oberfläche von JavaAnwendungen ein mehr dem durch Windows geprägten Massengeschmack angepasstes Aussehen. Das in der Version 1.0 noch sehr starr ausgelegte Eventhandling (also die Reaktion auf Ereignisse) wurde bereits für die Java-Version 1.1 völlig überarbeitet und wurde damit wesentlich flexibler. Das 1.1-Eventmodell arbeitete als Neuerung mit so genannten Delegates, d.h. Objekten, die die »Delegierten« von anderen Objekten sind. Ein Fenster erhält zum Beispiel ein 42
Java 2 Kompendium
Die Java-Neuerungen
Kapitel 1
Delegate-Objekt, an das dann eine Menge von Methoden geschickt wird, die das Delegate dann abarbeiten kann. Je nach dem Rückgabewert der Methode in dem Delegate wird das Fensterobjekt dann unterschiedlich reagieren. Das Delegate-Konzept an sich ist nicht neu und bereits ziemlich ausgereift, denn es wird schon seit Jahren unter dem Betriebsystem Nextstep, aber auch unter Windows NT und in der Entwicklungsumgebung OpenStep verwendet. Die Win-32-lmplementation des AWT wurde ebenfalls komplett überarbeitet, da die alte Version zu langsam war und viele Inkonsistenzen im Vergleich mit anderen Plattformen hatte. Damit war bereits das AWT 1.1 insgesamt erheblich schneller und stabiler geworden. Zu der Erweiterung des AWTs zählt gleichfalls das so genannte Input Method Framework. Darunter versteht man die Eingabe und Verwendung von Text, der in beliebigen Sprachen (etwa Japanisch, Chinesisch oder Koreanisch) geschrieben sein kann. Das Besondere daran ist, dass diese Zeichen über Tastenkombinationen eingegeben werden können. Sie können Tausende von Zeichen aus den unterschiedlichsten Sprachen mit Tastenkombinationen erzeugen und Java kann damit umgehen (sowohl bei der Eingabe, aber auch im AWT allgemein oder anderen Ausgaben). Die Technik ist wichtig, um Geräte zu unterstützen, denen einige Tasten fehlen (etwa Handheld-Computern). Damit wird ein wichtiger Schritt in Richtung Plattformunabhängigkeit gemacht.
1.3.7
Java-Archive
Netscape hatte bereits im Navigator der Version 3.0 das Packen mehrerer Java-Kompilate (also .class-Dateien) in eine Datei implementiert. Umgesetzt hat Sun diese Idee seit der Version 1.1 in den so genannten JARDateien (Java-Archive sind Dateien mit der Erweiterung .jar). In diesen JAR-Archiven können neben .class-Dateien gleichermaßen Image- und Sound-Dateien verpackt werden. Statt vieler einzelner und unkomprimierter Dateien wird beim Laden aus dem Netz ein gepacktes Archiv übertragen. Der Vorteil dieses Konzepts liegt darin, dass die Applikation in einer einzigen HTTP-Transaktion übertragen wird und nicht in vielen einzelnen Übertragungsschritten. Natürlich kann durch die Komprimierung der Daten zusätzlich die Transferzeit erheblich gesenkt werden. Die JAR-Funktionalität wird in dem ab der Version 1.1.x neu hinzugekommenen Package java.util.jar realisiert. Das zugehörige Java Archive Tool heißt jar. Das Tool wurde für das JDK 1.3 nochmals erheblich überarbeitet und arbeitet gegenüber den Vorgängerversionen bedeutend schneller.
Java 2 Kompendium
43
Kapitel 1
What's new?
1.3.8
Datenbankunterstützung
In Version 1.0 war für Java-Applikationen noch keine Datenbankunterstützung implementiert, was sich ab der Version 1.1 änderte. Das JDK bot nun die Möglichkeit, auf SQL-Datenbanken zuzugreifen. Bemerkenswert an dem JDK 1.2 waren insbesondere die neuen Zugriffsmöglichkeiten auf SQL-Datenbanken (inklusive SQL-3-Datentypen). Dazu dient auf der Seite von Java 2.0 die JDBC-2.0-Schnittstelle (Java Database Connectivity). Diese wiederum greift über die standardisierte ODBC-Schnittstelle auf eine SQLDatenbank zu. Die ODBC-Schnittstelle (Open Database Connectivity) stammt von Microsoft und basiert auf dem gleichen Konzept wie JDBC – dem X/Open SQL CLI (Call Level Interface). In dem Package java.sql steckt die wesentliche JDBC-Funktionalität von Java.
1.3.9
Verteilte Programmierung und Object Serialization
Im Bereich der verteilten Programmierung entwickelte sich Java über die verschiedenen Versionen erheblich. Das Remote Method Invocation Interface (RMI) bietet die Möglichkeit, beliebige Java-Klassen, die auf einer anderen virtuellen Maschine laufen, anzusprechen. Dabei ist es egal, ob die virtuelle Maschine lokal vorhanden ist oder irgendwo im Internet ausgeführt wird. Die Erweiterung des RMI-Konzeptes 1.2 erlaubt es nun, Objekte anhand einer Referenz zu reaktivieren, wenn diese zuvor persistent (dauerhaft gespeichert) gemacht wurden (Remote Object Activation). Zwei neue Programme, rmic – der Java RMI Stub Compiler, der übrigens im JDK 1.3 diverse neue Optionen hinzugefügt bekam – und rmiregistry (Java Remote Object Registry) dienen ab der Version 1.1.x zur programmiertechnischen Umsetzung des RMI-Konzeptes. Neu im JDK 1.2 ist das Tool rmid (Java RMI Activation System Daemon). In diesem Zusammenhang wurde gleichfalls das Konzept der so genannten Object Serialization aufgenommen. Es ermöglicht das Abspeichern der Inhalte eines Objekts in einen Stream. Dieser kann z.B. eine beliebige Datei sein. Ein Objekt kann in einem Stream zwischengespeichert und zu einem späteren Zeitpunkt daraus wieder aufgebaut werden. Die Lebensdauer eines Objekts kann also über die eigentliche Laufzeit eines Programms hinaus verlängert werden. Hauptanwendung hierfür ist das Versenden von Objekten über das Netzwerk im Zusammenhang mit dem RMI-Konzept. Das Package java.io ist von den entsprechenden Erweiterungen betroffen. Das JDK 1.3 erlaubt es mittlerweile, dass auch Strings, die länger als 64 KByte sind, serialisiert werden können. Das Object Serialization Tool für Java heißt serialver. 44
Java 2 Kompendium
Die Java-Neuerungen
1.3.10
Kapitel 1
Java IDL
Als eine logische Fortsetzung dieser RMI-Entwicklung folgte die Java IDL (Interfaces Definition Language), die es Java ermöglicht, eine Verbindung zu anderen Verteilungsplattformen wie zum Beispiel CORBA aufzubauen, d.h. entfernte Schnittstellen über IDL zu definieren. IDL ist eine Definitionssprache, die die Kommunikation zwischen verschiedenen Programmiersprachen über Schnittstellen ermöglicht und in das CORBA-Konzept (Common Object Request Broker Architecture) integriert. CORBA liegt derzeit in der Version 2.0 vor und ist ein plattformunabhängiger Standard, der die Kommunikation von Objekten im Netzwerk definiert. Java 2.0 bietet vollständigen CORBA-Support, indem der CORBA-Standard mithilfe des CORBA IDL Compilers integriert wird. Die Internetadresse und einige andere interessante Links zu CORBA folgen in der Tabelle: URL
Beschreibung
http://siesta.cs.wustl.edu/~schmidt/ corba.html
Detaillierte Informationen zu CORBA
http://siesta.cs.wustl.edu/~schmidt/ corba-urls.html
Eine CORBA-Link-Sammlung
http://www.sun.com/
Die Homepage von Sun lässt ebenfalls eine Suche nach dem Begriff CORBA zu.
Tabelle 1.1: CORBA-Quellen
Seit der Finalversion des JDK 1.2 haben sich in der Java IDL zahlreiche Veränderungen ergeben, die im Wesentlichen auf Grund der 2.3 OMGSpezifikation notwendig waren. Das neue API des JDK 1.3 berücksichtigt die Neuerungen mit neuen Paketen bzw. Erweiterungen der bestehenden OMG-Pakete.
1.3.11
JavaBeans
In den letztgenannten RMI-Kontext fallen ebenso die so genannten JavaBeans – Java-Komponenten innerhalb eines Objektmodells. JavaBeans ist ein portables, plattformunabhängiges, in Java geschriebenes Komponentenmodell, das von JavaSoft in Kooperation mit führenden Industrieunternehmen der Computerbranche (klangvolle Beispiele sind Apple, Borland, IBM, JustSystem, Microsoft, Netscape, Rogue Wave, SunSoft, Symantec und viele andere) entwickelt wurde. Erstmals wurde das Modell am 4. September 1996 im Internet als Konzept vorgestellt, die konkrete Realisierung dauerte natürlich länger. Die JavaBeans sind eine der wichtigsten Erweiterungen des Java-SDK 1.1 (SDK steht für Software Development Kit). In das JDK 1.2 gehörte die nächste Generation der JavaBeans (Codename Glasgow) als fes-
Java 2 Kompendium
45
Kapitel 1
What's new? ter Bestandteil. Vergleichbar sind JavaBeans mit ActiveX-Controls oder OCX-Controls. Sie können wie diese visuell manipuliert werden und bieten im Allgemeinen über so genannte Bridges eine Schnittstelle zu Komponenten wie ActiveX-Controls oder OLE-Komponenten an. Im Gegensatz zu diesen Komponenten sind JavaBeans jedoch explizit plattformunabhängig und vor allen Dingen im Sicherheitskonzept von Java integriert. Häufig wird gefragt, ob JavaBeans nicht dasselbe wie Applets sind. JavaSoft gibt als wesentlichen Unterschied die Möglichkeit der visuellen Erstellung und eine relativ eng ausgelegte Zielfunktionalität von Beans an. Allerdings stellt JavaSoft ebenso fest, dass Applets so entwickelt werden können, dass sie wie Beans aussehen und arbeiten. Um JavaBeans entwickeln und dann zu komplexen Anwendungen kombinieren zu können, musste ein eigenes Beans Developement Kit (BDK 1.0) von Sun dem JDK 1.1 (und folgende) beigefügt werden. Dieses gehört nicht zum JDK 1.2 und ist eine der Erweiterungen, die den Unterschied zwischen der vollständigen Plattform und dem JDK ausmachen. Mittlerweile sind alle wichtigen kommerziellen Java-Entwicklungstools in der Lage, JavaBeans zu erstellen. Einmal entwickelte Beans werden üblicherweise in die oben beschriebenen JAR-Dateien verpackt und beim Enduser in das Applet oder die Applikation integriert.
1.3.12
Servlets
Das Java Servlet API erlaubt die Erstellung von so genannten Java Servlets. Eingeführt wurde diese Technologie während der 1.1.x-Upgrades und richtig manifestiert in der Version 1.2. Allerdings zählt im Rahmen der Java-2Plattform auch das Servlet-API (javax.servlet.*) nicht mehr zum JDK (wie noch bis zur JDK-1.2-Beta-3-Version), sondern gilt als eigenständiges Produkt – das Java Servlet Development Kit. Es ist wie das JDK frei von Sun zur Verfügung gestellt und kann von der Servlet Product Page unter http:// java.sun.com/products/servlet/index.html heruntergeladen werden. Servlets bedeuten eine Verlagerung von Funktionalität vom Client auf den Server. Die Assoziation zu CGI-Scripts ist naheliegend. Servlets verbrauchen jedoch viel weniger Ressourcen und sind – da sie in Java geschrieben sind – im Gegensatz zu CGI-Scripts plattformunabhängig.
1.3.13
Inner Classes und Anonymous Classes
Neu in 1.1 wurden die so genannten Inner Classes eingeführt. Klassen und Interfaces können damit innerhalb anderer Klassen eingebettet werden. Solche Klassen werden innere Klassen genannt. Sie können ausschließlich die Klassen unterstützen, in die sie integriert sind.
46
Java 2 Kompendium
Die Java-Neuerungen
Kapitel 1
Damit unterstützt Java die bisher schon bei vielen Programmiersprachen praktizierte Blockstruktur innerhalb einer Klasse bzw. das dort verwendete Konstrukt. Anonymous Classes steht für eine Abart der inneren Klassen. Es handelt sich um eine Kurzform von inneren Klassen. Sie haben allerdings keinen Namen, nur eine Implementation mit new. Der Compiler generiert bei den anonymen Klassen eine namenlose Klasse, die wie spezifiziert eine bestehende Klasse dann überschreibt. Das Ende einer anonymen Klasse wird durch das Ende des mit new eingeleiteten Ausdrucks festgelegt.
1.3.14
Native Methoden
Das API, um C-Implementationen für Java-Methoden zu schreiben, ist seit der Version 1.1.x komplett gegenüber der Version 1.0.x geändert worden. Das Mapping wurde einheitlich geregelt und es ist nur ein innerer Bestandteil der Java Virtual Machine. Das Java Native Interface (JNI) ist viel einfacher anzuwenden und zu lernen als das davor verwendete Konzept. Das JNI ist eine Standardprogrammierschnittstelle, um native Java-Methoden zu schreiben und die JVM in native Applikationen einzubetten. Das primäre Ziel ist die binäre Kompatibilität von nativen Methodenbiliotheken über alle JVM-Implementationen auf den unterstützten Plattformen. JDK 1.2 erweiterte gegenüber dem JDK 1.1 das Java Native Interface mit einige neuen Features, es bleibt jedoch weitgehend gleich. Wichtig ist, dass es nun getrennte Unterstützung von dem Impelemntation von Native-Code über das JNI und älteren Schnittstellen gibt. Der resultierende Code ist erheblich effizienter als zuvor.
1.3.15
Reference Objects
Neu im JDK 1.2 wurde das Reference-Objects-Konzept eingeführt. Darunter versteht man, dass die Referenz auf ein Objekt wie ein Objekt selbst behandelt wird und damit genauso manipuliert und untersucht werden kann (siehe auch RMI). Dies funktioniert sogar noch dann, wenn auf das Objekt selbst gar nicht mehr zugegriffen werden kann (etwa, weil der Garbage Collector das Objekt gelöscht hat). Eine potenzielle Anwendung für diese Referenzobjekte ist der Aufbau eines einfachen Caches.
1.3.16
Multimedia-Unterstützung in Java 2
Multimedia nimmt in der Java-2-Plattform großen Raum ein. Mit der neuen Java-Version wird beispielsweise die Audio-Unterstützung erweitert. Sowohl in Applikationen als auch in Applets lassen sich nun MIDI-Dateien (Typ 0 und Typ 1) sowie RMF-, WAVE-, AIFF-, und AU-Dateien in hoher
Java 2 Kompendium
47
Kapitel 1
What's new? Tonqualität abspielen und zusammenmischen. Das JDK 1.3 stellt insbesondere für die Arbeit mit MIDI-Dateien einige neue Pakete zur Verfügung. Das vollständige Java Sound API bleibt bei dieser Erweiterung weitgehend erhalten, nur die Engine wurde ausgetauscht. Sie wird als eigene Sammlung ausgeliefert. Allgemein wird die Multimedia-Unterstützung von Java immer mehr in ein eigenes Framework verlagert. Zu der Java-2-Plattform zählt als eigenes Paket das Java Media Framework (JMF), das eine sehr komfortable Verbindung von verschiedenen Mediatypen mit Java-Applets und -Applikationen erlaubt (http://java.sun.com/products/java-media/jmf/). Das JMFAPI unterstützt eine einfache und vereinheitlichte Synchronisation, Kontrolle, Verarbeitung und Präsentation von komprimierten, Zeit-basierenden Mediadaten. Dies beinhaltet sowohl Javastreams als auch MIDI, Audio und Video auf allen Java-Plattformen.
1.3.17
Die Java-2D-API-Referenz
Das im JDK 1.3 enthaltene 2D-API bietet umfangreiche Klassen für die Erstellung und Entwicklung von grafischen Applikationen und Applets. Der Anspruch dieses APIs geht erheblich über das hinaus, was davor in der Grund-Java-Grafik vorgesehen war. So stehen dort primitive Grafikformen (etwa die verschiedensten Geometrieformen) sowie transparente Images (Alpha Channel) direkt als vorgefertigte Methoden zur Verfügung. Die 2DAPI lässt sich zudem zur elektronischen Bildbearbeitung oder für Präsentationen in Java verwenden. Leider muss man festhalten, dass das Java-2D-API seit der Finalversion des JDK 1.2 vielfach inkompatibel zu den davor verbreiteten Java-2D-APIs ist (sogar späten JDK-1.2-Betaversionen). Es gibt diverse Methoden, Felder und ganze Klassen, die nicht nur verlagert, sondern teilweise vollständig abgelöst wurden. Und nicht etwa, indem sie als deprecated erklärt wurden wie bei sonstigen Bestandteilen des JDK-APIs. Es gibt sie teilweise einfach nicht mehr. Man findet keinen Hinweis darauf, dass sie in einer Vorgängerversion verwendet wurden und was jetzt als Ersatz für die Funktionalität zu verwenden ist. Wenn Sie beispielsweise die Klasse java.awt.font.StyledString – eine finale Subklasse von font in der Beta 3-Version des JDK 1.2 – suchen, werden sie in der gesamten Dokumentation des JDK 1.2 bzw. 1.3 keinen Hinweis mehr darauf finden. Zwar werden das Konzept von Java 2D und die allgemeinen Veränderungen über die einzelnen Versionen davon in der offiziellen Dokumentation recht ausführlich behandelt. Dies hilft aber herzlich wenig, wenn Sie einen Java-Quelltext nach dem alten Konzept erstellt haben, ihn mit dem neuen Compiler übersetzen und nur die nichtssagende Fehlermeldung erhalten, ein bestimmtes Element sei nicht vorhanden. Es gibt keinen Hinweis auf eine Veränderung der API, und der Anwender steht da, als ob er einen Fehler 48
Java 2 Kompendium
Die Java-Neuerungen
Kapitel 1
gemacht hätte. Obwohl das neue Java-2D-Konzept mit einigen Detailmängeln der Vorgängerversionen aufgeräumt hat (insbesondere in der Darstellung des Hintergrundes bei Rotationen), werden viele Anwender ihren Wortschatz an Flüchen herauskramen, wenn bisher fehlerfreie Quelltexte scheinbar ohne Grund nicht mehr zu übersetzen sind.
1.3.18
Swing JDK 1.2/1.3 und die Java Accessibility API
Swing ist der Teil der Java Foundation Classes (JFC), der einen vollständigen Satz von GUI-Komponenten (Graphical User Interface = grafische Benutzerschnittstelle) implementiert. Das Look and Feel von Java-Benutzerschnittstellen passt sich immer mehr den Standards der GUI-Welt an. Das Swing-Konzept geht jedoch noch weiter. Swing ist vollständig in reinem Java entwickelt und dort ohne Widersprüche implementiert. Swing basiert auf dem JDK 1.1 Lightweight UI Framework und versetzt Sie in die Lage, einen Satz von GUI-Komponenten zu entwickeln, die sich automatisch zur Laufzeit dem passenden Look and Feel für jede Betriebssystemplattform (Windows, Solaris, Macintosh) anpassen – wenn Sie es wollen. Swing-Komponenten beinhalten alle bereits vorher existierenden AWT-Komponentensätze (Button, Scrollbar, Label usw.) sowie einen großen Satz von HigherLevel-Komponenten (Baumansicht, Listboxen usw.). Mit dem Java Accessibility API (ab 1.2) können Entwickler Java-Applikationen generieren, die mit innovativen Hilfstechnologien (etwa Spracheingabesystemen oder Blindensprache-Terminals) interagieren können. Die daraus resultierenden Applikationen sind nicht auf bestimmte technische Plattformen beschränkt, sondern können auf jeder Maschine eingesetzt werden, die die virtuelle Javamaschine unterstützt. Die neue Java Accessibility API ist nun eines der Kernbestandteile der Java Foundation Classes. Die Finalversion des JDK 1.2 hat bezüglich der Swing- und AccessibilityPackages eine erhebliche Veränderung gegenüber den Vorgängerversionen (auch den Betaversionen des JDK 1.2 bis Beta 3) erfahren. Die vorher in dem Namensraum com.sun.java.* (oder kurz java.*) untergebrachten Pakete wurden samt und sonders in den Namensraum javax.* verlagert. Außerdem wurde die Paketstruktur erheblich verändert. Die alte SwingAPI-Referenz (vor dem JDK 1.2 Final) sah wie folgt aus: java.awt.swing java.awt.swing.basic java.awt.swing.beaninfo java.awt.swing.border java.awt.swing.event java.awt.swing.jlf java.awt.swing.motif java.awt.swing.multi
Java 2 Kompendium
49
Kapitel 1
What's new? java.awt.swing.plaf java.awt.swing.table java.awt.swing.target java.awt.swing.text java.awt.swing.undo
Die ehemalige Accessibility-API.Referenz war Folgende: java.awt.accessibility
Ab dem JDK 1.2 Final und im JDK 1.3 gilt (inklusive der anderen in dem Namensraum javax.* vorhandenen Pakete) folgende Struktur (auch die wurde gegenüber dem JDK 1.2 Beta erweitert): javax.accessibility javax.naming javax.naming.directory javax.naming.event javax.naming.ldap javax.naming.spi javax.rmi javax.rmi.CORBA javax.sound.midi javax.sound.midi.spi javax.sound.sampled javax.sound.sampled.spi javax.swing javax.swing.border javax.swing.colorchooser javax.swing.event javax.swing.filechooser javax.swing.plaf javax.swing.plaf.basic javax.swing.plaf.metal javax.swing.plaf.multi javax.swing.table javax.swing.text javax.swing.text.html javax.swing.text.html.parser javax.swing.text.rtf javax.swing.tree javax.swing.undo javax.transaction
1.3.19
Drag&Drop
Java unterstützt den Datenaustausch zwischen Programmen per Drag&Drop (kurz DnD). Das Drag&Drop-API der Java-2-Plattform zählt ebenfalls zu der JFC und ermöglicht den Datentransfer zwischen Java und 50
Java 2 Kompendium
Die Java-Neuerungen
Kapitel 1
Native-Applikationen. Die Integration in das Swingkonzept wurde bereits in den ersten Betaversionen des 1.2-JDK vollzogen. Es gab dort jedoch durch einen Fehler in dem Swing-Eventhandling in den ersten 1.2-Betaversionen noch diverse Probleme, sodass erst ab dem 1.2-Final von einer echten Implementation gesprochen werden kann. Vollständig unterstützt werden Drag&Drop für Win32 und OSF/Motif (auf Solaris), d.h., der Datentransfer innerhalb einer JVM, zwischen verschiedenen JVMs und sogar zwischen einer JVM und einer nativen Plattform, die ihrerseits DnD-fähige Applikationen beinhaltet. Vollständig implementiert, getestet und unterstützt sind u.a. DnD-Transfers von Text (ASCII, ISO8859-1, Unicode). Transfer von Text der Form X11 Window System mit COMPOUND_TEXT-Verschüsselung wird nicht unterstützt (nicht kompatibel zu Unicode). Daneben können auch Dateien und Klassen per DnD transferiert werden (bis auf wenige Situationen getestet und vollständig unterstützt). DnD von Datentypen (plattformspezifisch oder selbst definiert), Instanzen oder serialisierten Java-Objekten sind in der Java-2-Plattform ebenfalls möglich.
1.3.20
Die Java-Standardklassen und die Datei classes.zip
Zwischen der Version 1.1 des JDK bzw. den ersten drei Betaversionen des JDK 1.2 auf der einen Seite und der Finalversion 1.2ff auf der anderen Seite hat sich der Zugriff auf die Laufzeitumgebung von Java erheblich verändert. Insbesondere wird seit dem JDK-1.2-Final die Datei classes.zip nicht mehr benötigt (sie wird auch nicht mehr zur Verfügung gestellt – Ersatz ist im Wesentlichen die Datei rt.jar). Diese hatte in den ersten Versionen von Java die Standardklassen von Java enthalten und war dementsprechend zwingend notwendig. Jedoch hat sich die Laufzeitumgebung von JDK 1.2 vollständig geändert. Seit dem JDK 1.2 verwendet das JDK für den Zugriff auf die Systemklassen nun einen Bootstrap Class Loader, der seinen eigenen Pfad für die Suche der Systemklassen verwendet. Die Folge ist, dass im neuen JDK auch nur noch selbst erstellte Klassen bei Bedarf in den CLASSPATH aufgenommen werden müssen, wobei diese Bemerkung noch ausführlicher diskutiert werden muss. Das soll aber erst bei der Besprechung der JDK-Tools erfolgen.
1.3.21
Veränderungen bei den Tools
Nicht nur neue Klassen und Methoden kamen von der Java-Einführung bis hin zu der aktuellen Java-Version hinzu. Einige der neuen Java-Fähigkeiten konnten damit praktisch nutzbar gemacht werden, indem die bereits vorhandenen JDK-Programme erweitert wurden. Aber viele der neuen Funktionalitäten machten gänzlich neue Java-Tools notwendig. Die Programme
Java 2 Kompendium
51
Kapitel 1
What's new? werden ausführlich im Rahmen des Abschnitts über die JDK-Tools beschrieben. Hier ist jedoch schon einmal vorab eine Liste der Programme, die ab der Version 1.1 hinzukamen: Das Java Archive Tool (jar) Das Digital Signing Tool (javakey) – nur in der Version 1.1 vorhanden Der Native-To-ASCII Converter (native2ascii) Der Java RMI Stub Converter (rmic) Das Tool Java Remote Object Registry (rmiregistry) Das Serialisierungstool Serial Version Command (serialver) Das Konvertierungstool AWT 1.1 Conversion Tool (updateAWT) Das Programm extcheck, ein Diagnose-Tool für Jar-File-Versionskonflikte Mit oldjava bzw. oldjavaw stehen zwei Versionen eines Javainterpreters zur Verfügung, der zum Ausführen von älteren Java-Applikationen genutzt werden kann. Dies ist unter anderem dann vonnöten, wenn diese über einen eigenen Sicherheitsmanager verfügen, der auf dem alten Sicherheitsmodell von Java beruht. Das Tool tnameserv erlaubt den Zugriff auf den benannten Service. Selbstverständlich wurden auch die bestehenden Tools im Laufe der Releasewechsel immer wieder überarbeitet, sodass hier nur ein paar besonders wichtige Details genannt werden sollen. Der Java-Compiler javac wurde für das JDK 1.3 komplett überarbeitet und bzgl. der Performance verbessert. Bereits seit dem JDK 1.2 gibt es die Option -target version. Über die Versionsangaben kann festgelegt werden, auf welcher virtuellen Maschinen eine Applikation laufen soll. Der Java-Interpreter und einige weitere Tools verstehen im JDK so genannte Non-Standard-Options. Neben dem normalen Satz von Standardoptionen gibt es die Möglichkeit, einen Satz von weitergehenden Kommandozeilenoptionen zu verwenden. Diese Non-Standard-Options beginnen fast immer mit einem -X, etwa -Xdebug oder -Xverify. Wichtigster Unterschied zwischen den Standardoptionen und den Nicht-Standardoptionen ist, dass die Unterstützung für die Standardoptionen im JDK 1.2 und allen zukünftigen Versionen der JVM von Sun garantiert wird. Eine Unterstützung für die Nicht-Standardoptionen wird jedoch ausdrücklich für zukünftige Versionen der JVM nicht garantiert.
52
Java 2 Kompendium
Die Java-Neuerungen
Kapitel 1
In Bezug auf die Sicherheit von Java haben sich über die verschiedenen Versionen des JDK häufig Veränderungen ergeben. Besonders wichtig war die Ersetzung des javakey-Tools durch keytool, jarsigner und policytool im JDK 1.2.
1.3.22
Neues Lizenzierungsmodell für Java 2.0
Mit der neuen Java-Plattform 2.0 und dem JDK 1.2 wurde auch ein neues Lizenzierungsmodell eingeführt, das die Java-Entwicklung weiter vorantreiben sollte. Dieses erleichterte insbesondere die Unterstützung von nichtkommerziellen Entwicklungen. Die Idee beruhte darauf, dass der Sourcecode des Java Runtime Environment im Internet bereitgestellt wird und von Entwicklern kostenlos benutzt und für eigene Zwecke erweitert werden darf. Und dies ohne die bis dahin vorhandene Verpflichtung, die Neuerungen von Sun wieder kostenlos zur Verfügung zu stellen. Für nichtkommerzielle Anwendungen beinhaltete das Lizenzierungsmodell auch keinerlei weitere Lizenzgebühren, wohingegen bei kommerziellen Entwicklungen – aber erst beim konkreten Vertrieb dieser Produkte – weiterhin Lizenzgebühren an Sun fällig wurden. Sun machte diese neue Lizenzpolitik folgendermaßen schmackhaft: »Das neue Modell ist eine Investition in die Zukunft unserer Kunden. Unter diesem neuen Modell verdienen auch wir kein Geld, bis diese mit Java Geld verdienen.« Sun behielt sich ansonsten eine Kompatibilitätskontrolle von Weiterentwicklungen ihrer Java-Umgebung vor. Beides – die offene Lizenzpolitik auf der einen Seite und die Kontrolle von Kompatiblitätsstandards auf der anderen Seite – sollte nach Aussage von Sun eine Balance zwischen einer offenen Weiterentwicklung von Java und einem harmonischen Plattformstandard gewährleisten. Diese Lizenzpolitik erwies sich aber entgegen der Marketingaussagen von Sun nicht als Initialzündung für das JDK 1.2 und Java 2, sondern das Gegenteil war der Fall. Die Industrie konnte sich mit dem Lizenzierungsmodell überhaupt nicht anfreunden und dementsprechend wurde der Java-2Standard nur sehr langsam umgesetzt. Da das JDK 1.2 in Bezug auf Performance nicht das hielt, was man sich davon versprochen hatte, dauerte es bis zur Einführung des JDK 1.3, bis die Euphorie der ersten Java-Versionen die Java-2-Plattform erreichte. Das JDK 1.3 hat aber dann auch bei der bis dahin skeptischen Industrie für Java den endgültigen Durchbruch geschafft.
Java 2 Kompendium
53
Kapitel 1
What's new?
1.4
Zusammenfassung
Zusammenfassend wollen wir festhalten, dass Java 2.0 sehr viele Neuerungen gebracht hat. Zum großen Teil sind sie nur für fortgeschrittene Programmierer interessant, die vor allem im professionellen Bereich Anwendungen erstellen. Dazu zählen RMI, Sicherheitsaspekte, Datenbankzugriffe oder Reflection. Andere Neuerungen sind jedoch auch schon für den OttoNormal-Programmier und sogar den Einsteiger sehr interessant. Insbesondere sollte die HotSpot-Technologie des JDK 1.3 zur Beschleunigung von Java-Prozessen den letzten echten Mangel von Java beseitigt haben.
54
Java 2 Kompendium
2
Schnuppertour Java
Obwohl Java noch nicht alt ist, hat es bereits eine bewegte Vergangenheit hinter sich. So hat Java Gegner zu gemeinsamen Projekten vereint, die ohne diese revolutionäre Technik sich wahrscheinlich nie an einen Tisch gesetzt hätten. Apple und Microsoft kooperieren beispielsweise seit geraumer Zeit bei der Entwicklung ihrer virtuellen Java-Maschinen. Aber Java erzeugt auch Unfrieden. Bekanntlich laufen diverse Klagen zwischen Sun und Microsoft. Diversen Quellen zufolge ist der Java-Erfinder Sun Gegner Nummer 1 von Microsoft. Dies macht sicher deutlich, wie mächtig Java ist, welches gewaltige Sprengpotenzial sich hinter diesem kleinen Wort verbirgt. Ob in Kooperation oder ohne Partner – zahlreiche Giganten der Computerbranche setzen mittlerweile auf Java. Die Liste liest sich wie das »Who is who« der EDV-Welt. Aber auch außerhalb der EDV-Kernbranche gibt es mehr und mehr Java-Projekte: Industrie, Banken, Versicherungen, Kreditkartenfirmen. Es gibt wahrscheinlich keine Branche, wo keine großen Java-Projekte laufen. Insbesondere nach der Jahrtausendwende und dem Freiwerden der davor in Altprojekten gebundenen Ressourcen haben Java-Projekte erheblich zugenommen. Java hat sich als eine der wichtigsten Programmiertechniken der Gegenwart etabliert. Und obwohl niemand in die Zukunft sehen kann, riskiert man wohl keine Fehlaussage, wenn man Java als die Programmiersprache der Zukunft bezeichnet. Bevor wir mit Java als Programmiersprache beginnen, soll auf die Java-Welt und das Java-Umfeld im weiteren Sinn eingegangen werden. Java unterscheidet sich in vielerlei Hinsicht von anderen Programmiersprachen wie COBOL, FORTRAN, BASIC, PASCAL oder aber auch SMALL TALK und C/C++. Die Unterschiede beschränken sich nicht auf die oft als Hauptkriterium genannte Objektorientierung von Java. Die Objektorientierung gilt zwar als eine der herausragenden Eigenschaften von Java und dürfte ein entscheidender Grund für den phänomenalen Erfolg von Java sein. Die Differenzen umfassen jedoch die komplette Java-Welt und das Java-Umfeld selbst. Immerhin gab es auch schon vor Java objektorientierte Sprachen wie Lisp oder Small Talk, die allerdings nie über ein »Mauerblümchendasein« hinaus kamen. Java muss also mehr bieten als nur Objektorientierung. Java ist für eine heterogene Welt, sei es das Internet mit seinen diversen Rechnermodellen, sei es ein PC, ein Mac, ein Unix-Rechner, sei es nur eine Kaffeemaschine oder ein Videorecorder, entwickelt worden. Die damit zusammenJava 2 Kompendium
55
Kapitel 2
Schnuppertour Java hängende Plattformunabhängigkeit ist das zweite wichtige Kriterium, um die Bedeutung von Java einschätzen zu können. Deshalb ist die Beschäftigung mit der Java-Welt sicher ein guter und gewinnbringender Einstieg in die Materie.
2.1
Was ist Java?
Java ist eng mit dem Begriff Internet verbunden. Vielfach wird Java sogar als eine reine Internet-Sprache bezeichnet. So stimmt das aber nicht, zumal Java ursprünglich gar nicht auf das Internet zielte. Internet-Sprache als Charakterisierung für Java fasst einfach nicht das ungeheure Potenzial, das in der Technologie steckt. Das oft zitierte Beispiel eines Java-Betriebssystems für eine Kaffeemaschine oder eine Waschmaschine macht die allgemeinere Bedeutung sicher deutlich. Trotzdem nimmt das Internet eine zentrale Stelle in der Java-Welt ein. Vielleicht kann man das Internet etwas unseriös als »Hebamme« für die Geburt von Java bezeichnen. Wir wollen uns Java aus diesem Grund über die Geschichte des Internets näheren.
2.1.1
Das Internet
Das Internet und seine Firmenableger – die Intranets – wachsen permanent. Das Internet boomt! Der Computer als eigenständige, unabhängige Recheneinheit gehört selbst im Privatbereich der Vergangenheit an. Kontakt zu einem Netzwerk und/oder zum Internet ist die Regel. Insbesondere sind über das Internet unzählige Rechner unterschiedlichster Bauart und mit den verschiedensten Betriebssystemen zusammengeschlossen. Das World Wide Web und die anderen Internet-Dienste sind ein riesiges, geniales, dezentrales, dynamisches, unstrukturiertes und- im besten Sinn des Wortes – chaotisches System, das sämtliche Bereiche des täglichen Lebens durchdrungen hat. Eine E-Mail-Adresse und eine Homepage sind für jedes wichtigere Unternehmen Pflicht, aber auch im Privatbereich selbstverständlich. Besonders beeindruckend ist vor allem das immer noch anhaltende rasende Wachstum des Internets. Und ein Ende ist noch nicht abzusehen. Da das Internet immer mehr von Nichtfachleuten genutzt wird, kennen auch prozentual gesehen immer weniger Anwender Details davon. Früher nutzten das Internet hauptsächlich Experten. Heute sind die Experten absolut in der Minderheit. Und so kommt es, dass das Internet von Laien oft mit dem World Wide Web (WWW) gleichgesetzt wird (so wie viele Amerikaner Bayern mit Deutschland gleichsetzen und viele Deutsche Holland mit den Niederlanden :-)). Dies kommt nicht zuletzt daher, dass die Geschichte und die anderen Dienste des Internets kaum bekannt sind. Obwohl das WWW inzwischen unbestreitbar die wichtigste Säule ist, besteht das Internet natür56
Java 2 Kompendium
Was ist Java?
Kapitel 2
lich aus mehr Diensten, ist mehr als nur das populäre WWW. Das Internet ist ein gigantisches, weltweites Computernetzwerk, das die unterschiedlichsten Dienstleistungen anbietet, die vielfach weit älter sind als das WWW. Abbildung 2.1: Das News-Fenster des Netscape Navigators
Elektronische Post, Möglichkeiten von weltweiter Werbung und Repräsentation, Diskussionsforen, das Suchen von Informationen in einer riesengroßen Informationsquelle, das Ausführen von Programmen auf entfernten Computern oder das Laden von Dateien und Programmen von – über die ganze Welt verteilten – Rechnern auf den eigenen Computer und wieder zurück sind nur einige dieser Dienste. Dazu kommen Internet-Telefonie, die Verzahnung von Arbeit auf dem lokalen Rechner und permanenten Zugriffen auf Internet-Server, Online-Bankgeschäfte und einkaufen per Internet. Wer will, kann sein ganzes Leben rund um das Internet aufbauen. Viele dieser Möglichkeiten haben primär nichts mit dem WWW zu tun (obwohl sie oft darüber mittels Verweisen genutzt werden können). Im Internet gibt es natürlich nicht nur Licht. Diverse Probleme werfen dort Schatten. Gerade die Heterogenität der Plattformen und Rechner ist sicher ein Hauptproblem für Software im Internet. Andere wesentliche Probleme betreffen die Interaktion zwischen Anwendern an ihren Clients und den Servern sowie Sicherheitsfragen. Java bietet dafür und für viele andere InternetFragen einen wichtigen Lösungsansatz.
Java 2 Kompendium
57
Kapitel 2
Schnuppertour Java
2.1.2
Ein kurzer Abriss der Internet-Geschichte
Die Geschichte des Internets zeigt eines der wenigen nützlichen Resultate des kalten Kriegs der vergangenen Jahre. Die Furcht amerikanischer Militärs vor einem Atomschlag durch die ehemalige UdSSR veranlasste in den späten Sechzigerjahren die Vereinigten Staaten von Amerika, eine Organisation namens Advanced Research Projects Agency (ARPA) zu beauftragen, für das US-Verteidigungsministerium mit großem finanziellem Aufwand ein Rechnersystem zu entwickeln, das selbst nach einem atomaren Erstschlag der UdSSR noch funktionieren sollte und den Gegenschlag organisieren konnte. Erstes Resultat war im Jahre 1969 ein Forschungsnetzwerk aus vier miteinander verbundenen Großrechnersystemen. Ein zentraler Großrechner als Mittelpunkt dieses Rechnersystems kam nicht in Frage, weil bei seinem Versagen ein Gesamtausfall des Rechnersystems die Folge gewesen wäre. Kleinere und unabhängig voneinander aufgebaute Rechnersysteme mussten entwickelt werden. Dabei war es unmöglich, nur homogene Rechnersysteme zu fordern. Die Verbindung von heterogenen Rechnersystemen war notwendig. Diese Verbindung zwischen den einzelnen Systemen sollte unter allen Umständen funktionieren – sogar beim Ausfall eines beliebigen Rechners im Netz. Da feste Standleitungen mit Sicherheit bei einer solchen Katastrophe wie einem Atomschlag zerstört würden, reichten diese als einzige Verbindung nicht aus. Zusätzlich musste das Netzwerk – ursprünglicher Name ARPANET bzw. gelegentlich ARPNET genannt – über flexible Leitungen, etwa Telefonleitungen, kommunizieren können. Für eine maximale Fehlertoleranz und Sicherheit des Netzes sollten immer mehrere Wege zwischen sämtlichen Computern im Netzwerk möglich sein. Nachrichten im Netz mussten in der Lage sein, quasi selbstständig den Weg zum Adressaten zu finden. An jedem Knotenpunkt von Informationswegen sollte eine Nachricht selbstständig den optimalen freien Weg zum nächsten Knotenpunkt finden. Dies erzwang als eine Konsequenz die Zerlegung von Informationen in kleinere Datenpakete, die unabhängig voneinander verschickt werden konnten. Für die Datenhaltung auf den einzelnen Rechnern sah das ursprüngliche Konzept folgende Logik vor: Die Daten und Funktionalitäten sollten nicht nur auf einem Rechner, sondern auf allen unabhängigen Rechnern gehalten werden (eine Art Backup-Verfahren). In regelmäß igen Abständen sollten die Daten abgeglichen werden. Dabei wurden in der Anfangsphase immer nur komprimierte Datenpakete übermittelt, die man dann offline auswerten musste. Online-Übertragung mit direkter Verwendung der Daten, wie sie heute etwa im WWW praktiziert wird, kannte man in der Anfangszeit des Internets noch nicht. Online-Anwendungen kamen erst später hinzu.
58
Java 2 Kompendium
Was ist Java?
Kapitel 2
In der Anfangsphase wuchs das ARPANET langsam (was auf Grund der zu dieser Zeit immensen Kosten sicher verständlich ist) und bestand 1972 aus ungefähr 50 Systemen. Insbesondere zwang der Kostendruck die Militärs, das Netz für zivile Zwecke zu öffnen. Besonders die National Science Foundation (NSF), eine Dachorganisation verschiedener Bildungseinrichtungen in den USA, zeigte Interesse am Internet und sorgte dafür, dass im Laufe der Zeit zahlreiche Universitäten und andere Forschungseinrichtungen an das Internet angeschlossen wurden. Damit erlangten immer mehr Personen Zugang zum Netz, die nicht unbedingt als militärisch zuverlässig zu betrachten waren. Die Militärs begannen um die Sicherheit ihrer Geheimnisse zu bangen. Große Teile des militärischen Bereichs wurden deshalb Anfang der Achtzigerjahre in ein eigenes Netz, das MILNET, ausgegliedert. Der zivile Teil wurde Internet genannt. Woher der Name »Internet« kommt, ist nicht ganz unumstritten. Es gibt einige Varianten. Folgende ist aber die wahrscheinlichste: Das Internet wird oft als das »Netz der Netze« bezeichnet. Warum gerade »Netz der Netze« und nicht »Zusammenschluss vieler Netzwerke« oder ähnlich? Der Begriff Internet steht für Interconnected Networks. Übersetzt heißt das »Netz der verbundenen Netze«. Da zentrale Ideen des Internets flexible Möglichkeiten der Datenübertragung und die Unterstützung heterogener Rechnerplattformen waren, mussten verschiedene Protokolle entwickelt werden, über die sich die Rechner verständigen konnten und die unabhängig von der eigentlichen Rechnerarchitektur und dem normalen Befehlssatz des jeweiligen Betriebssystems waren. Ein Protokoll ist allgemein als eine Vereinbarung zu verstehen, wie bestimmte Prozesse ablaufen. Das kann sowohl auf der Ebene von zwischenmenschlichen Beziehungen (etwa einem Staatsempfang) notwendig sein, aber erst recht, wenn verschiedene Computersysteme an einer Aufgabe beteiligt sind. Andere Protokolle mussten die Verbindung zwischen den Systemen regeln. Es war ja durchaus vorgesehen, dass Verbindungen über unterschiedlichste Wege aufgebaut werden, etwa einen Telefonanschluss. Und da muss beispielsweise ein kontaktierter Rechner einen Anruf eines anderen Rechners von einem Telex-, Fax- oder Sprachanruf unterscheiden können. Zusätzlich Vereinbarungen innerhalb von Protokollen regeln die konkrete Datenübertragung und überprüfen die übertragenen Daten auf Fehlerfreiheit. Da bei jeder Datenübertragung Fehler entstehen können, sind bessere Protokolle ebenfalls in der Lage, fehlerhafte Daten zu erkennen und gegebenenfalls während der Übertragung vom Sender noch einmal anzufordern.
Java 2 Kompendium
59
Kapitel 2
Schnuppertour Java Viele im Internet gebräuchliche Protokolle basieren auf Unix-Protokollen. Grund ist, dass das ursprünglich im ARPANET hauptsächlich verwendete Betriebssystem Unix war und so waren die am Anfang verwendeten Protokolle natürlich Unix-Datenübertragungsprotokolle. 1973 wurde mit IP (Internet Protocol) ein Transportprotokoll präsentiert, das plattformunabhängigen Transport von Daten garantieren sollte. Bereits Anfang 1974 erschien eine Verbesserung – das auf IP basierende Erweiterungsprotokoll TCP (Transmission Control Protocol), das eine noch fehlerfreiere Übertragung gewährleistete. TCP funktioniert nur mit IP als Unterbau, gibt aber diesem erst seine Übertragungssicherheit. Und so kommt es, dass heute beide Protokolle meist nur noch in Verbindung verwendet werden (TCP/IP). Seit 1983 ist TCP/IP das Internet-Standard-Protokoll für Direktverbindungen und dient sogar zur Definition der Begriffe Internet/Intranet (alle auf dem TCP/IP-Protokoll basierenden Netzwerke). Es löste das bis dahin verwendete Internet-Standard-Protokoll NCP (Network Control Protocol) ab. TCP/IP ist ein sehr leistungsfähiges Transportprotokoll, aber eben nur ein reines Transportprotokoll. Man kann sich die Situation ganz gut vorstellen, wenn man sie mit dem Eisenbahnverkehr vergleicht. TCP/IP ist in dieser Vorstellung das Schienennetz, über das der eigentliche Verkehr rollt. Auf diesen Schienen rollen in unserem Gedankenmodell also Züge, die als Dienstprotokolle oder Internet-Dienste bezeichnet werden. Je nach Aufgabe gibt es Personenzüge, Güterzüge, Postzüge, Wartungszüge oder kombinierte Varianten. Für verschiedene Aufgaben werden verschiedene Zugarten (Dienste) eingesetzt. Aber so, wie man in einem Personenzug auch Waren transportieren kann, so können manche Dienste im Internet auch vielfältig eingesetzt werden. Vielleicht nicht so effektiv wie die Spezialvariante, aber dennoch mit Einschränkungen möglich. Auf das Internet bezogen stellt man sich TCP/IP als untere Schicht, Dienstprotokolle als eine Schicht darüber vor. Man spricht im Zusammenhang mit dem Internet-TCP/IP-Protokoll auch von einem so genannten Schichtprotokoll. Die Dienstprotokolle, die auf dem TCP/IP-»Schienennetz« aufsetzen, sind Dienste wie Telnet, FTP oder auch das WWW. Das aus HTML aufgebaute World Wide Web wurde im Mai 1991 auf den Rechnern des Kernforschungszentrums CERN (European Organisation for Nuclear Research, Genf) erstmals offiziell vorgestellt. Die ersten Ansätze dieses Projekts gehen allerdings bereits bis in den März 1989 zurück. Damals schlug ein Mitarbeiter von CERN, Tim Berners-Lee, ein Hypertext-Projekt für das Internet vor, um darüber verteilte Inhalte miteinander zu verbinden. Es sollte ein vernetztes Hypertext-System geschaffen werden, in dem die vielen Informations-
60
Java 2 Kompendium
Was ist Java?
Kapitel 2
quellen von CERN über eine gemeinsame und vor allem einfach zu bedienende Oberfläche verfügbar sein sollten. Eigens für dieses Projekt wurde eine Organisation namens W3C (http://www.w3.org) gegründet, deren ursprüngliches Ziel es also war, Wissenschaftlern aller Fachbereiche eine leicht zu bedienende Struktur zu schaffen. Viele Wissenschaftler aus EDV-fremden Sparten waren Ende der Achtzigerjahre nicht in der Lage oder gewillt, sich mit der relativ komplizierten Befehlsstruktur von FTP, Telnet oder den anderen Diensten auseinander zu setzen. Das WWW sollte auch Nicht-EDV-Freaks unter den Wissenschaftlern die Möglichkeit eröffnen, Daten und Informationen auszutauschen und in weltweiten Datenbeständen zu recherchieren. Dabei war von vornherein vorgesehen, über reinen Text hinausgehende Daten mit einzubeziehen. Basis für eine Sprache zu Beschreibung eines solchen Hypertext-Systems war SGML (Standard Generalized Markup Language – siehe: http:// www.w3.org/MarkUp/SGML/), eine bereits seit den Sechzigerjahren verwendete Beschreibungssprache zur Darstellung von Inhalten auf unterschiedlichen Plattformen. Daraus entstand die neue Beschreibungssprache HTML (Hypertext Markup Language), die mit einer geeigneten Darstellungssoftware (dem so genannten Browser) diesen Anforderungen genügte. Mit der ersten Version von HTML präsentierte das W3C zwar nur einen aus heutiger Sicht sehr einfachen Standard, in dem zunächst nur die Möglichkeit bestand, relativ simple HyperText-Systeme im Internet aufzubauen. Diese konnten jedoch bereits wie geplant neben einfachem Text und Hyperlinks bereits Grafiken enthalten. Insbesondere war damit das daraus und dem zusätzlich entwickelten Protokoll HTTP (Hypertext Transfer Protocol) entstehende WWW bereits den bisherigen Internet-Diensten in Bezug auf Anwenderfreundlichkeit überlegen. Richtig populär wurde das Internet freilich erst durch den Mosaic-Browser, den ersten WWW-Browser mit einer grafischen Oberfläche. Die gemeinsame Erfolgsstory von WWW und dem Browser begann im Jahr 1993, als dieser von Marc Andreessen am National Center for Supercomputing Applications (NCSA – http://www.ncsa.uiuc.edu/) an der Universität von Illinois zum ersten Test freigegeben wurde. Erstmals nahm neben den bisherigen Internet-Anwendern (Militärs, Wissenschaftler und EDV-Experten) eine private Öffentlichkeit Notiz vom Internet und begann eine erste Erkundungstour mit diesem Browser. Lange Zeit stand der NSCA-Mosaic als Synonym für die Darstellungssoftware des WWW. Insbesondere ist er der Vorfahr des Netscape Navigators, den Marc Andreessen 1994 entwickelt hatte, nachdem er NSCA verlassen und mit Jim Clark die Firma Netscape gegründet hatte.
Java 2 Kompendium
61
Kapitel 2
Schnuppertour Java
2.1.3
HTML
HTML ist wie gesagt die Abkürzung für Hypertext Markup Language und wurde aus der in der ISO-Norm 8779:1986 festgeschriebenen Sprache SGML (Standard Generalized Markup Language) entwickelt. HTML ist als Ableger von SGML im Gegensatz zu SGML speziell auf Hypertext-Funktionen ausgerichtet (wie man bereits aus dem Namen ableiten kann). HTML ist für die Arbeit mit Java eine zentrale Grundlage, denn die Funktionalität von Java-Applets basiert auf einer Verknüpfung mit HTML. HTML ist keine (!) Programmiersprache wie Java, PASCAL oder C/C++, sondern eine so genannte Dokument-Beschreibungssprache (oder Dokumentenformat genannt). Mit einer Dokument-Beschreibungssprache werden die logischen Strukturen eines Dokuments beschrieben und kein Programm im eigentlichen Sinn erstellt. Insbesondere fehlen HTML Variablen und Programmfluss-Anweisungen, die zentrale Bestandteile einer Programmiersprache sind. Im Grunde gibt ein Dokumentenformat nur unverbindliche Empfehlungen an eine Darstellungssoftware (bei HTML Browser genannt), wie eine bestimmte Dokumentenstruktur darzustellen ist, damit sie dem geplanten Layout und der vorgesehenen Funktionalität entspricht. Es gibt allerdings keine absolute Darstellungsvorschrift, weswegen sich Darstellungen von HTML-Seiten in verschiedenen Browsern oft erheblich unterscheiden können (eine Folge der geplanten Plattformunabhängigkeit). Zu einer durch HTML beschriebenen logischen Dokumentenstruktur gehören Verweise, aber auch Kapitel, Unterkapitel, Absätze usw. HTMLDateien bestehen dabei aus reinem Klartext. Dadurch bleiben HTMLDokumente plattformunabhängig. Wie später bei Java war diese Plattformunabhängigkeit neben der extrem einfachen Benutzerführung per Hyperlinks in einer so heterogenen Welt wie dem Internet der wichtigste Schlüssel für den Erfolg des WWW. Plattformabhängig ist im WWW immer nur die Software zum Interpretieren der HTML-Dateien (der Browser). Da von Anfang an ein in HTML geschriebenes Dokument mit den Texten, Grafiken sowie einigen weiteren multimedialen Elementen (Sound, Video usw.) verknüpft werden sollte, mussten entsprechende Techniken entwickelt werden. Elemente, die jenseits von Textanweisungen zu sehen sind, werden in einer Webseite als Referenz auf eine entsprechende externe Datei notiert. Wenn diese in einer Webseite dargestellt werden soll, muss natürlich die Präsentations-Software entsprechende Softwaremodule und die Hardware die zugehörigen Komponenten (beispielsweise eine Soundkarte für akustische Daten) verfügbar haben. Eine weitere wichtige Eigenschaft von HTML ist, Verbindungen zu anderen Internet-Diensten in eine Webseite aufnehmen zu können. Diese werden als
62
Java 2 Kompendium
Was ist Java?
Kapitel 2
Referenz in einer Webseite notiert, weshalb unter der Oberfläche des WWW viele Dienste wie E-Mail oder FTP verfügbar gemacht werden können. Es ist sogar eingeschränkt möglich, mit HTML Datenbankabfragen zu formulieren, die Resultate optisch aufzubereiten und Menüstrukturen aufzubauen. Auch Interaktion mit Anwendern ist – allerdings mit erheblichen Einschränkungen – in purem HTML zu realisieren. Hinter der Normung von HTML stand und steht auch heute noch das World Wide Web-Consortium (W3C – http://www.w3.org) mit Sitz in Genf. HTML beinhaltete in der Version 1.0 nur wenige der heute im Web zu findenden Möglichkeiten. Da bereits der Mosaic-Browser diverse eigene Features implementiert hatte, die von dem einfachen HTML-1.0-Standard stark abwichen, und sich der Browser (und noch einige andere grafische Browser) rasend schnell verbreitete, versuchte das W3C relativ zügig, einen um einige dieser neuen Möglichkeiten erweiterten HTML-Standard zu schaffen. Der im November 1993 gestartete Versuch – ein Diskussionspapier mit dem Namen »HTML + Discussion Document« – konnte sich allerdings nicht als neuer Standard durchsetzen. Das W3C hatte sich bereits als Forum von unterschiedlichsten Interessengruppen von einer effektiven Arbeit verabschiedet. Erst im September 1995 konnte der nächste HTML-Standard, die Version 2.0, verabschiedet werden. Dieser 2.0-Standard enthielt eine ganze Menge Gestaltungsmöglichkeiten für das WWW, die sich seit dem 1.0-Standard schon in diversen Browsern als zusätzliche Features etabliert hatten. Dazu gehörten unter anderem die Elemente der äußeren Dokumentenstruktur (zum Beispiel Kopfteil mit Meta- und Titelzeile, die Body-Struktur), verschiedene Arten der Überschrift, Hyperlinks, Hervorhebungen, Befehlsstrukturen zu einer flexiblen Grafikeinbindung, aber auch bereits Tabellen, Listen und Formulare. Dies waren nur ein Teil der potenziellen HTML-Möglichkeiten, denn durch die Trägheit des W3C (in Bezug auf das unglaublich dynamische Internet) hatten sich derweil verschiedenste Browser-spezifische Dialekte gebildet, unter denen eine Standardisierung des WWW auch heute noch krankt. Die nächste HTML-Version (3.0) wurde wegen der mangelnden Konsensfähigkeit der am W3C beteiligten Organisationen und Unternehmen nie offiziell verabschiedet. Allerdings war die Version 3.0 durch viele mehrfach vorhandene, aber syntaktisch unterschiedliche Anweisungen so aufgebläht, dass die in der Ende 1996 offiziell verabschiedete Version 3.2 vorgenommene Straffung dringend notwendig war. Zwar hatte HTML 3.2 nicht mehr alle in 3.0 versuchten Möglichkeiten. Dafür wurde nun offiziell die Fähigkeit integriert, Objekte aus anderen Internet-Techniken in eine Webseite einzubauen. Insbesondere lassen sich seit dem 3.2-Standard Java-Applets in jede beliebige Webseite integrieren.
Java 2 Kompendium
63
Kapitel 2
Schnuppertour Java Der HTML-Standard 3.2 hatte ungefähr ein Jahr Bestand. Ende 1997 fand die offizielle Verabschiedung eines neuen HTML-4.0-Standards statt, der auch heute noch aktuell ist. Dieser neue HTML-Standard beinhaltet einige interessante Details, die in großen Teilen von der Java-Entwicklung beeinflusst wurden oder zumindest parallel dazu gehen.
Abbildung 2.2: Der HTML-4.0Standard wird auf den W3C-Seiten erstmals postuliert.
2.1.4
Die Organisation des Internets
Das Internet ist im besten Sinne ein chaotisches System. Das bedeutet, es gibt keine oberste Instanz, kein Kontrollorgan, keine Organisation zur Zensur. Zwar gehören einzelne Teile des Internets (Hosts, WANs, LANs, der private Rechner,...) irgendwelchen Besitzern, jedoch das ganze Netz selbst gehört niemandem. Es gibt keine zentrale Gesellschaft, die das Internet überwacht und lenkt. Es existieren jedoch einige Gruppen und Organisationen, die sich mit Entwicklungen im Internet befassen, versuchen Standards zu definieren, Absprachen zur Kontrolle zu treffen und das Internet (in gewissen Grenzen) lenken. Sie sind allerdings auf relativ freiwillige Kooperation der InternetTeilnehmer (vor allem der einflussreichen kommerziellen Unternehmen) angewiesen. Hier ist eine kleine Liste von wichtigen Organisationen (ohne Anspruch auf Vollständigkeit).
64
Java 2 Kompendium
Was ist Java?
Kapitel 2
Bezeichnung
Bedeutung
Beschreibung
ISOC
Internet Society
Eine nicht-kommerzielle Vereinigung von freiwilligen Mitgliedern. Sie beschäftigt sich im Rahmen von diversen Unterorganisationen mit den unterschiedlichsten Aspekten des Internets. De facto ist sie das Management des Internets (http://www.isoc.org).
IAB
Internet Architecture Diese Organisation (http://www.iab.org) hat die technische Entwicklung des Board Internets im Auge und ist in mehrere Gruppierungen unterteilt. Neue Technologien werden beobachtet und ggf. gefördert, neue Standards werden festgelegt. Dazu zählt als eine der wichtigsten Aktionen die Vergabe der IP-Adressen (vgl. folgenden Abschnitt).
DE-NIC
Deutsches Network Information Center
Für eine eindeutige Vergabe von Rechneradressen ist in jedem Land eine eigene Organisation verantwortlich. In Deutschland ist dies DE-NIC (http:// www.denic.de).
W3C
World Wide WebConsortium
Das W3C (http://www.w3c.org) mit Sitz in Genf ist für die Standards im WWW verantwortlich und hat sich mit diesem und HTML ein (Internet-)Denkmal geschaffen. Allerdings ist das W3C in vielen Bereichen eine zahnloser Tiger, denn die Browser-Hersteller kümmern sich oft nicht um die Empfehlungen des W3C.
NIC bzw. InterNIC
Internet Network Information Center
Dies ist die internationale Dachorganisation der regionalen Vertretungen und die oberste Ebene zur Verwaltung von Domainnamen (http://www.internic.net).
2.1.5
Tabelle 2.1: Organisationen im Internet
Die Adressierung im Internet
Unbestritten ist das Internet ein äußerst großes und komplexes Gebilde. Mainframes, eigenständige Netzwerke, einzelne Rechner – alle sind zu dem Netz der Netze zusammengeschlossen. Wie finden Daten in solch einem scheinbaren Chaos den Weg vom Sender zum Empfänger? Und bei einer Anfrage den Weg wieder zurück? Für diese Weiterleitung von Daten gibt es
Java 2 Kompendium
65
Kapitel 2
Schnuppertour Java im Internet spezielle Server mit Adressinformationen und einer Weiterleitungsfunktionalität – so genannte Router. Router erledigen die Weiterleitung von Daten durch das Netz, indem sie ein Datenpaket jeweils in die optimale Richtung schicken, damit das Datenpaket dem Ziel »näherkommt«. Wenn die kürzeste Wegstrecke jedoch verstopft ist (also fast immer :-)), kann die optimale Wegstrecke ein Datenpaket physikalisch vom Ziel entfernen. »Näherkommen« ist also in Bezug auf die Ankunftszeit zu sehen und irgendwann kommen die meisten Daten beim Adressat dann doch an. Wie aber sieht die Verarbeitung von Adressinformationen auf den Routern aus? In einem derartig komplizierten Computernetzwerk ist eine Orientierung und Navigation nur dann möglich, wenn es dort ein eindeutiges Adresskonzept gibt. Dabei müssen sowohl die einzelnen Rechner, aber auch die Softwarebestandteile eindeutige Adressen haben.
2.1.6
Die IP-Nummern
Zunächst zu den Computern selbst: Für jeden einzelnen Rechner innerhalb eines Netzwerks muss eine eindeutige Zuordnung existieren. Dies gilt selbstverständlich auf für das Internet. Diese Zuordnung geschieht im Internet über eine eindeutige Adresse, die unter dem Transportprotokoll TCP/IP genau vier Byte lang ist. Sie wird als IP-Nummer bezeichnet. Da in einem Byte 256 verschiedene Zeichen binär dargestellt werden können (2 hoch 8 = 256), lässt sich in jedem der vier Byte eine Zahl zwischen 0 und 255 verschlüsseln. Insgesamt sind so also rein theoretisch 4.294.967.296 (256 hoch 4) Computer im Internet adressierbar. Gewöhnlich werden die IP-Nummern im Dezimalsystem darstellt und jedes Byte mit einem Punkt abgetrennt (zur besseren Lesbarkeit). Eine fiktive IP-Nummer eines Host wäre also so darstellbar: 123.187.111.189 Das Adresskonzept der IP-Nummern sieht eine Gliederung der vier Byte in logische Abschnitte vor. Die Gliederung ist hierarchisch und in zwei wesentliche logische Abschnitte unterteilt. Der erste Teil der vier Byte ist die eindeutige Adresse eines Netzwerks, das mit dem Internet verbunden ist (der so genannte Netzwerkidentifikator). Der zweite Teil ist die eindeutige Adresse des einzelnen Rechners innerhalb dieses von dem ersten Byte eindeutig bestimmten lokalen Netzwerks (Hostidentifikator). Ein Datenpaket wird nur dann in einem lokalen Netzwerk weitergeroutet, wenn der Netzwerkidentifikator korrekt ist. Wie viele Bytes dabei für die Adresse des Netzwerks und wie viele Bytes für die Adresse des einzelnen Rechners verwendet werden, ist vom Typ des Netzwerks, d.h. der Grö ß e des lokalen Netzwerks, abhängig. Ein weltweit gültiges Regelwerk sorgt für die eindeutige Zuordnung und Klassifizierung. Zwar klingt das im ersten Moment ziemlich kompliziert und willkürlich, es
66
Java 2 Kompendium
Was ist Java?
Kapitel 2
ist dennoch gut durchdacht und bei näherem Hinsehen logisch. Lokale Netzwerke werden im Internet in drei Klassen unterteilt. Klasse-C-Netz: Das kleinste Netzwerk. Für die Adresse dieses Netzwerks im Internet werden die ersten drei Bytes der IP-Nummer herangezogen. Da dann nur noch ein Byte für die Adressen der einzelnen Rechner innerhalb dieses lokalen Netzwerks übrig ist, ergibt sich zwingend die maximale Grö ß e diese Netzwerktyps. Maximal 256 Rechner sind innerhalb eines C-Netzes theoretisch adressierbar. Davon gehen dann zwei reservierte Nummern noch ab. Klasse-B-Netz. Das B-Netz benutzt die ersten zwei Bytes für Adresse des Netzwerks innerhalb des Internets. Die übrigen zwei Bytes werden für eine Adressierung im lokalen Netz verwendet. Also stehen darin dann maximal 65.536 (256 * 256) eigenständige Rechneradressen im Rahmen der lokalen Netzwerks zur Verfügung (wieder abzüglich zweier reservierter Nummern, was die verfügbaren Hosts auf 65.534 reduziert). Klasse-A-Netz: Das A-Netz kann die grö ß te Anzahl von Rechnern beinhalten. Hier wird als konkrete Netzwerkadresse nur noch ein Byte genommen. Damit stehen drei Bytes zur lokalen Adressierung zur Verfügung (in der Realität bedeutet das 16.777.214 verfügbare Hostadressen pro Netzwerk). Wann eine IP-Adresse welchem Typ zuzuordnen ist, wird durch ein Konzept von Zahlengruppen festgelegt, die das erste Byte betreffen. Es muss ja eindeutig festgelegt sein, ob das zweite und dritte Byte bereits den lokalen Rechner adressiert. Das erste Byte von einem Klasse-A-Netz hat immer einen Wert zwischen 1 und 126. Dementsprechend gibt es maximal 126 verfügbare Netzwerke dieser Klasse. Der B-Netz-Bereich geht von 128 bis 191 (daraus resultieren maximal 16.3841 verfügbare Adressen für B-Netzwerke) und der Wert des ersten Byte von einem C-Netz liegt zwischen 192 und 223, was maximal über zwei Millionen verfügbare Adressen für C-Netzwerke bedeutet. Außer den Nummernkreisen ist keine andere Zuordnung festgelegt. Die Lücken in den Nummernkreisen sind für diverse Sondernetze reserviert. So entspricht die IP-Nummer 127.0.0.0 immer dem lokalen Host in einem Netzwerk. Aber auch einige Nummern, die mit einem der gerade beschriebenen Werten anfangen, werden nicht im Internet verwendet. IP-Nummern, die mit 192.168 beginnen, sind für lokale Adressierungen innerhalb eines Intranets reserviert.
1
16.384 = 64 * 256.
Java 2 Kompendium
67
Kapitel 2
Schnuppertour Java Durch die hierarchische Struktur der IP-Nummern wird ein Datenblock auf seinem Weg vom Sender zum Empfänger von einem Router zuerst nur in eine Grobrichtung weitergeleitet, die sich aus der ersten logischen Adressangabe (je nach Klasse dem ersten, den ersten beiden oder den ersten drei Bytes) ergibt. Erst wenn der Datenblock an dem Router angekommen ist, zu dem das lokale Netzwerk gehört, werden die genauen Adressangaben von innerhalb des Netzes liegenden Adress-Servern ausgewertet. Es gibt Überlegungen, das Adressierungskonzept zu erweitern, denn die Anzahl der Rechner und sonstigen Geräte mit Internetkontakt droht den Adressvorrat zu sprengen. Diskutiert wird eine abwärtskompatible Erweiterung der IP-Nummern auf 128 Bit.
2.1.7
Das DNS-Konzept
Bei fast allen Datenübermittlungen im Internet werden IP-Nummern verwendet, obwohl Anwender sie selten sehen. IP-Nummern sind zwar hervorragend dazu geeignet, in kompakter und eindeutiger Form eine Adressierung von Rechnern vorzunehmen, jedoch für Menschen sind diese abstrakten Zahlenkolonnen oft lästig. Aus diesem Grund wurde den Zahlencodes zusätzlich ein eindeutiger Alias-Name zugeordnet und diese Zuordnung weltweit auf speziellen Namensservern dokumentiert. In Gegensatz zu der Zuordnung der IP-Nummern auf Grund der Grö ß e der Netzwerktopologie erfolgt die Vergabe der eindeutigen Namen nicht über irgendwelche Grö ß enangaben oder sonstige topologische Eigenschaften, sondern ist ein System von logisch und inhaltlich zusammengehörigen Rechnergruppen. Dennoch ist auch dieses Namensystem hierarchisch geordnet. Logisch zusammengehörende Bereiche werden Domain genannt, woraus sich der Name für dieses Namenssystem ableiten lässt: Domain-Name-System, kurz DNS. Genau wie bei den IP-Nummern werden die einzelnen Bestandteile eines DNS-Namens mit Punkten getrennt, die hierarchische Wertigkeit der Stufen ist jedoch umgekehrt, also von hinten nach vorne zu lesen. Der hinterste Teil eines solchen Alias-Namens stellt die gröbste logische beziehungsweise inhaltliche Einteilung da (die so genannte Top Level Domain), etwa eine Nation oder eine Organisationsform. Der unter Umständen zweiteilige Mittelteil (die so genannte Local Level Domain (Teil 1 des Mittelteils) und die Second Level Domain (Teil 2 des Mittelteils – von hinten gelesen) ist eine genaue Beschreibung des Rechnerverbandes bzw. des Rechners selbst. Der vorderste Teil bezeichnet direkt den einzelnen Server auf einem Rechner bzw. Rechnerverband.
68
Java 2 Kompendium
Was ist Java?
Kapitel 2
Wenn Sie in einem Browser bei der Kontaktaufnahme zu einem Server die Statuszeile im Auge behalten, werden Sie dort kurz die IP-Nummer sehen, auch wenn Sie im Adresseingabefeld des Browsers oder beim Klick auf den Hyperlink nur den DNS-Namen verwendet haben. Gültige Alias-Namen (rein fiktiv) für eine Adressangabe in diesem DNSKonzept sind: ftp.cs.tu-muenchen.de www.rjs.de webscripting.de java.sun.com www.mut.de
Der hinterste Teil des DNS-Namens wird für Rechner, die ihren Standort in den USA haben, seit Beginn des Internets nach einem eigenen Regelwerk ausgewählt. In der Regel erfolgt kein direkter Hinweis auf die Nation. Es gibt in diesem nur in den USA gültigen (was bedeuten soll, die Rechner sind dort registriert, adressiert werden können sie von überall her und auch die physikalische Präsens ist davon unberührt) Regelwerk nur sechs (sieben mit dem nachfolgend letztgenannten Spezialfall) logische Bereiche, in die alle Computer in den USA unterteilt werden (daher ist die Nation implizit klar). In der folgenden Tabelle sind sie alphabetisch aufgelistet. Name des Bereichs
Abkürzung für
Bedeutung
com
commercial
Alle von kommerziellen Unternehmen betriebenen Rechner
edu
educational
Rechner von Ausbildungsinstitutionen (Universitäten, Schulen usw.)
gov
government
Regierungsbehörden
mil
military
Militärische Computer
org
organization
Rechner von nicht kommerziellen Organisationen
net
network
Organisationen, die ein eigenes Netzwerk betreiben
arpa
Advanced Research Die Gründungsorganisation des InterProjects Agency nets hat eine eigene Sonderform für ihre Top Level Domain. Sie gilt nur für diese Organisation.
Java 2 Kompendium
Tabelle 2.2: Top-Level in den USA
69
Kapitel 2
Schnuppertour Java Beispiele für gültige Namen für Rechner in den USA sind: altavista.digital.com www.webcrawler.com ourworld.compuserve.com
Ende 2000 wurde eine Erweiterung der Top-Level-Domains vorgenommen. Beschlossen wurden info (einzig offene neue Erweiterung. Markennamen werden bevorzugt), name (Domain-Erweiterung für Namen von privaten Personen, nur in Verbindung von Vor- und Nachname), pro (Freiberufler), biz (kommerzielle Unternehmen; Alternative zu com), museum (Museen), aero (Unternehmen der Luftfahrt) und coop (genossenschaftliche Unternehmen) Außerhalb den USA werden die Top Level Domain-Namen als Abkürzungen für die Ländernamen genommen. Diese Ländercodes sind in der ISO3166-Norm (http://www.din.de/gremien/nas/nabd/iso3166ma/) festgelegt. Ein paar wichtige Beispiele folgen: Tabelle 2.3: Top-Level außerhalb der USA
70
Top Level Domain
Abkürzung für
at
Österreich
au
Australien
be
Belgien
br
Brasilien
ca
Kanada
ch
Schweiz
de
Deutschland
dk
Dänemark
es
Spanien
fr
Frankreich
gr
Griechenland
hk
Hongkong
il
Israel
mx
Mexiko
nl
Niederlande
no
Norwegen
Java 2 Kompendium
Was ist Java?
Kapitel 2
Top Level Domain
Abkürzung für
pl
Polen
pt
Portugal
ru
Russland
se
Schweden
tr
Türkei
uk
Großbritannien
za
Südafrika
Tabelle 2.3: Top-Level außerhalb der USA (Forts.)
Für die USA ist die Top Level Domain us reserviert, obwohl dort ein anderes Konzept priorisiert wird. Wie wir bereits diskutiert haben, verwendet das TCP/IP-Protokoll IP-Nummern zur Adressierung. Die DNS-Namen müssen durch spezielle NamensServer erst in die zugehörigen IP-Nummern übersetzt werden. Da das Internet so groß und komplex ist, übersteigen die gesamten Alias-Namen die Kapazität eines einzelnen Namens-Servers. Sofern ein Alias-Name nicht auf dem zuerst kontaktierten Namens-Server zuzuordnen ist, wird entsprechend der Namenshierarchie in einem komplexen Verfahren der Übersetzungsauftrag an andere Namens-Server weitergeroutet, bis die IP-Nummer vollständig ermittelt ist.
2.1.8
Uniform Resource Locator
Computer sind über die IP-Nummern beziehungsweise ihre Alias-Namen eindeutig adressiert. Innerhalb des Internets müssen aber alle Daten und Programme in Form von unverwechselbaren Internetadressen adressiert werden. Dabei werden neben der Rechneradresse zusätzlich einige andere Angaben notwendig. Der Name dieser Internetadressen für konkrete Adressanfragen an Dokumente (im weitesten Sinn) im Internet lautet URL und steht für Uniform Resource Locator, was übersetzt ins Deutsche ungefähr »einheitliches Adressierungsschema für Objekte im Internet« bedeutet (ziemlich ungünstige Übersetzung, aber leider üblich und abgesehen vom Klang recht treffend). Einheitlich deshalb, weil mit einer URL sowohl verschiedenen Dienste wie WWW, FTP, Gopher usw. als auch Rechner selbst oder direkt Dokumente beschrieben werden können.
Java 2 Kompendium
71
Kapitel 2
Schnuppertour Java Der Begriff Objekt steht in diesem Zusammenhang für so ziemlich alles, was Sie im Netz finden. Die exakte Schreibweise ist je nach Dienstprotokoll leicht unterschiedlich, sieht jedoch in der Regel folgendermaßen aus: :// Dienstprotokoll steht beispielsweise für http, ftp, gopher, news, mailto oder wais. Danach folgt ein Doppelpunkt und fast immer zwei Schrägstriche zur Abtrennung (eine Ausnahme ist mailto).
Mit host.domain wird ein Server im Internet adressiert. Dabei kann direkt die IP-Nummer eingegeben werden, was jedoch nicht üblich ist. Meist nimmt man dafür den DNS-Namen in der Form Server.{LocalDomain}.SecondLevelDomain.TopLevelDomain
Auf einem Internet-Rechner kann unter einem Namen (also der DNSAngabe ohne den Server) eine ganze Reihe von verschiedenen Diensten parallel betrieben werden (üblich sind z.B. ein FTP-Server und ein HTTP-Server). Um auf einem ausgewählten Rechner den gewünschten Dienst zu erreichen, benötigt man oft eine weitere Information, den so genannten Port bzw. Kanal. Innerhalb des TCP-Headers steht ein zwei Byte großes Flag für Port-Nummern zur Verfügung. Damit sind 65.536 numerische Werte möglich (2 hoch 16). Die Port-Adressen 0 bis 1.023 sind dabei reserviert. Sie sind vordefinierten Diensten zugewiesen (wellknown ports). Port-Adressen über 1023 sind aber durchaus möglich. Welcher Dienst darüber angesprochen werden soll liegt in der Verantwortung des Anbieters. Das bedeutet, nahezu jeder Internet-Dienst hat einen Defaultwert, der immer dann verwendet wird, wenn man in einem URL keinen Port notiert. In der Regel braucht deshalb der Port nicht explizit angegeben zu werden. Der Default-Port für zum Beispiel einen HTTP-Server ist 80, ein FTP-Server hat den Port 21, Gopher benutzt den Port 70. Es ist allerdings möglich, die Ports für bestimmte Zwecke zu manipulieren, d.h. es ist optional möglich, in dem URL mit :port festzulegen, auf welchem Kanal dieser Server angesprochen werden soll. Das genaue Objekt, das mit einer DNS-Angabe referenziert werden soll, verbirgt sich hinter der Angabe . Dies ist eine übliche Pfad- und Dateiangabe im Rahmen der Verzeichnisstrukturen eines Rechners. Beachten Sie, dass diese gesamten Angaben von Unix abstammen und deshalb als Trennzeichen kein Backslash (wie unter DOS oder Windows), sondern Slash verwendet wird.
72
Java 2 Kompendium
Die Geschichte von Java
2.2
Kapitel 2
Die Geschichte von Java
Die Geschichte von Java geht bis ins Jahr 1990 zurück, in die Labors der amerikanischen Firma Sun Microsystem. Sun war (und ist) einer der führenden Hersteller von Workstations. Die Sun-Workstations basieren auf dem Unix-Betriebssystem Solaris und Sparc-Prozessoren. Abbildung 2.3: Die Sun-Homepage
Die Geschäfte der Firma Sun mit Workstations und Mainframes liefen zu dieser Zeit zwar gut, aber Sun-PCs waren eigentlich nicht richtig konkurrenzfähig. Als Reaktion auf den drohenden Einbruch im PC-Markt rief Scott McNealy, der Sun-Chef, auf Anraten eines seiner Programmierer – Patrick Naughton – eine Entwicklergruppe mit Namen »Green« ins Leben, dessen zentrale Mitarbeiter neben Naughton James Gosling und Mike Sheridan waren. In dem hochgeheimen Projekt – niemand außer den obersten Führungskräften von Sun wusste in der Anfangsphase Bescheid – sollte der zukünftige Bedarf an EDV analysiert werden, um einen zukunftsträchtigen Markt für Sun zu lokalisieren. Haupterkenntnis des Green-Projektes war, dass die Computerzukunft nicht im Bereich der Großrechner (soweit nichts Besonderes, denn dies ahnten auch damals schon andere), jedoch auch nicht unbedingt bei den PCs zu sehen ist (und dies war neu und nicht unumstritten). Der Consumerbereich der allgemeinen Elektronik (Telefone, Videorecorder, Waschmaschinen, Kaffeemaschinen und eigentlich alle elektrischen Maschinen, die Daten benötigten) war nach Ansicht des Green-Projektes der Zukunftsmarkt der EDV. Wichtigste Forderungen an ein neues, voll-
Java 2 Kompendium
73
Kapitel 2
Schnuppertour Java kommen plattformunabhängiges Betriebssystem für dieses Einsatzfeld waren eine erheblich grö ß ere Fehlertoleranz, eine leichtere Bedienbarkeit und eine bedeutend bessere Stabilität als bei bisherigen Betriebssystemen. Im Frühjahr 1991 entwickelte sich aus den Planungen ein konkretes Projekt zur Generierung eines Prototyps für ein solches universales Betriebssystem. Ab August 1991 wurde – hauptsächlich von James Gosling – nach einer geeigneten Programmiersprache dafür gesucht. Allerdings entschieden sich die Green-Entwickler nicht für eine bereits existierende Programmiersprache, denn diese wiesen zu große Schwächen in Hinblick auf die Stabilität auf. Gerade bei Bedienerfehlern – und diese gestand man beim Green-Projekt der potenziellen Zielgruppe, Consumer, im Gegensatz zur gängigen Praxis bei Computeranwendern einfach zu – waren diese Programme einfach zu intolerant. Eine neue, plattformunabhängige und objektorientierte Sprache musste entwickelt werden. Der Name für diese neue Sprache? Nein, noch nicht Java. Oak – zu deutsch Eiche. Warum Oak? Bekannt ist das Gerücht, dass James Gosling einfach kein passender Name einfiel und er beim Brüten über neue Namen permanent auf eine imposante Eiche vor seinem Büro geblickt haben soll. Eine etwas andere Variante verlegt die Eiche in ein an der Wand hängendes Bild. Dieses ist aber alles inoffiziell und sicher nicht so wichtig. Eine seriöse Erklärung für Oak ist, dass es die Abkürzung für »Object Application Kernel« war. Allerdings findet man diesen Begriff in offiziellen Sun-Quellen nicht sofort, was wohl damit zusammenhängt, dass Sun sich nicht so gerne an Oak erinnert (dazu gleich mehr). 1992 präsentierte das Green-Team der Sun-Führung mit Duke, einer kleinen Trickfigur in einem virtuellen Haus, das erste Ergebnis. Und Duke überzeugte. Ein Sun-Tochterunternehmen namens First Person (im Prinzip das Green-Projekt, aber als unabhängige Firma) wurde gegründet, um das Projekt weiter zu entwickeln und der Öffentlichkeit sowie der Elektronik- und Unterhaltungsindustrie bekannt zu machen. Die Zeit war allerdings noch nicht reif für die neue Technik, die ersten Kontakte verliefen im Sand und First Person verzettelte sich zusätzlich in viele Kleinexperimente. 1994 verschwanden Oak und First Person wieder in der Versenkung und das GreenTeam kehrte zurück zu Sun. Dann aber bewahrte ein anderes Ereignis Oak vor dem völligen Vergessen – der legendäre Mosaic-Browser. Dieser hatte sich mittlerweile als eine feste Grö ß e im Internet etabliert und mit seinen Möglichkeiten wurde plötzlich das heterogene Internet zu einer möglichen Zielplattform für Oak. William Joy, einem Mitbegründer von Sun, ist es zu verdanken, dass Oak Ende 1994 für das Internet umgearbeitet wurde und zudem entsprechend dem gerade vorexerzierten Netscape-Geschäftsmodell über das Netz frei und umsonst verteilt wurde. Die geniale Theorie hinter diesem besagten Netscape-Geschäftsmodell heißt, Marktanteile ohne Gewinn zu erobern, um dann einen Standard festlegen zu können. Kassiert wird danach. 1995 war es soweit – Oak war zum großen Auftritt im Internet bereit. Allerdings musste aus zwei Gründen ein neuer Name gefunden werden. Marketinggründe waren der eine. Oak war ob der gescheiterten
74
Java 2 Kompendium
Die Geschichte von Java
Kapitel 2
Experimente zu negativ belastet. Es gab gleichfalls juristische Schwierigkeiten, denn der Name Oak war einem anderen Produkt zu ähnlich. Und wieder war die Namensgebung eine schwierige Geburt. Man konnte sich nicht auf einen Namen einigen. Dem Gerücht nach wurde der Name dann außerhalb des eigentlichen Brainstormings in der Cafeteria gefunden. JAVA! Der Name Java ist in Amerika – wie inzwischen wohl allgemein bekannt – eine Bezeichnung (eigentlich altenglisch) für das wichtigste Grundnahrungsmittel von Programmierern: Kaffee, heißen, sehr heißen Kaffee (und ist natürlich ebenfalls eine Insel in Indonesien).
2.2.1
Erstes Java-Auftreten, HotJava und das JDK 1.0
Im März 1995 präsentierte Sun Java und das zugehörige Paket von Entwicklungs-Tools (das JDK) der breiten Internet-Öffentlichkeit. Java schlug dort bekanntlich wie eine Bombe ein. In einem Atemzug mit Java wird bei diesem Datum oft der Begriff HotJava genannt. Erst die Kombination mit HotJava verschaffte Java die gebührende Aufmerksamkeit. HotJava war die erste komplexe und vollständig in Java geschriebene Anwendung und naheliegenderweise der erste Java-fähige Browser. Und damit die Präsentationsplattform für die ersten Java-Applets. Zudem war er eine wesentliche Ergänzung der ersten Java-Entwicklungstools von Sun – dem Java Development Kit 1.0 oder kurz JDK 1.0 (HotJava gehörte und gehört immer noch nicht zu dem JDK). Dieses Development Kit soll in der neuesten Version2 natürlich noch ausführliches Thema dieses Buchs sein. Kurz nach Präsentation des JDK 1.0 wurde von der Sun-Tochter Sunsoft mit dem Java-Workshop ein kommerzielles Entwicklungspaket nachgeschoben. Dieser war wie der HotJava-Browser vollständig in Java geschrieben und damit plattformunabhängig. Außerdem bot der Java-Workshop gegenüber dem frei verfügbaren Java Developement Kit statt einer kommandozeilenorientierten Shell, rudimentären Test- und Debug-Möglichkeiten sowie einem Referenzcompiler bereits eine integrierte Entwicklungsumgebung mit Editor, Browser, Project-, Portfolio- und Build-Manager, Debugger, ProjectTester und Online-Hilfe. Andere Hersteller sollten schnell folgen und heute gibt es eine Vielzahl von integrierten Java-Entwicklungsumgebungen. Alle basieren jedoch auf dem JDK. Meist direkt, indem sie die Tools des JDK einfach aufrufen, oder indirekt, indem sie die Funktionalitäten nachbilden. Natürlich gab es im ersten JDK diverse kleinere Kinderkrankheiten. Einige betrafen die Sicherheit, andere fehlende Funktionalitäten. 2
Und auch in älteren Versionen, da es für deren Verwendung immer noch Argumente gibt.
Java 2 Kompendium
75
Kapitel 2
Schnuppertour Java
Abbildung 2.4: Der 1. Java-Workshop von Sunsoft
Dies ist für ein vollkommen neues Produkt verständlich (und leider in der Softwarebranche üblich, wie die permanenten Schnellschüsse Windows oder die verschiedenen Office-Pakete sehr drastisch zeigen) und dürfen auf keinen Fall als Kritikpunkte an Java missbraucht werden. 1997 folgte deshalb nach einigen kleinen Zwischenversionen das erste bedeutende Update mit der Version 1.1 des JDK, der diverse kleinere Verbesserungen folgen sollten. Neben den für Updates üblichen Fehlerbereinigungen enthielten die Versionen 1.1.x einige entscheidende Neuerungen. Diese machten eine ganze Reihe von neuen Tools für das Java-SDK (Software Development Kit) nötig. Bestehende Tools wurde vielfach überarbeitet und sowohl im Layout als auch im Funktionsumfang verändert. Bereits Ende 1997 gab es dann die ersten Betaversionen zum JDK 1.2 und Java 1.2, wie nach den damaligen Veröffentlichungen die komplette Plattform heißen sollte. Das Final wurde für das erste, spätestens aber das zweite Quartal 1998 angekündigt. Daraus wurde jedoch nichts. Die Einführung von Java 1.2 und JDK 1.2 wurde immer wieder verschoben. Insgesamt vier Betaversionen fanden den Weg in die Öffentlichkeit. Diese erwiesen sich als recht stabil und allgemein wurde fast täglich die Freigabe der Finalversion erwartet. Aber erst im Dezember 1998 ließ Sun gleich zweimal eine Bombe platzen. Am 4. Dezember 1998 hatte Sun endlich die Finalversion des 76
Java 2 Kompendium
Die Geschichte von Java
Kapitel 2
JDK 1.2 freigegeben. Pikanterweise erfolgte dies unmittelbar, nachdem im Rahmen des Rechtsstreits mit Microsoft um Java die Redmonder eine erneute Niederlage hinnehmen mussten. Ein Schelm, wer vermutet, Sun nutzte die Aufmerksamkeit, um die Beachtung der neuen Java-Plattform zu erhöhen. Die zweite Bombe explodierte kurz danach auf der Java Business Expo. Nicht nur ein neues JDK, sondern ein vollständiges Plattform-Update unter dem Namen Java 2.0 wurde dort offenbart. Entgegen der allgemeinen Erwartung wurde das vollständige Update nicht als Version 1.2 in Umlauf gebracht. Mitte 2000 erschien dann das JDK 1.3, das das JDK 1.2 im Rahmen der Java-2-Plattform ablöste. Die gesamte Plattform wird aber immer noch Java 2 genannt.
2.2.2
Was macht Java so einzigartig?
Im Gegensatz zu HTML ist Java das Produkt einer Organisation, nicht eines Konsortiums von unterschiedlichsten Interessengruppen. Zudem ist Java vollkommen neu entwickelt worden und muss sich nicht mit Altlasten rumplagen, wie es beispielsweise C++ tun muss. Statt sich immer auf den kleinsten Nenner einigen zu müssen, konnten relativ leicht innovative Ideen und Denkansätze verfolgt und realisiert werden. Dies ist sicher ein Baustein für Erfolg und Leistungsfähigkeit von Java. Ein weiterer Baustein ist das Umfeld, in dem Java das erste Mal präsentiert wurde. Beim Auftreten von Java gab es einige Merkmale für den einschlagenden Triumph. Dabei waren es für verschiedene Gruppen von Leuten durchaus unterschiedliche Aspekte, die Java zu dem durchschlagenden Erfolg verhalfen. Für die reinen Anwender waren es sicher im Wesentlichen zwei Dinge: grafische, dreidimensionale Animationen und Interaktionsmöglichkeiten von Internet-Anwendern. Das Auftreten von Java und etwas später seinem Skript-Ableger JavaScript kam für Internet-Anwender einer Revolution gleich. Dem WWW (von den zeilen- oder Menü-orientieren Diensten ganz zu schweigen) fehlten, mit dem bis zu diesem Zeitpunkt realisierten Stand von HTML, einige ganz entscheidende Merkmale: eine dreidimensionale Darstellung von Objekten, eine bewegte Animation und die Möglichkeit einer vernünftigen Interaktion mit dem Anwender. Java rannte mit seinen Multimedia- und Interaktionseigenschaften schon lange geöffnete Türen ein. Die ziemlich eingeschränkten Interaktionsmöglichkeiten von WWW-Surfern beruhten bis dahin im Wesentlichen auf so genannten Hotspots, sensitiven Elementen auf HTML-Seiten, die beim Anklicken eine Aktion auslösten. Bei der Mausaktion »Klick« muss bei Hotspots die Cursorposition ausgewertet und zum Server zurückgeschickt werden (wie auch immer technisch realisiert). Aber auch eine Tastatureingabe des Anwenders auf einer Java 2 Kompendium
77
Kapitel 2
Schnuppertour Java Webseite musste zum Server übermittelt werden. Auf dem Server wurde die Eingabe verarbeitet und eine neue HTML-Seite zurück zum Client geschickt. Dieser Mechanismus wird CGI (Common Gateway Interface) genannt. Die grö ß te Arbeitslast liegt bei den Servern und den Leitungen des Internets. Der Client steht relativ ungenutzt herum und wartet (meist sehr, sehr lange). Der Ansatz von Java für die Optimierung dieser Vorgänge lag darin, dem Prozessor des Clients einen Teil der Arbeit zu übertragen. Denn obwohl die Rechner der Internet-Anwender immer leistungsfähiger wurden, lagen sie für die Internet-Arbeit normalerweise ziemlich brach. Der Client wurde zum dummen Terminal aus grauer Großrechner-Urzeit degradiert. Es war eigentlich absolut unlogisch, einen Hochleistungs-PC oder einen vergleichbaren Rechner als Client zu verwenden und ihn mit Aufgaben zu betrauen, die ein ZX81 (Kultrechner Anfang bis Mitte der 80er-Jahre mit Folientastatur und 1-16 KByte Hauptspeicher) zur Not noch zu Wege gebracht hätte. JavaApplets sorgten also mit einer Arbeitsverlagerung auf den Client für Aufsehen. Das zweite Java-Highlight für die Anwender – die neuen Darstellungsmöglichkeiten – sorgte zum ersten Mal für echtes Multimedia im Internet. Als Java der Öffentlichkeit präsentiert wurde, fanden Anwender bis dahin beim Surfen nur passive Seiten (zudem noch zweidimensional) vor, die weitgehend in HTML 1.0 oder 2.0 geschrieben waren. Selbst wenn es Texte mit Hyperlinks waren und recht komfortabel Effekte wie Grafiken oder Sounds eingebunden werden konnten, die Einschränkung »Text« blieb. Insbesondere die dreidimensionale Darstellung von Objekten war damit nicht zu realisieren. Genauso wenig eine bewegte Animation. Mit Java änderte sich die Situation schlagartig, die dritte Dimension hielt bewegend Einzug ins Netz. Für die EDV-Spezialisten gab es neben diesen beiden offensichtlichen Knallern jedoch noch andere Gründe. Ein oft genannter Grund ist, dass Java ohne Mitwirkung des »Reichs des Bösen« (liebevoller Kosename für Microsoft – zumindest von allen Nicht-Microsoft-Abhängigen) entstanden ist. Da gab es etwas Neues, Revolutionäres, Innovatives und Bill Gates war nicht dabei. Nicht nur das, Microsoft schätzte als einziges wichtiges EDV-Unternehmen die Internetentwicklung zu dieser Zeit vollkommen falsch ein und ignorierte die ungeheuren Zukunftsperspektiven. Eine in der Computerwelt zu diesem Zeitpunkt einmalige Chance für andere/neue Unternehmen, EDVEntwicklung zu gestalten, ohne von Microsoft die Richtung angezeigt zu bekommen. Firmen wie Sun oder Netscape nutzten die Gunst der Stunde. Bald aber hatte Microsoft seine erste Fehleinschätzung revidiert und sich mit Vehemenz in Entwicklungsprojekte rund um das Internet gestürzt. Allerdings schien es lange so, als ob sich Microsoft bei Java ziemlich kooperativ verhalten wollte. Die Partnerschaft mit Apple bzgl. der JVM- und JRE-
78
Java 2 Kompendium
Die Geschichte von Java
Kapitel 2
Entwicklung (JRE – Java Runtime Environment) signalisierten Kooperationsbereitschaft mit anderen Unternehmen. Diverse Aktionen von Microsoft in der jüngeren Vergangenheit verstärkten jedoch wieder die Befürchtungen der Anti-MS-Gemeinde, Bill Gates wolle doch Java und das Internet umarmen und verschlingen. Dazu zählt beispielsweise die Weigerung von Microsoft, die Java Foundation Classes, die Klassenbibliothek zur Erstellung grafischer Oberflächen, als Bestandteil der Sprache Java anzusehen. Damit wähnt sich Microsoft natürlich frei von etwaigen Lizenzbestimmungen, die Microsoft dazu verpflichten würden, die JFC mit jeder Java-VM auszuliefern – und damit in den Internet Explorer und Windows zu integrieren. Microsoft entwickelte statt dessen zwei Gegenentwürfe: Die eigenen AWTKlassen und J/Direct (Teil der Direct-Technologie von Microsoft), das es Java-Programmen erlaubt, Windows-Routinen zu nutzen. Beides natürlich plattformabhängig an Windows gebunden. Dies und diverse andere Aktionen führten zu mehreren Rechtsstreitigkeiten zwischen Sun und Microsoft, wobei Sun die meisten gewonnen hat. Insbesondere hat Sun erreicht, dass bestimmte Microsoft-Produkte das Java-Logo nicht führen dürfen, wenn sie nicht den Sun-Vorgaben entsprechen. Aber zurück zu den Erfolgsfaktoren von Java: Die Java-Eigenschaften Objektorientiertheit und Plattformunabhängigkeit waren die weiteren und wohl wichtigsten Gründe für den Erfolg. Und last but not least – die extreme Stabilität von Java.
2.2.3
Die Herkunft von Java
Java hat – wie jedes gute Kind – offiziell zwei Elternteile. Die Syntax ist im Wesentlichen eine an C/C++ angelehnte Programmiersprache, die Konzeption und die Objektorientiertheit stammen eher von dem anderen Elternteil – Small Talk. Als »Trauzeugen« werden ab und zu noch Eiffel und Objective-C genannt. Warum aber ist Java so erfolgreich? Eigentlich war es ein einfaches Rezept: Es wurden die Stärken der Vorfahren genommen, die Schwächen ausgemerzt und dann fehlende, sinnvolle Innovationen hinzugefügt. Zwar war einer der Java-Ahnen sowieso objektorientiert (Small Talk), jedoch kaum verbreitet. Dies liegt wohl in der vollständigen Andersartigkeit von Small Talk (gegenüber anderen Programmiersprachen wie C, PASCAL, Basic, COBOL oder FORTRAN). Small Talk ist zwar sehr leicht zu erlernen, jedoch einfach nicht mit gewohnten Befehlsstrukturen vergleichbar. Zumindest aus damaliger Sicht. Java macht nicht den Fehler von Small Talk und vermeidet ein gänzlich neues Befehlskonzept, sondern definiert sich von der Syntax eigentlich als eine Art bessere Untermenge von C++. Der Umstieg von C/C++ auf Java stellt also für die meisten Entwickler keinen großen Aufwand dar.
Java 2 Kompendium
79
Kapitel 2
Schnuppertour Java Von dem anderen Elternteil C/C++ hat sich Java zwar die Syntax geliehen, aber diverser C/C++-Altlasten entledigt und ist erheblich einfacher geworden, ohne viel an Leistungsfähigkeit gegenüber C/C++ zu verlieren. Dies betrifft einmal die Objektorientierung. Obwohl man mit C/C++ objektorientiert programmieren kann, hatte die überwiegende Mehrzahl der Programmierer C/C++ nie so eingesetzt. Die Möglichkeit des prozeduralen (altbekannten) Programmierens untergräbt bei C/C++ den strengen objektorientierten Gedanken. Java lässt einem Programmierer diese Wahl nicht. Man kann nur objektorientiert programmieren. Aber im Gegensatz zu C++ wurde keine Mehrfachvererbung realisiert, was Java-Programme viel besser wartbar macht. Ein weiterer Vorteil von Java gegenüber C/C++ ist, dass die JavaSyntax weniger kompliziert ist. Dies betrifft im Wesentlichen ziemlich üble C/C++-Möglichkeiten wie Zeiger, Operator-Overloading, manuelle Speicherverwaltung (d.h. das Speichermanagement, also das Beschaffen und das Freigeben von Speicher durch den Programmierer), automatische Konvertierung von Datentypen, Headerdateien, Pre-Prozessoren, Strukturen, VariantDatentypen und multidimensionale Arrays. Auf die genauen Unterschiede soll an einer anderen Stelle im Buch eingegangen werden. Insbesondere sollte Java nach Erwartung der Java-Väter (und Mütter) einige der wichtigsten Probleme moderner Programmierung lösen. Kernprinzipien von Java sollten die Eigenschaften kompakt, zuverlässig, portierbar, dezentral, echtzeitfähig und integriert sein. Die offizielle Definition von Sun Microsystems zu Java lautet folgendermaßen: Java: eine einfache, objektorientierte, dezentrale, interpretierte, stabil laufende, sichere, architekturneutrale, portierbare und dynamische Sprache, die Hochgeschwindigkeits-Anwendungen und Multithreading unterstützt. Im Einzelnen bedeutet dies: Java ist einfach, obwohl sehr nahe an der (ziemlich komplizierten) C/C++-Syntax entworfen wurde. Dadurch soll Java für C/C++-Progammierer schon auf den ersten Blick vertraut und verständlich sein und damit die Einarbeitungszeit kurz halten. Dass Java einfach zu erlernen ist (wie Sun behauptet), dem werden jedoch vor allem Programmieranfänger wohl trotzdem kaum zustimmen. Dazu ist Java auch zu mächtig. Aber da auf viele C/C++-Spezialitäten verzichtet wurde, hat Java unter der Prämisse »Weniger ist mehr« erheblich gewonnen. Hinzugefügt wurden dafür selektive und bisher nicht in C/C++ implementierte Funktionalitäten, die die Entwicklung, Implementation und Instandhaltung der Software erleichtern sollen. Obwohl beispielsweise OperatorenÜberladung weggelassen wurde, wurde das Überladen von Methoden beibehalten. Oder für das Speichermanagement, eine der Hauptfehlerquellen bei C/C++-Programmen, verfügt Java über eine automatische Garbage Collection (automatische Speicherbereinigung). Da Java über
80
Java 2 Kompendium
Die Geschichte von Java
Kapitel 2
diesen automatischen Garbage Collection-Mechanismus verfügt, vereinfacht es nicht nur die Programmierung, sondern verringert daneben die Anzahl der Fehler und beseitigt das alte C/C++-Problem des manuellen Speichermanagements. Java ist klein, was unter anderem ein angenehmer Nebeneffekt der Einfachheit ist. Eines der ursprünglichen Ziele von Java war, die Entwicklung von Software zu erleichtern, die »stand alone« auf kleinen Rechnern (ab mindestens 4 MByte RAM) oder sogar in Kaffeemaschinen oder Videorecordern läuft. Die Grö ß e des Basisinterpreters und die Klassenunterstützung betragen in etwa 40 KByte RAM. Zusätzliche Standardbibliotheken und Thread-Unterstützung benötigen weitere 175 KByte. Diese 215 KByte sind wesentlich kleiner als jede andere vergleichbare Programmiersprache oder -umgebung. Java ist objektorientiert. Es zählt zu einer Familie von Sprachen, die Daten als Objekte definieren und Methoden verwenden, um diese Objekte zu bearbeiten. Einfach gesagt, beschreiben objektorientierte Sprachen (OO ist die Abkürzung für Object Orientation) Interaktionen zwischen Datenobjekten. Allerdings hat Java gegenüber einigen anderen objektorientieren Sprachen eine entscheidende Einschränkung – es gibt keine Mehrfachvererbung. Dafür werden jedoch abstrakte Klassen und ein Schnittstellenkonzept unterstützt. Damit ist es möglich, bedeutend transparenter die Möglichkeiten der Mehrfachvererbung zu kompensieren. Damit werden einige der zentralen Probleme umgangen, die die tatsächliche Mehrfachvererbung aufwirft, wohingegen die meisten der Vorteile bewahrt werden. Mehr dazu später. Java ist dezentral, denn Java erfüllt ein wesentliches Charakteristikum von Client/Server-Anwendungen: die Fähigkeit, Informationen und die Last für die Berechnung der Daten zu verteilen. Der Begriff »dezentral« beschreibt dabei die Beziehung zwischen Systemobjekten, gleichgültig, ob sich diese Objekte nun auf lokalen oder auf entfernten Systemen befinden. Einer der großen Vorteile von Java-Applets und -Anwendungen ist, dass sie Objekte über URLs im gesamten Web genauso wie auf dem lokalen Rechner öffnen und auf diese zugreifen können. Dabei können bereits wichtige Teile der Anwendung bzw. der Daten lokal vorhanden sein, andere werden hingegen erst bei Bedarf geladen. Der schon beschriebene Mangel bei anderen Internetsystemen bzw. -sprachen wie HTML (der Client ist im Prinzip zur Arbeit bereitet, hat aber nichts zu tun und Server und Netzleitungen schaffen sich fast tot) wird umgangen. Dabei steht dem Java-Programmierer eine implementierte, umfangreiche Programm-Routinen-Bibliothek für die Zusammenarbeit mit TCP/IP-Diensten wie HTTP und FTP zur Verfügung. Das neue RMI-Konzept ist ein weiterer Schritt in diese Richtung.
Java 2 Kompendium
81
Kapitel 2
Schnuppertour Java Java ist interpretiert, denn ein gewisser Teil des Java-Codes (zwar nur ca. 20 % – das sind jedoch die kritischen 20 %) werden vom Container, z.B. dem Browser, interpretiert. Manchmal sagt man deshalb sogar, dass Java interpretiert und kompiliert zur gleichen Zeit ist, was auf den ersten Blick ja ein Widerspruch zu sein scheint. Sowohl die wesentlichen Aspekte der Java-Sicherheit, als auch die Fähigkeit, auf verschiedenen Systemen zu laufen, rühren von der Tatsache her, dass die letzten Schritte der Kompilierung lokal abgearbeitet werden. Vorher wurde der Java-Quellcode mit dem Java-Compiler in Bytecode kompiliert. Bei dem Bytecode handelt es sich um ein architekturneutrales Object-Codeformat. Ferner ist er binär und so lange nicht vollständig und lauffähig, bis er von der Java-Laufzeitumgebung interpretiert wird. Da jede Java-Laufzeitumgebung plattformspezifisch ist, arbeitet das endgültige ausgeführte Programm auf dieser spezifischen Plattform. Diese Kombination von Kompilierung und Interpretation ist vorteilhaft, da zunächst einmal Sicherheit und Stabilität erhöht werden. Die Java-Umgebung enthält ein Element namens »Linker«, das die in ihr System eingehenden Daten überprüft, um sicherzustellen, dass sie weder schädigende Daten enthalten (Sicherheit) noch Daten, die den Systembetrieb ihres Computers unterbrechen könnten (Stabilität). Außerdem wird die Pflege von Versionsnummern erleichtert. Die Tatsache, dass der letzte Teil der Übersetzung des Bytecodes von einem plattformspezifischen Programm auf der Plattform des Endanwenders ausgeführt wird, nimmt dem Entwickler die Verantwortung, verschiedene Quellen für verschiedene Plattformen halten zu müssen. Die Interpretation erlaubt zudem, Daten zur Laufzeit zu laden, was die Grundlage für das dynamische Verhalten von Java ist. Java ist stabil in der Bedeutung von »zuverlässig«. Je stabiler eine Sprache ist, desto unwahrscheinlicher ist es, dass in dieser Sprache geschriebene Programme abstürzen. Ein wesentliches Kriterium für die Stabilität einer Programmiersprache ist, dass bereits während der Kompilierungsphase der grö ß te Teil der Datentyp-Überprüfung ausgeführt wird, nicht erst zur Laufzeit wie bei nicht so stabilen Sprachen. Man nennt dies »stark typisiert«. Eine stark typisierte Sprache wie Java ermöglicht also bereits ein intensives Überprüfen des Codes während der Kompilierungsphase. Damit können Fehler relativ früh gefunden werden. Ein weiteres Stabilitätskriterium von Java-Programmen ist, dass sie – im Gegensatz zu Programmen, die in vielen anderen Sprachen geschrieben sind – keinen Zugriff auf den vollständigen Speicherbereich eines Computers, insbesondere den Systemspeicher, haben. Java besitzt eine eingebaute Begrenzung der Zugriffsmöglichkeiten. Damit verringert sich die Absturzwahrscheinlichkeit eines Java-Programms erheblich. Schlimmstes Negativbeispiel für eine Programmiersprache, die den umgekehrten Weg verfolgt und Programmieren weitgehend ungeprüfte
82
Java 2 Kompendium
Die Geschichte von Java
Kapitel 2
Zugriffe auf sämtliche Speicherbereiche des Rechners ermöglicht, ist C/ C++. Die Stichworte Zeigerarithmetik und implizite Deklarationen sind mit höchster Gefahr gleichzusetzen. Bei C/C++ liegt eindeutig der Schwerpunkt auf maximaler Geschwindigkeit, plattformnaher Programmierung und Flexibilität, bei Java auf Stabilität und Sicherheit. Ein weiterer Stabilitätsaspekt von Java ist die abschließende Sicherheitsüberprüfung durch einen Linker. Der Linker ist Teil der Laufzeitumgebung. Er versteht das Typensystem und wiederholt während der Laufzeit viele der Typenüberprüfungen, die der Compiler durchführt. Java gilt als sehr sicher. So ist die Unterbindung von beliebigen Speicherzugriffe durch Java nicht nur ein Stabilitätskriterium, sondern trägt gleichfalls zur Sicherheit bei. Da Java-Programme zuerst in BytecodeAnweisungen und Proto-Programme übersetzt werden, lassen diese sich dann recht gut überprüfen. Bytecode-Anweisungen sind nicht plattformspezifisch und enthalten zusätzliche Typinformationen. Diese Typinformation kann für die Überprüfung des legalen Status und von möglichen Sicherheitsverletzungen verwendet werden. Mehr zur Sicherheit folgt noch an anderer Stelle. Java ist architekturneutral, d.h., es ist auf verschiedenen Systemen mit unterschiedlichen Prozessoren und Betriebssystemarchitekturen lauffähig. Der kompilierte Java-Bytecode kann auf jedem Prozessor ausgeführt werden, der einen Java-fähigen Browser bzw. die virtuelle Maschine von Java im Allgemeinen unterstützt. Der Java-Compiler erzeugt niemals plattformabhängigen Binärcode, sondern neutralen Java-Bytecode. Dieser wird erst während der Laufzeit in systemeigenen Maschinencode übersetzt. Und dennoch ist Java eine vollständige Programmiersprache. Java ist portierbar, was eine wesentliche Folge der Architekturunabhängigkeit ist. Darüber hinaus fallen implementationsabhängige Aspekte, wie man sie in C und C++ findet, bei Java durch die Standards für Datentypen weg. In Java wird die Grö ß e einfacher Datentypen genau spezifiziert, und auch, wie sich die Arithmetik gegenüber diesen Datentypen verhält. Fast alle wichtigen Prozessoren unterstützen diese Java-Spezifikation und außerdem beinhalten die Java-Bibliotheken portierbare Schnittstellen für die wichtigsten Plattformen. Java ist sehr leistungsfähig, denn in Tests von Sun auf einer Sparc Station 10 hat sich gezeigt, dass die Performance von Java-Bytecode, der in Maschinencode umgewandelt wurde, von native C oder C++ bei vielen Funktionen nicht zu unterscheiden ist. Allerdings ist dabei die Zeit für die Laufzeit-Kompilierung nicht mit einbezogen. Der Start der virtuellen Maschine beim Aufruf einer Java-Applikation ist zudem zeitaufwändig. Bei allem Java-Lob muss man in dem Punkt Performance realistisch bleiben. Nicht umsonst zählt die absolut mangelhafte Performance früher Java-Versionen zu den Hauptkritikpunkten. Die Java-2-Plattform
Java 2 Kompendium
83
Kapitel 2
Schnuppertour Java beinhaltet diverse Neuerungen zur Steigerung der Performance (wir sind im ersten Kapitel darauf eingegangen). Die Geschwindigkeit von interpretiertem Java-Bytecode ist dennoch auf leistungsschwachen Plattformen recht niedrig, aber für die meisten Aufgaben ausreichend. Und für den Fall, dass eine höhere Performance notwendig ist, kann native Code integriert werden (wovon jedoch aus Sicherheitsgründen dringend abzuraten ist). Ein Ansatz zur Steigerung der Performance ist die Entwicklung von Just-in-Time-Compilern (JIT) durch diverse Hersteller von Java-Entwicklungsumgebungen. Insbesondere gehört in der Java-2Plattform ein JIT-Compiler zum Lieferumfang. Nach Angaben einiger Hersteller von Entwicklungstools kann die Leistungsfähigkeit von JavaApplikationen und Applets um das Fünf- bis Zehnfache erhöht werden – unabhängig davon, mit welchem Tool sie erstellt wurden. Ein JIT (Just-in-Time-Compiler) hat grob gesagt die Funktion, ein Java-Applet oder – Programm vor seiner Ausführung bereits zu übersetzen, um damit eine bessere Performance zu erreichen. Genauer genommen übersetzt der JIT nur häufig benötigte Funktionalitäten, d.h., beim ersten Aufruf einer Methode wird deren Bytecode in Maschinenbefehle übersetzt und im RAM gehalten. Beim erneuten Aufruf dieser Methode wird dann der bereits kompilierte Code verwendet. Zudem steigt durch die »Just-in-Time«-Funktionalität die Geschwindigkeit bei der parallelen Abwicklung mehrerer rechenintensiver Softwareapplikationen. Ein JITCompiler ist aber kein Allheilmittel, denn in der Praxis zeigt sich unter ungünstigen Umständen, dass durch Laden des JIT sogar eine Verringerung der Ausführungsgeschwindigkeit die Folge sein kann. In der Regel ist jedoch eine Steigerung der Performance wirklich oft zu beobachten. Die im JDK 1.3 eingeführte Hotspot-Technologie ist ein neuer Ansatz, die Leistungsfähigkeit von Java erheblich zu steigern. Java unterstützt Multithreading. Multithreading bedeutet zunächst nur, dass mehrere Aufgaben oder Prozesse quasi gleichzeitig ausgeführt werden können. Einfachstes (und bestes) Beispiel für ein Multithreadingsystem ist ein Mensch, der viele Dinge gleichzeitig erledigt. Bei Computersystemen werden solch natürliche Dinge wie Multithreading zu einem großen Problem. Viele ältere Betriebssysteme wie DOS waren nie in der Lage, Multithreading auch nur ansatzweise auszuführen. Nicht einmal Multitasking (das parallele Abarbeiten von mehreren Programmen) war bei diesen Betriebssystemen möglich. Auch das Multitasking von Windows 3.x war nie echtes Multitasking, geschweige denn Multithreading. Neben den Betriebssystemen unterstützten zudem die Konzepte bisheriger Programmiersprachen den Multithreading-Vorgang gar nicht oder nur sehr eingeschränkt. C/C++ ist ebenfalls auf Single-Threaded-Programme ausgerichtet. Bei Java ist die Beschränkung auf Single-Threading aufgehoben. Allerdings gilt vor übereilter Euphonie über Multithreading, dass die Vorteile von Multithreading (bessere interaktive Antwortfähigkeit, besseres Laufzeitver-
84
Java 2 Kompendium
Die Geschichte von Java
Kapitel 2
halten) stark von der zugrunde liegenden Plattform abhängen. Selbst wenn eine Plattform Multithreading unterstützt, Java-MultithreadingAnwendungen sind hauptsächlich für eine Multiprozessor-Umgebung geeignet und haben eigentlich nur dort ein optimales Laufzeitverhalten. Multithreading-Verhalten wird zwar funktionieren, wenn Sie eine JavaMultiprozessor-Anwendung unter Windows 98 auf einem PC laufen lassen, jedoch kaum gute Ergebnisse bieten. Im Prinzip bedeutet Multithreading ja nichts anderes, als dass mehrere Aufgaben gleichzeitig ausgeführt werden können. Bei mehreren Prozessoren kann jeder Prozessor einen Thread abarbeiten, was echte Gleichzeitigkeit darstellt. Was bei einem Prozessor Gleichzeitigkeit bedeutet, kann auf philosophischer Ebene bis zum Exzess diskutiert werden, jedoch ist dies durch die kurzen Intervalle der Zeitzuweisungen an einzelne Threads im Grunde mü ß ig. Die Einschränkungen auf Grund der Zyklen von Kapazitätszuweisungen sind aber offensichtlich. Der Vorgang des Multithreading sollte freilich noch einmal etwas genauer beleuchtet werden. Eigentlich ist die oft verwendete Übersetzung von Threads mit Aufgaben ungenau und verschleiert den Unterschied zu Multitasking, was ja streng genommen bedeutet, dass mehrere Aufgaben (Tasks) gleichzeitig ausgeführt werden können. Ein Thread ist im Grunde statt mit »Aufgabe« besser (wie es im Wörterbuch steht) mit »Faden«, »Faser« oder am besten »Zusammenhang« zu übersetzen. Nicht die gleichzeitige Ausführung von mehreren Programmen wie beim Multitasking, sondern die gleichzeitige, parallele Ausführung von mehreren einzelnen Programmschritten (oder zusammenhängenden Prozessen) ist echtes Multithreading. Dies kann auch bedeuten, dass innerhalb eines Programmes mehrere Dinge gleichzeitig geschehen, mehrere Fäden/Fasern eines Programms gleichzeitig verfolgt und abgearbeitet werden. Beispielsweise kann ein Thread eines Programms eine lange und hochkomplizierte Berechnung ausführen, während sich ein anderer Thread um die Bildschirmausgabe kümmert und ein dritter Thread die Usereingabe bearbeitet. Und dies im Sinne von »computergleichzeitig«. Bei Java ist das Multithreadingkonzept voll integrierter Bestandteil der Philosophie. Der hochentwickelte Befehlssatz in Java, um Threads zu synchronisieren, ist in die Sprache integriert, macht diese stabil und einfach in der Anwendung. Das Multithreadingkonzept an sich ist freilich schon viel älter als Java und auch das Java-Multithreadingkonzept basiert auf dem Cedar/Mesa-System von Xerox. Die einfachen Thread-Anweisungen von Java beruhen auf dem 20 Jahre alten Paradigma von Steuerund Bedingungsvariablen von C. Anthony Hoare. Allerdings hat Sun mit Java in dem äußerst wichtigen und komplizierten Bereich der Synchronisation von Threads viel Neues entwickelt und viele der Synchronisationsschwierigkeiten bisheriger Multithreading-Umgebungen aus dem Weg geräumt.
Java 2 Kompendium
85
Kapitel 2
Schnuppertour Java Java ist dynamisch, d.h., es kann sich an eine sich ständig weiterentwickelnde Umgebung anpassen. Sun zieht zur Verdeutlichung dieser Eigenschaft von Java das Beispiel C++ heran und definiert den Vorteil von Java gegenüber Entwicklungen mit C++ so, dass Entwickler bei C++ von jemand anderem abhängig werden. Grund dafür sind die Klassenbibliotheken, eine Sammlung von Plug-and-Play-Komponenten, in denen der C++-Code implementiert wird. Diese werden von fremden Unternehmen ständig verändert oder »upgegradet«. Da C++-Programme die Klassenbibliotheken verwenden, muss deshalb oft der C++Code angepasst werden, nur um die Programme lauffähig zu halten. Dummerweise werden die meisten User bei einem Programmfehler nach einem (vielleicht noch unbemerkten, weil bei einer Installation einer anderen Software erfolgten) Upgrade von Klassenbibliotheken den Fehler dem Programm zuschieben und nicht den neuen Klassenbibliotheken. Java verbindet im Gegensatz zu C++ die einzelnen Module in einer späteren Phase und vermeidet dadurch das Bibliotheken-Problem. Ein weiterer dynamischer Aspekt von Java ist der Mechanismus der Schnittstellen (Interfaces). Mehr dazu im Abschnitt über Schnittstellen. Nur soweit vorab die Information, dass Schnittstellen im Prinzip Klassen sehr ähnlich sind, jedoch im Gegensatz dazu Mehrfachvererbung unterstützen und auch sonst flexibler in bezug auf Vererbung sind, als dies bei Klassen der Fall ist. Eine wesentliche Eigenschaft von dynamischen Sprachen wie Java oder deren dynamischen Vorgängern Small Talk oder auch Lisp ist, dass sie sich auf Grund ihrer Flexibilität hervorragend zur Erstellung von Prototypen eigenen. Ein Hauptgrund dafür ist, dass sie in jeder Phase von Programmierern explizite Entscheidungen fordern, damit durch eine schlechte oder nicht erfolgte Entscheidung (etwa wie bei C/C++ ein Zeiger auf einen unbekannten Objekttyp) ein System nicht zum Absturz gebracht werden kann. Wenn Sie beispielsweise in Java einen Methodenaufruf schreiben und dabei Fehler machen, wird dieser beim Kompilieren bemerkt und Sie bekommen eine Warnmeldung, wenn Sie den Code kompilieren wollen.
2.3
Zusammenfassung
Zusammenfassend kann man sagen, dass Java eine der wohl spannendsten Entwicklungen der letzten Jahre in der EDV war und immer noch ist. Nahezu konkurrenzlos für die Bereiche, wo es auf Plattformneutralität und Stabilität ankommt, hat sich Java zu einer festen Grö ß e entwickelt. Java bietet weit mehr als nur die Möglichkeit, im Internet kleine, bunte »Progrämmchen« zu erstellen. Es ist eine der leistungsfähigsten Entwicklungsumgebungen, die man sich vorstellen kann.
86
Java 2 Kompendium
3
Das JDK
Wir wollen in diesem Kapitel zu den Entwicklungstools für Java kommen. Das Java Development Kit (JDK) ist die Sun-Entwicklungsumgebung zum Erstellen von Java-Applikationen, -Applets und -Komponenten. Es ist Kern sämtlicher Java-Entwicklungswerkzeuge (gleichfalls von Fremdherstellern). Das Kapitel ist relativ knapp gehalten und spricht nur die wichtigsten Tools an. Im Anhang gehen wir intensiver auf das JDK und ergänzende Entwicklungstools ein. Das JDK wird in der aktuellen Version 1.3 (die – um es nochmals zu betonen – das aktuelle Entwicklungspaket der Java-2-Plattform ist) von Sun für Windows 95/98/ME/NT (ab der Version 4.0)/2000 sowie deren Nachfolger und das Solaris-Betriebssystem bzw. das in der letzten Zeit immer populärer werdenden Linux frei zur Verfügung gestellt. Genauere Informationen finden Sie unter http://java.sun.com/products/jdk/faq.html. Beachten Sie, dass das JDK 1.3 an diversen Stellen als SDK 2 – Software Development Kit – bezeichnet wird, womit streng genommen aber eine Obermenge des JDK gemeint ist. Für die Windows-Versionen 3.x und früher gibt es keine JDK-Variante, was jedoch schon deswegen klar ist, da dort lange Dateinamen nicht unterstützt werden. Schwierigkeiten beim Erzeugen einer Multithreading-Umgebung in einer Betriebssystemumgebung, die nicht einmal preemptives Multitasking unterstützt, und fehlende direkte TCP/IP-Unterstützung bei Windows 3.x sind weitere Gründe. Allerdings können Windows 3.1x-Anwender seit einiger Zeit zumindest Java-Applets laufen lassen, denn es gibt sowohl von Netscape als auch von Microsoft mittlerweile Java-taugliche Browser für Windows 3.1x. Das JDK ist bezüglich der minimalen Plattformvoraussetzungen genügsam. Nicht nur Java selbst ist klein, auch das JDK ist relativ klein und kompakt. Die Java-Gesamtkonzeption legt auf solche Eigenschaften ja sehr großen Wert. Das Win32-Release läuft sogar schon auf einem einfachen PC mit einem Pentium-Prozessor der ersten Generation mit nur 32 Megabyte recht gut. Wenn es sein muss, sogar bei noch sparsamerer Ausstattung. Das jewei-
Java 2 Kompendium
87
Kapitel 3
Das JDK lige Betriebssystem oder eine auf dem JDK aufbauende Entwicklungsumgebung wird statt dessen die notwendigen Minimalanforderungen für die Hardware festlegen. Das JDK besteht fast ausschließlich aus Befehlszeilen-orientierten Tools und erreicht damit natürlich nicht den Komfortstandard integrierter Entwicklungsumgebungen (IDE), die eine grafische Benutzerumgebung (GUI – Graphical User Interface) haben. Aber Befehlszeilen-Orientierung ist nicht immer schlecht. Man sollte nur daran erinnern, wie viele PC-Profis sogar heute noch lieber mit DOS-Befehlen, als mit den langsamen Stützrädern der Windows-Programme arbeiten. Die JDK-Werkzeuge (den Compiler eingeschlossen) können über eine einfache Befehlszeileneingabe auf Prompt-Ebene aufgerufen werden. Der JavaSource selbst (eine Datei mit Erweiterung .java) kann mit jedem einfachen Texteditor erstellt und bearbeitet werden. Im Anhang werden wir dennoch als Alternativen zu diesem rustikalen – und aus didaktischen Gründen sehr sinnvollen – Arbeiten einige frei verfügbare IDEs vorstellen. Um nun das JDK auf einem lokalen Rechner zu installieren und anzupassen, sollte man in mehreren chronologischen Schritten vorgehen. Am Anfang steht natürlich der Bezug eines möglichst aktuellen JDKs.
3.1
Bezug des JDK
Natürlichste Quelle für ein Java-Entwicklungstool ist wohl das Internet. Immerhin hat dieses erst den Erfolg von Java ermöglicht und wer mit Java programmieren will, hat auch meistens einen funktionierenden Internetzugang. Es gibt nun zahlreiche Quellen, von wo Sie das jeweils aktuelle JDK (aber auch ältere Versionen) beziehen können. Am einfachsten ist der Weg über eine der vielen Seiten des Sun-Imperiums. Der sinnvollste Einstieg erfolgt über den Java-Bereich von Sun (http://java.sun.com). Dort finden Sie alles rund um Java und natürlich auch die neue Java-2-Plattform samt dem zugehörigen JDK 1.3. Wenn Sie diese Java-Seiten aufrufen, folgen Sie einfach den entsprechenden Links für die gewünschte Java/JDK/SDK-Version und Ihrem Betriebssystem. In der Regel müssen einige übliche Copyright- und die Readme-Dateien auf dem Weg dorthin gelesen und bestätigt werden. Sie können die konkreten Seiten für das JDK natürlich auch direkt anwählen. Das erfolgt meist über eine URL wie die folgende: http://java.sun.com/j2se/
88
Java 2 Kompendium
Bezug des JDK
Kapitel 3 Abbildung 3.1: Alles rund um Java direkt von Sun
Abbildung 3.2: Hier gibt es das JDK.
Java 2 Kompendium
89
Kapitel 3
Das JDK Sie können das JDK auch von Seiten laden, die nicht zum offiziellen SunInternetbereich zählen. Etwa von gespiegelten FTP-Servern diverser Organisationen. Gerade die gespiegelten FTP-Server halten aber nicht immer, was diverse Quellen – sogar die offiziellen Sun-Unterlagen – versprechen. Prinzipiell sollte man ob der permanenten Umstrukturierungen auf den Servern bei Fehlermeldungen auf dem Root-Verzeichnis der jeweiligen Organisation einsteigen und dann sein Glück versuchen. An dieser Stelle stehen Ihnen dann u.a. eine Liste der unterstützten Plattformen und die dafür vorhandenen Versionen des JDK zur Verfügung. Sie werden mit detaillierten Anweisungen und Hyperlinks zum Download für Ihre Version und Plattform geführt. Auf der CD finden Sie ein 1.3-JDK. Allerdings kann es sein, dass es seit der Bucherstellung neuere Versionen gibt. Über die angegebenen URLs laden Sie in der Regel ein selbstextrahierendes Archiv auf den lokalen Rechner, sofern es sich um eine ältere Version des JDK handelt. Ab der Version 1.2 (bzw. sogar schon in 1.1.x-Zwischenversionen) steht – zumindest in der Windows-Version – ein Standard-Setup zur Verfügung. Auf den Sun-Downloadseiten stehen verschiedene Möglichkeiten zum Download des doch ziemlich großen JDK-Paketes zur Verfügung. Es gibt sowohl einen FTP-Download als auch einen HTTP-Download. Wenn es Ihnen möglich ist, sollten Sie einen FTP-Download durchführen. Der ist in der Regel schneller als ein HTTP-Download. Zusätzlich müssen Sie das gesamte Paket nicht als eine Datei laden. Es gibt das JDK auch in kleinen Häppchen. Die genauen Grö ß enangaben für das JDK können je nach Release variieren, aber grob sollte die Installationsdatei des JDK 1.3 ca. 29,4 MByte groß sein und mit allem Drum und Dran nach der Installation auf der Festplatte ca. 50 MByte belegen. Da die genauen Grö ß enangaben sich permanent verändern, macht eine genauere Angabe keinen Sinn. In der Regel wird sowieso immer die neueste Variante des JDK geladen und deren Grö ß enangaben können immer wieder variieren. Bei der Kapazität heutiger Datenspeichermedien spielt dies kaum noch eine Rolle, und das JDK ist auf jeden Fall kleiner als übliche Entwicklungsumgebungen.
90
Java 2 Kompendium
Installation des JDK
Kapitel 3 Abbildung 3.3: Eine Installationsdatei des JDK
3.2
Installation des JDK
Die Installation des JDK ist in neueren Versionen kein Problem mehr. Die ersten Versionen waren da nicht so einfach zu handhaben. Es ist jedoch zu empfehlen, ein neues JDK nicht über ein altes Release zu installieren. Wenn Sie eine ältere Version ersetzen wollen, werden als Installationsorte sowieso defaultmäß ig verschiedene Verzeichnisstrukturen vorgeschlagen. Wollen Sie jedoch die bereits vorhandene Verzeichnisstruktur bzw. die bisherigen Namen der Verzeichnisse verwenden (etwa, um diverse Pfade und Einstellungen nicht mehr ändern zu müssen), sollten Sie vor der Installation einer neuen JDK-Version eine gegebenenfalls vorhandene Altversion vollständig beseitigen, damit die beiden Varianten sich nicht in die Quere kommen. Falls Sie vor einer Installation die alte Version beseitigen, sollten Sie natürlich gegebenenfalls vorhandenen eigenen Java-Source und sonstige selbst erstellten Dokumente in dem Verzeichnis in Sicherheit bringen, bevor Sie das alte Verzeichnis liquidieren. Die genauen Schritte bei der Installation sind für die unterschiedlichen Betriebssysteme leicht verschieden, entsprechen aber den üblichen Schrittfolgen bei Installationen unter den jeweiligen Plattformen. Die genauen Installationshinweise für jedes Release werden auf Java-Webseiten unter der zu jedem Release gehörenden Verzeichnisstruktur zur Verfügung gestellt. Wir werden als Beispiel die Installation unter dem Betriebssystem Windows durchsprechen. Ab der Version 1.1.x verwendet das JDK den Standardvorgang zum Installieren unter Windows, den InstallShield. Positiv zu erwähnen ist dabei, dass selbst ein Aufruf des Installationsprogrammes auf Befehlzeilenebene keine sonst übliche Fehlermeldung der Art »This program requires Windows« erzeugt, sondern direkt zum InstallShield übergeht. Der InstallShield bietet alle bekannten Einstellmöglichkeiten in Bezug auf das Installations-Verzeichnis und zu installierende Komponenten. Außerdem wird das Ziellaufwerk überprüft.
Java 2 Kompendium
91
Kapitel 3
Das JDK
Abbildung 3.4: Der Beginn des Installationsvorgangs ist WindowsAnwendern bekannt
Abbildung 3.5: Standardeinstellungsmöglichkeiten
Ziemlich störend ab der Version 1.2 sind die zahlreichen Stellen, wo Sie Lizenzbedingungen akzeptieren müssen. Wenn man die zusätzlichen Bestätigungen der Lizenzbedingungen vor dem Download dazu zählt könnte man auf die Idee kommen, Sun würde einen Augapfel opfern. Das Defaultverzeichnis setzt sich aus der Laufwerksangabe JDK und der Versionsnummer, durch Punkte getrennt, zusammen, zum Beispiel C:\JDK1.1.1 oder C:\JDK1.3. Dies erleichtert eine parallele Führung von mehreren Versionen erheblich.
92
Java 2 Kompendium
Installation des JDK
Kapitel 3
Der Setup-Wizard installiert neben dem JDK noch zusätzlich eine JavaLaufzeitumgebung in ein separates Verzeichnis. Dies ist etwas ärgerlich, weil Sie diese zweite Laufzeitumgebung nicht benötigen – das JDK enthält sowieso eine solche.
3.2.1
classes.zip, src.zip, src.jar, rt.jar & Co.
Zwischen dem JDK 1.1 und der Finalversion 1.2 (bzw. natürlich auch dem Wechsel von JDK 1.2 auf JDK 1.3, aber diese sind nicht ganz so massiv) haben sich zahlreiche Veränderungen ergeben, die die interne Arbeitsweise der JDK-Tools und die Voraussetzungen für deren Aufruf betreffen. Hier soll ein wichtiges Detail hervorgehoben werden – die Dateien Classes.zip und Src.zip sowie die Laufzeitumgebung von Java. Bis zu der JDK-Version 1.1.7 (und sogar den meisten Betaversionen des JDK 1.2) erzeugte das Extrahieren des JDK-Archives beim Installieren normalerweise (u.a.) zwei interessante Dateien bzw. Verzeichnisse: Src.zip im Java-Verzeichnis selbst bzw. das Verzeichnis src und Classes.zip in dessen Unterverzeichnis lib.
Beide ZIP-Dateien sollten normalerweise nicht (!) extrahiert werden. Classes.zip war wesentlicher Teil des RUNTIME-Moduls (die Systemklassen) von Java in den damaligen Versionen. Die Datei enthält den vollständigen, kompilierten Code von Java und muss in diesen Versionen des JDK einigen JDK-Tools (insbesondere dem Compiler und dem Interpreter) zur Laufzeit zur Verfügung stehen. Ein Entpacken der Datei ist so gut wie nie notwendig, denn Java kann ZIP-Dateien in komprimierter Form verwenden. Falls Sie die Datei dennoch entpacken, erreichen Sie nur eine geringfügige Steigerung der Performance und verlieren viel Platz auf der Festplatte. Die Datei Src.zip beinhaltete in den älteren Versionen den eigentlichen Quellcode von Java. Normalerweise werden Sie diesen Quelltext nicht benötigen. Mit einer Ausnahme – diese Source-Files enthalten zum einen wichtige Informationen über Java, zum anderen können sie das Lernen von Java unterstützen, indem man den Source analysiert. Die Klassen sind in der API-Reference-Dokumentation beschrieben, die mit dem Tool javadoc erstellt wurde. Aber um es nochmals zu betonen – ab der Finalversion des JDK 1.2 werden Sie die Dateien Src.zip und Classes.zip nicht mehr finden. Die Datei Src.zip bzw. das Verzeichnis SRC ist durch die Datei Src.jar ersetzt worden. Diese ist in ihrer Funktionalität identisch. Sie können deren Installation im Setup-Programm mit angeben. Ein .jar-File ist ein Kompri-
Java 2 Kompendium
93
Kapitel 3
Das JDK mierungsformat, mit dem Java direkt arbeiten kann. Es muss genauso wenig extrahiert werden, wie die bisherige ZIP-Dateien. Die Laufzeitumgebung des JDK-1.2-Finalrelease hat sich ebenso vollständig gegenüber den Vorgängern geändert. Die Datei Classes.zip ist im Wesentlichen durch die Datei rt.jar (rt steht für RunTime) ersetzt worden. Darauf kommen wir aber gleich wieder zu sprechen.
3.2.2
Verzeichnisstruktur innerhalb des JDK-Verzeichnisses
Innerhalb des Installationsverzeichnisses des JDK wird von dem Setup-Programm eine Unterstruktur angelegt. Diese hat sich über die verschiedenen JDK-Versionen öfters verändert. Außerdem ist die genaue Verzeichnisstruktur davon abhängig, welche Optionen Sie bei der Installation angegeben haben. Sie werden bei einer vollständigen Installation des JDK 1.3 normalerweise die folgende Struktur finden: Tabelle 3.1: Die Verzeichnisstruktur des JDK
Verzeichnis
Inhalt
\bin
In diesem Verzeichnis befinden sich (wie bei vielen Entwicklungsumgebungen anderer Sprachen) die JDK-Programme, also z.B. der Compiler, der Interpreter, ein Appletviewer, der Debugger, der Disassembler oder das Dokumentationstool. Dieses Verzeichnis ist immer vorhanden – es ist das JDK.
\lib
In diesem Verzeichnis befinden sich defaultmäßig StandardJava-Klassen des JDK. Im Wesentlichen ist das in den alten Versionen des JDK die Datei Classes.zip, ab dem JDK 1.2 (Final) sind es die Dateien Tools.jar, Jvm.lib und Dt.jar. Dazu kommem im JDK 1.3 die Dateien ir.idl, jawt.lib und orb.idl. Tools.jar beinhaltet die Non-Core-Klassen zur Unterstützung der Tools und Utilities im JDK. Dt.jar (DesignTime-Archive) ist dafür da, über BeanInfo-
Dateien interaktiven Entwicklungsumgebungen (IDE's) Informationen zur Verfügung zu stellen, wie sie die Java-Komponenten darstellen sollen und wie sie Entwickler dort für ihre Applikationen anpassen können. Die Datei Jvm.lib ist eine Bibliothek für die virtuelle Maschine von Java. Auf diese Thematik werden wir gleich noch etwas näher eingehen. Die beiden Dateien ir.idl und orb.idl dienen der Zusammenarbeit von Java mit der IDL-Sprache, jawt.lib mit dem AWT Native Interface.
94
Java 2 Kompendium
Installation des JDK
Verzeichnis
Kapitel 3
Inhalt Beachten Sie, dass zwar in den JDK-Versionen vor 1.2 in diesem Verzeichnis die Datei classes.zip zu finden war, aber in neuerern JDK-Versionen nicht deren wesentlicher Ersatz rt.jar. Diese befindet sich an anderer Stelle innerhalb der Verzeichnisstruktur (im Unterverzeichnis \jre).
\include
Der C/C++-Programmierern sicher bekannte Ausdruck »include« steht für ein optionales Verzeichnis, das diverse Header-Dateien für die gemeinsame Verwendung von Java und C/C++ enthält. Außerdem gibt es dort ein Unterverzeichnis \win32, das weitere Header-Dateien beinhaltet. Die CHeaderfiles in diesem Verzeichnis sind ausschließlich für die Unterstützung von Nativecode-Programmierung über das Java Native Interface und das Java Virtual Machine Debugger Interface gedacht.
\include-old
Ab dem JDK 1.2 (Final) gibt es optional dieses Verzeichnis, in dem aus Gründen der Abwärtskompatibilität Header-Dateien zur Unterstützung von Nativecode-Programmierung über ältere Schnittstellen zur Verfügung stehen. Diese Dateien gelten als deprecated (veraltet), werden nicht weiter unterstützt und stehen nicht auf allen Plattformen zur Verfügung.
\demo
Das optionale Demoverzeichnis beinhaltet Beispielprogramme. Es hat einige Unterverzeichnisse (u.a. applets und jfc).
\src
In der JDK-Version 1.1.x und den meisten Betaversionen des JDK 1.2 können Sie das Verzeichnis SRC mit installieren. Darin befinden sich dann die entkomprimierten Java-Dateien, die in diesen JDK-Versionen sonst nur im gepackten Zustand in der Datei Src.zip vorhanden sind. Das Verzeichnis ist durch die Datei Src.jar ab dem JDK 1.2 ersetzt worden, die seitdem im Hauptverzeichnis befindet.
\jre
Das Verzeichnis beinhaltet die Laufzeitumgebung von Java. Es beinhaltet die zwei Unterverzeichnisse bin und lib. Im Wesentlichen finden Sie die Datei rt.jar in lib und in bin diejenigen Programme, die zum Ausführen von Java-Applikationen notwendig sind (ohne Entwicklungstools).
Tabelle 3.1: Die Verzeichnisstruktur des JDK (Forts.)
Nach erfolgreicher Installation können Sie normalerweise das Setup-File des SDK von der Festplatte wieder löschen. Es wird nicht mehr benötigt und belegt nur Platz. Überdies ist es im Gegensatz zu gekaufter Software jederzeit erneut zu beziehen. Jedoch sollten Sie sich darüber im Klaren sein, dass Ihnen dann im Falle einer Neuinstallation erneut ein längerer Download bevorsteht.
Java 2 Kompendium
95
Kapitel 3
Das JDK
3.2.3
Die Java-Laufzeitumgebung jre
Hinter dem Begriff Java Runtime Environment (JRE) verbirgt sich die JavaLaufzeitumgebung, der Minimalstandard, um Java-Programme laufen lassen zu können. Das Java Runtime Environment besteht aus der Java Virtual Machine, Java Core Classes und aufseiten der Tools hauptsächlich aus dem Java-Interpreter, ein paar ergänzenden Programmen sowie einigen dazu gehörenden DLLs. Insbesondere sind keinerlei Entwicklungstools enthalten und kein Tool zum Starten und Ansehen von Applets. Laden können Sie das Java Runtime Environment separat von denselben Quellen, wo auch das JDK zu finden ist. Sie können (und müssen!!) es aber auch in der Setuproutine des JDK mit installieren. Das JRE wird dann wie oben beschrieben im Unterverzeichnis \jre installiert. Sie werden auf Ihrem Rechner diverse Java-Laufzeitumgebungen finden. Das ist einmal diejenige, die vom JDK in dessen Installationsverzeichnis angelegt wird, aber auch an unterschiedlichsten anderen Stellen werden Sie – teils differierende Versionen – finden. Die verschiedensten Programme installieren eine solche Umgebung – in den unterschiedlichsten Versionen.
3.2.4
Das Java-Plug-In
Wir wollen hier den Einsatz des Java-Plug-In (siehe dazu auch Seite 35) durchspielen. Dies wird Ihnen dann die Möglichkeit eröffnen, ihre mit erweiterten Java-Technologien erstellten Java-Applets nicht nur im Appletviewer des JDK zu testen, sondern auch in einem (moderneren) Browser zu testen. Zuerst laden Sie das Tool, das als ZIP-Datei bereitgestellt wird, von den Sun-Seiten. Es gibt dort verschiedene Möglichkeiten, von wo und wie Sie das Tool laden können. Am besten ist natürlich ein direkter FTP-Download. Danach entpacken Sie einfach die ZIP-Datei in leeres Verzeichnis auf Ihrem Rechner. Sie erhalten dort zwei Verzeichnisse. Im Verzeichnis classes befinden sich dann die eigentliche Java-Programmdatei HTMLConverter.class, die Sie mit dem Java-Interpreter direkt starten können (java HTMLConverter) sowie eine Batchdatei mit Namen HTMLConverter.bat, die diesen Vorgang ein wenig vereinfacht. Wenn Sie das Tool gestartet haben, können Sie HTML-Dateien konvertieren und dabei diverse Konfigurationen vornehmen.
96
Java 2 Kompendium
Installation des JDK
Kapitel 3 Abbildung 3.6: Die Downloadseite des HTML-Converters
Abbildung 3.7: Die Downloadseite des HTML-Converters 1.3
Java 2 Kompendium
97
Kapitel 3
Das JDK
Abbildung 3.8: Das Unterverzeichnis classes
Abbildung 3.9: Konvertierung von einer HTML-Seite mit dem <APPLET>-Tag
Spielen wir die Situation mit einem vollständigen Applet durch. Der nachfolgende HTML-Code bindet ein Swing-Applet mittels des <APPLET>-Tags in eine Webseite ein. Listing 3.1: Einbindung von einem SwingApplet mit dem <APPLET>-Tag
98
<TITLE>DemoApplet – Einbindung mit Applet-Tag
Java 2 Kompendium
Installation des JDK <APPLET CODEBASE = "." CODE = "DemoApplet.class" NAME = "TestApplet" WIDTH = 400 HEIGHT = 300 HSPACE = 0 VSPACE = 0 ALIGN
Kapitel 3
= middle>
Im Appletviewer des JDK wird das Applet auch einwandfrei funktionieren. Abbildung 3.10: Klick auf die eine Schaltfläche im Demo-Applet
Abbildung 3.11: Klick auf die andere Schaltfläche im Demo-Applet
Java 2 Kompendium
99
Kapitel 3
Das JDK Schauen wir uns die Webseite nun in halbwegs modernen Browsern an. Der Netscape Navigator 4.7 meldet einen Fehler in der Statuszeile und zeigt nichts an.
Abbildung 3.12: Netscape meldet Probleme im Demo-Applet.
Der Internet Explorer 5 meldet ebenso, dass er eine Klasse nicht finden kann und zeigt auch nichts an. Abbildung 3.13: Der Explorer übt sich in Ignoranz.
100
Java 2 Kompendium
Installation des JDK
Kapitel 3
Wenden wir das Tool nun auf die HTML-Datei an. Das kommt dabei heraus: <TITLE>DemoApplet – Einbindung mit Applet-Tag <EMBED type="application/x-java-applet;version=1.3" CODE = "DemoApplet.class" CODEBASE = "." NAME = "TestApplet" WIDTH = 400 HEIGHT = 300 ALIGN = middle VSPACE = 0 HSPACE = 0 scriptable=false pluginspage="http://java.sun.com/products/plugin/1.3/plugininstall.html"> -->
Listing 3.2: Die konvertierte Webseite
Mit dieser Webseite läuft dann das Applet in den besagten Browsern einwandfrei. Der Appletviewer wird das Applet jedoch zweimal öffnen. Bei der Auswahl der zu konvertierenden Dateien ist zu beachten, dass beim Durchsuchen der Festplatte das Verzeichnis mit den zu konvertierenden Dateien nicht per Mausklick geöffnet werden darf. Das ist etwas ungewöhnlich.
Java 2 Kompendium
101
Kapitel 3
Das JDK Ausführlichere Details und laufend aktuelle Informationen zu den gesamten Prozessen finden Sie auf der Sun-Webseite http://java.sun.com/products/ jdk. Wir werden im Laufe des Buchs noch darauf zurückkommen.
3.3
Die JDK-Dokumentation
Für das JDK und die Java-Plattform wird von Sun eine umfangreiche (wenngleich oft recht verzweigte und daher unübersichtliche) Dokumentation zur Verfügung gestellt. Die Java-Dokumentation beinhaltet eine vollständige Beschreibung aller Pakete, Klassen, Methoden usw. sowie der JDKTools. Das JDK-Installationsfile alleine enthält diese Dokumentation jedoch nicht. Sie müssen sie (und sollten sie auf jeden Fall) separat von den SunServern laden (ca. 21,2 MByte für die Zip-Datei unter Windows). Dazu gibt es auf den Downloadseiten des JDK Hyperlinks, die zu der Downloadseite für die Dokumentationen der einzelnen JDKs führen. Sie finden dort diverse Formate der Dokumentation für die unterschiedlichen Plattformen. Über die Auswahl der jeweiligen Version und der Zielplattform landen Sie auf der konkreten Downloadseite. Abbildung 3.14: Die Online-Dokumentation zu Java und dem JDK
Zum Entpacken benötigen Sie ca. 110 MByte freien Platz auf dem Ziellaufwerk. Kaum zu glauben, wie groß eine Dokumentation werden kann. Aber die Dokumentation ist für eine effektive Arbeit mit Java unumgänglich! Und wenn man sich daran gewöhnt hat, ein vorzügliches Hilfsmittel. Sie sehen – es gibt mittlerweile ziemlich viel zu Java und dem JDK zu sagen und kein Buch zu Java kann das noch leisten, denn Java und die Dokumentation werden in Details permanent aktualisiert. Zudem ist es fraglich, ob die gleichen 102
Java 2 Kompendium
Fehlervorbeugung und Fehlerbehebung beim JDK
Kapitel 3
Informationen wie in der Online-Dokumentation in einem Buch aufbereitet werden sollten. Die Verknüpfung per Hyperlinks macht die Verwendung der Online-Dokumentation zum Nachschlagen von Details ideal. Bücher zu Java bieten nur dann einen Mehrwert, wenn sie mit übergeordneten Konzepten und Beispielen arbeiten und für die vollständigen Details auf die Online-Dokumentation verweisen. Wir wollen es so halten.
3.4
Fehlervorbeugung und Fehlerbehebung beim JDK
Die Installationsroutine des JDK legt wie gesagt eine Verzeichnisstruktur an, die Sie unter günstigen Umständen in die Lage versetzt, direkt die Installation zu testen und loszulegen. Die Tools des JDK lassen sich einfach auf Befehlszeilenebene aufrufen – etwa indem Sie unter Windows eine DOS-Box öffnen und dort die Programme aufrufen. Davor ist es jedoch sinnvoll, eine Anpassung der Java-Umgebung vorzunehmen. Unterlassen Sie dies, werden Sie unter Umständen immer wieder durch überflüssige Fehlersituationen genervt. Dabei kann eine Reihe von einfachen, vorab ausgeführten Schritten diese potenziellen Fehlerquellen erst gar nicht entstehen lassen. Einfach installieren und loslegen ist also bei der Arbeit mit dem JDK und Java nicht immer zu empfehlen. Vorher sollten einige Einstellungen überprüft und ggf. gesetzt sowie andere potenzielle Fehlerquellen vorab analysiert werden. Sie sparen sich später viel Zeit und Ärger. Zwischen den Tools des JDK 1.1.x (bzw. den Betaversion des JDK 1.2) auf der einen und den Tools der Nachfolgeversionen auf der anderen Seite haben sich einige schwerwiegende Veränderungen bezüglich ihres Aufrufs und den Voraussetzungen für eine korrekte Verwendung ergeben. Es gilt zwar prinzipiell, dass die Aufrufparameter bei Tools weitgehend unverändert blieben, wenn sie noch die gleiche Funktion wie bei dem zugehörigen Tool der Vorgängerversion haben. Außerdem sollten neue JDKs soweit wie möglich abwärtskompatibel sein. Einige wichtige Details sind jedoch verändert.
3.4.1
Pfad-Angaben
Da Sie in der Regel die JDK-Programme auf Befehlszeilenebene ausführen werden, ist es im Allgemeinen sinnvoll, die in der JDK-Umgebung verfügbaren Werkzeuge von allen Verzeichnissen auf Ihrem System aus zugänglich zu machen. Dies realisiert ein Eintrag in den Suchpfad des Computersystems. Für Windows-Anwender wird dies am sinnvollsten erreicht, indem man das Verzeichnis, das die Werkzeuge enthält, in die Pfadangaben (Set path) einträgt. Die Pfadangabe sollte ungefähr so aussehen: SET PATH= C:\;D:\;C:\JDK1.3\BIN;C:\WINDOWS;C:\WINDOWS\COMMAND
Java 2 Kompendium
103
Kapitel 3
Das JDK In unserem Beispiel ist C:\JDK1.3\BIN das Verzeichnis der JDK-Umgebung. In einer DOS/Windows-Umgebung werden Verzeichnisse mit Backslash getrennt, die einzelnen Einträge im Pfad mit Semikolon. Sie können den Pfad auf Kommandozeilenebene jederzeit überprüfen, indem Sie path ohne weitere Parameter eingeben. Sie können auch unter Windows einfach den aktuellen Pfad mit der folgenden Anweisung erweitern: path=%path%;C:\JDK1.3\BIN
Wenn Sie diese Anweisung mit einem entsprechend konfigurierten DOSFenster für Java koppeln (eine Batch-Datei oder etwas Ähnliches), sparen Sie sich die jeweils notwendige Eingabe bei einem neuen Öffnen der DOSBox. Auch unter Solaris können Sie die Pfadangabe setzen. Unix verwendet den normalen Slash zum Trennen der Verzeichnisebenen. Wenn Sie z.B. eine C-Shell (csh) verwenden, können Sie die Pfadangabe in der Startup-Datei (~/.cshrc) wie folgt setzen (Beispiel mit dem Verzeichnis jdk1.3): set path=($path /usr/local/jdk1.3/bin)
Anschließend laden Sie die Startup-Datei erneut (bzw. neu einloggen) und überprüfen, ob der Pfad richtig gesetzt ist. Wenn Sie später versuchen, innerhalb eines bestimmten Unterverzeichnisses zu kompilieren und die Fehlermeldung »Bad command or filename« (»Ungültiger Befehl oder Dateiname«) unter Windows bzw. »File not found« unter Unix bekommen, liegt das wahrscheinlich daran, dass diese Pfadangabe nicht korrekt ist. Es gibt diverse Gründe, warum man verschiedene Versionen des JDK gleichzeitig auf dem Rechner verwenden möchte. In diesem Fall sollten Sie jedoch unbedingt mit verschiedenen Umgebungen (z.B. DOS-Fenster unter Windows oder Shell-Fenster unter Solaris) arbeiten, die mit unterschiedlichen Pfadangaben konfiguriert sind. Es langt definitiv nicht, sämtliche parallel vorhandenen JDK-Verzeichnisse für die unterschiedlichen JDKVersionen in den Suchpfad aufzunehmen. Der Kommando-Interpreter wird immer zuerst im aktuellen Verzeichnis und dann entsprechend der Reihenfolge der eingetragenen Verzeichnisse nach einem Programm suchen. Er wird immer die erste gefundene Version eines Tools verwenden. Es wird also immer nur die Tool-Version ausgeführt, die im aktuellen Verzeichnis vorhanden ist oder die zuerst in der Pfadangabe aufgelistet wird.
104
Java 2 Kompendium
Fehlervorbeugung und Fehlerbehebung beim JDK
3.4.2
Kapitel 3
CLASSPATH
Die besprochenen Schwierigkeiten mit der CLASSPATH-Umgebungsvariable bei verschiedenen JDK-Versionen sind ein noch entscheidenderes Argument für die Verwendung mehrerer Umgebungen. Bei der Angabe CLASSPATH handelt es sich um eine Pfadangabe, über die Java (genau genommen bestimmte Tools von Java, aber wir wollen der Einfachkeit halber von Java reden) Klassen sucht, die es zur Laufzeit benötigt. Dabei muss ganz massiv zwischen dem JDK ab der Version 1.2 (Final) und seinen Vorgängerversionen (inklusive der meisten Betaversionen des JDK 1.2) unterschieden werden. Mit der Finalversion des JDK 1.2 wurde ein neues Suchpfad-Konzept für Klassen eingeführt. Aber der Reihe nach – schauen wir uns an, wie es davor war: Es gibt den Suchpfad für Java-Klassen in Rahmen des Java-Konzeptes in zwei Varianten. Bei CLASSPATH handelt es sich einmal um eine so genannte Umgebungsvariable. Umgebungsvariablen sind Einstellungen, mit denen Sie Ihr System spezifizieren können. Man kann sie sich als speicherresidente Variablen des Betriebssystems vorstellen, die globale Einstellungen festlegen. Sie werden z.B. unter der Windows-Plattform im Allgemeinen auf Befehlezeilenebene oder in der Autoexec.Bat mit der Anweisung »SET [Umgebungsvariable] = ...« gesetzt. Die globale Pfadangabe setzen Sie in einer Windows-Umgebung also wie folgt: Set CLASSPATH=c:\java\lib\classes.zip;. oder Set CLASSPATH=c:\jdkx.x.x\lib\classes.zip;.
ß ig den x.x.x ist dabei wieder die konkrete Versionsangabe, die defaultmä Namen des JDK-Verzeichnisses festlegt. Classes.zip ist das RUNTIMEModul (die Systemklassen) von Java in den früheren Versionen. Dabei fällt wahrscheinlich auf, dass explizit eine ZIP-Datei angegeben ist. Java ist in der Lage, diese ZIP-Dateien direkt zu nutzen. Der nach dem Semikolon folgende Punkt gehört ebenfalls zur CLASSPATH-Umgebungsvariable und steht für das aktuelle Verzeichnis. Damit werden JDK-Programme noch einmal ausdrücklich angewiesen, das aktuelle Verzeichnis zu durchsuchen. Neben dem globalen CLASSPATH gibt es einen weiteren Suchpfad für die Systemklassen, der als Option -classpath bei den entsprechenden Tools individuell gesetzt werden kann und die globale Angabe überschreibt. In den alten Versionen des JDK wurden alle Java-Klassen mithilfe des Pfades gesucht, der über die Option -classpath bei einem Tool oder der CLASSPATH-Umgebungvariable gesetzt war.
Java 2 Kompendium
105
Kapitel 3
Das JDK Über die Angagen bestimmen Sie in diesen älteren Versionen der JavaUmgebung, von wo aus Java-Klassen importiert werden. Verwenden Sie unter Windows das Semikolon, um die Liste von Verzeichnissen (und ZIPDateien, die Java direkt verwenden kann) voneinander zu trennen. Unix benötigt Doppelpunkte. Um sich das Verfahren klar zu machen, denken Sie einmal daran, wie normal ausführbare Dateien aus der Befehlszeile aufgerufen werden. Wenn ein DOS- oder Unix-Anwender den Namen eines Programms eingibt und auf die Eingabetaste drückt, sucht der Befehls-Interpreter nach einem ausführbaren Programm für den angegebenen Namen, lädt das Programm, wenn er es findet, in den Speicher und beginnt mit der Ausführung. Die Path-Variable bestimmt, wo und in welcher Reihenfolge der Befehlsinterpreter nach dem ausführbaren Programm sucht. Bei dem Suchkonzept für Java-Klassen ist es zwar ähnlich, wobei aber Java-Programme nicht direkt ausgeführt werden, sondern indirekt vom Java-Interpreter. Sie werden dazu vom Interpreter, einem Browser oder einem Hilfsprogramm wie dem Appletviewer (Teil des Java Development Kits) geladen und dort dann ausgeführt. Da diese Dateien also nicht direkt vom Betriebssystem ausgeführt werden können, wird die Path-Variable des Betriebssystems keinen Effekt mehr haben (darüber würde nur der Interpreter gefunden). Aus diesem Grund wird für diese Funktionalität Ersatz benötigt. Wo ein Betriebssystem die Path-Variable verwendet, um ausführbare Dateien zu finden, nimmt Java einen internen Suchpfad, um Klassen zu finden. Java durchsucht die angegebenen Verzeichnisse, um geeignete .class-Dateien zu finden. Wenn Sie verschiedene Versionen des JDK gleichzeitig auf dem Rechner verwenden möchte, sollten Sie wie gesagt mit zwei oder mehr verschiedenen Umgebungen arbeiten. Ganz wichtig ist dann jedoch, dass bei älteren JDKVersionen auch die CLASSPATH-Angaben unterschiedlich konfiguriert sind. Dazu sollten Sie in jeder DOS-Box bzw. jedem Shell-Fenster die CLASSPATHAngaben explizit setzen. Am besten löschen Sie mit set CLASSPATH= (Windows) bzw. % unsetenv CLASSPATH (Solaris) zuerst eine evtl. vorhandene Umgebungsvariable und setzen sie dann auf das aktuelle JDK-Verzeichnis. Man kann zwar die CLASSPATH-Angabe über getrennte Einträge auf verschiedene Verzeichnisse erweitern. Davon ist aber dringend abzuraten. Aus zwei Gründen: zum einen wird es aus ähnlichen Gründen wie bei der PathAngabe nicht einwandfrei funktionieren, zum anderen sollte aus Sicherheitsgründen die CLASSPATH-Angabe nicht unnötig erweitert werden. Eine Klasse, die in einem mit der CLASSPATH-Angabe spezifizierten Verzeichnis steht, wird beim Laden vom Classloader nicht mehr auf Viren und Fehler geprüft. Deshalb sollten dort ausschließlich absolut vertrauenswürdige Klassen stehen. Nochmals zu Verdeutlichung: Die bisherigen Ausführungen zum CLASSPATH betreffen die JDK-Version 1.1.x und die meisten 1.2-Betaversionen, haben also die grundsätzliche Vorgehensweise und die Hintergründe der früheren JDKs behandelt. Ein wesentliches Problem bei diesen Versionen des CLASS-
106
Java 2 Kompendium
Fehlervorbeugung und Fehlerbehebung beim JDK
Kapitel 3
PATH war die Tatsache, dass es zu Konflikten zwischen der dauerhaft gesetzten Angabe und der -classpath-Option einzelner Tools kommen konnte
(Versionskonflikte der Laufzeitumgebung usw.). Sun empfiehlt prinzipiell für neue JDKs, die globale Angabe überhaupt nicht mehr zu setzen und die individuelle Variante zu verwenden, sofern Klassen nicht gefunden werden. Diese Aussage muss aber sogar noch verschärft werden (trotz anders lautender Beteuerungen von Sun). In neuen Versionen des JDK kann die CLASSPATH-Umgebungsvariable eine Menge Ärger bereiten. Sie sollten unbedingt darauf verzichten und bei scheinbar unerklärlichen Fehlern beim Finden von Klassen die Umgebungsvariablen überprüfen. Setzen Sie die Umgebungsvariable explizit auf leer (Set CLASSPATH=). Java geht ab dem JDK 1.2 (Final) beim Finden von Klassen prinzipiell einen neuen Weg. Leider dokumentiert Sun dies nicht so deutlich und leicht verständlich, wie es wünschenswert wäre. Das kurz nach der Freigabe des JDK1.2-Finals vor Hilferufen überquellende Usenet verdeutlichte dies recht drastisch. Die meisten Hilferufe in allen Sprachen waren der Art »Ich habe gerade das JDK 1.2 geladen und installiert und nichts geht mehr« oder »Ich habe gerade das JDK 1.2 installiert und finde die Datei classes.zip nicht. Ich verwende aber das Entwicklungstool xyz, das diese Datei benötigt«. Eine Suche nach anderen Schlagworten rund um die Laufzeitumgebung von Java führte zu zahlreichen weiteren (oft ziemlich verzweifelten) JDK-1.2Anwendern. Aber es ist alles halb so schlimm – nur komplett anders als in den früheren JDK-Versionen. Die meisten Probleme waren ziemlich überflüssig und wären bei einer besseren Bekanntmachung durch Sun nie aufgetreten. Zwar hat Sun ziemlich brutal Dinge im JDK verändert, aber die Probleme beruhten auf der unverständlichen Dokumentation dieser Veränderungen nach außen. Probleme hatten vor allem die JDK-Anwender, die bereits mit einem älteren JDK arbeiteten. Diese suchten analog den bisherigen JDKs die Datei classes.zip und fanden sie nicht mehr. Worauf sollte also der CLASSPATH gesetzt werden? Die Antwort auf alle Fragen1 ist so einfach, dass Sun es wohl nicht für nötig erachtet hat, dieses richtig deutlich zu machen – das JDK ab der Version 1.2 (Final) benötigt die Datei classes.zip nicht mehr und der CLASSPATH muss (und darf – beachten Sie den Hinweis) gar nicht mehr gesetzt werden. Nicht mehr und nicht weniger.
1
natürlich 42, aber darum geht es hier nicht ;-)
Java 2 Kompendium
107
Kapitel 3
Das JDK Die Veränderungen fanden genau genommen in der Beta-4-Version des JDK 1.2 statt. Dies dürfte auch einer der Hauptgründe für die allgemeine Verwirrung um den CLASSPATH und die Datei classes.zip sein. Zum Zeitpunkt des Erscheinens der letzten Betaversion wartete die ganze Welt bereits auf das Finalrelease. Immerhin war es bereits mehr als ein halbes Jahr überfällig. Die letzte Betaversion wurde deshalb nicht so stark beachtet und die massiven Veränderungen fielen nur wenigen Anwendern auf. In der Finalversion wurden diese Beta-4-Veränderungen dann nur noch sehr mager deutlich gemacht. Dass die CLASSPATH-Angabe nicht gesetzt wird (bzw. werden darf), bedeutet nicht, dass die Tools des JDK die Systemklassen nicht mehr benötigen. Die Systemklassen (in der Datei rt.jar) finden sie nur nach einer erfolgreich durchgeführten Installation des JDK automatisch, wenn sie im lib-Verzeichnis von jre (die Laufzeitumgebung) vorhanden sind. Dabei ist es belanglos, ob es sich um das jre-Verzeichnis innerhalb des JDK-Verzeichnisses handelt oder das eigenständige jre-Verzeichnis einer Installation durch andere Programme. Ab dem JDK 1.2 gibt es nun ein Konzept mit drei Suchpfaden, die zum Auffinden von Klassen verwendet werden2. Die Suche erfolgt streng hierarchisch nach folgendem Ablauf: 1.
Die erste Stelle, an der Java nach Klassen sucht, ist der bootstrap Classpath – der Standardpfad zu den Systemklassen, der bereits nach der erfolgreichen Installation des JDK festliegt. Das aktuelle Verzeichnis ist immer darin enthalten. Der Wert dieser Pfadangabe kann über System.getProperty("sun.boot.class.path") ermittelt werden. Diese Pfadangabe sollte beim Aufruf eines Tools normalerweise nicht verändert werden. Die Option -classpath überschreibt diesen Pfad nicht mehr (wie etwa im alten Konzept), sondern gibt nur an, wo die anwenderdefinierten Klassen zusätzlich zu suchen sind. Zum Überschreiben des bootstrap Classpath (was so gut wie nie notwendig sein sollte) gibt es die neu eingeführte Aufrufoption -Xbootclasspath/-bootclasspath.
2.
Der zweite Suchpfad sind die optionalen Java-Erweiterungsverzeichnisse. Die Liste dieser Verzeichnisse kann über System.getProperty("java.ext.dirs") ermittelt werden.
3.
Die dritte und letzte Stelle, wo Java nach Klassen sucht, ist der »Applikations-Classpath« (also der von der Applikation individuell durch eine Option gesetzte Suchpfad, der auf anwenderdefinierte Klassen verweist). Der Wert von dieser Pfadangabe kann über System.getPro-
2
108
Damit soll diversen Problemen begegnet werden, die beim bisherigen Konzept auftauchten.
Java 2 Kompendium
Fehlervorbeugung und Fehlerbehebung beim JDK
Kapitel 3
perty("java.class.path") ermittelt werden. Falls die Option bei einem Tool-Aufruf nicht gesetzt wird, werden Sie für diesen Wert einen Punkt erhalten.
Wenn Sie sich bereits mit Java und den Tools ein wenig auskennen, können Sie das nachfolgende Testprogramm ausprobieren. Wenn Sie den JavaQuelltext selbst eingeben wollen, starten Sie bitte einen beliebigen ASCIIEditor und geben Folgendes ein (Name der Datei: Pfade.java): class Pfade { public static void main(String argv[]){ System.out.println("Der bootstrap Classpath: " + System.getProperty("sun.boot.class.path")); System.out.println("Die optionalen Java-Erweiterungsverzeichnisse: " + System.getProperty("java.ext.dirs")); System.out.println("Der Applikations-Classpath: " + System.getProperty("java.class.path")); } }
Listing 3.3: Pfade
Über den Aufruf javac Pfade.java können Sie den Compiler anweisen, die Datei zu übersetzen. Anschließend rufen Sie das Programm über java Pfade auf. Sie finden den Quelltext samt bereits übersetztem Programm auch auf der CD in dem Begleitmaterial zu diesem Kapitel. Abbildung 3.15: Pfadeinstellungen
3.4.3
Verwendung von mehreren JDK-Versionen unter Windows
Wenn Sie unter Windows verschiedene Versionen des JDK gleichzeitig auf dem Rechner verwenden möchten und deshalb unterschiedlich konfigurierte DOS-Boxen benötigen, sind zwei (oder entsprechend mehr) kleine Batchdateien vielleicht sehr hilfreich. Tragen Sie in der Autoexec.Bat keine um das Verzeichnis der JDK-Tools erweiterte Pfadangabe und keine CLASSPATH-Umgebungsvariable ein (dies ist Java 2 Kompendium
109
Kapitel 3
Das JDK sowieso nur noch nötig, falls Sie mit einem älteren JDK arbeiten) ein. Statt dessen erstellen Sie entsprechend der Zahl Ihrer gewünschten JDK-Versionen die folgenden Batchdateien: Für JDK-Versionen vor 1.2: PATH=%path%;[Verzeichnis der JDK-Tools]; SET CLASSPATH=[Verzeichnis der Systemdateien]\classes.zip;.
Für JDK-Versionen ab 1.2: PATH=%path%;[Verzeichnis der JDK-Tools]; SET CLASSPATH=
Speichern Sie die Dateien in Ihrem BATCH-Verzeichnis (wenn Sie ein solches haben) oder in einem Verzeichnis in dem Suchpfad des Kommando-Interpreters Ihres Betriebssystems. Ihr Pfad kann nicht beliebig lang werden. Sonst bekommen Sie irgendwann die Fehlermeldung »Kein Platz im Umgebungsbereich«. Arbeiten Sie mit nicht zu langen Pfadangaben (bzw. setzten Sie Ihren Pfad aus mehreren einzelnen Teilstücken, die in verschiedenen Variablen gespeichert werden, zusammen) oder konfigurieren Sie Ihren Rechner mittels der Umgebungsparameter so, dass er einen grö ß eren Umgebungsbereich nutzen kann. Dazu muss jedoch im Rahmen eines Java-Buchs auf entsprechende Literatur zum Betriebssystem verwiesen werden. Wenn Sie nun eine DOS-Box für die Arbeit mit dem JDK öffnen, rufen Sie einfach zuerst die passende Batchdatei auf und die Pfad- und Umgebungsangaben sind korrekt gesetzt. Noch komfortabler unter Windows ist die Erstellung eines eigenen Programmsymbols für die jeweilige JDK-DOS-Box im Arbeitsplatz oder Windows-Startmenü mit der direkten Übergabe der passenden Batchdatei als Programmaufruf-Parameter. Die DOS-Boxen werden in Windows über so genannten PIF-Dateien (Program Interfaces) realisiert. Erstellen Sie zuerst für jede JDK-DOS-Box im Windows-Startmenü einen eigenen Eintrag oder erstellen Sie im Arbeitsplatz bzw. Explorer ein Icon für einen Programmaufruf. Am einfachsten geht das, wenn Sie die Orginal-DOS-Eingabeaufforderung duplizieren und nur mit neuen Namen versehen. Sie können folgendermaßen vorgehen:
110
1.
Öffnen Sie zuerst den Arbeitsplatz.
2.
Wechseln Sie in das gewünschte Verzeichnis.
Java 2 Kompendium
Fehlervorbeugung und Fehlerbehebung beim JDK
Kapitel 3
3. Erstellen Sie dort eine Verknüpfung zum DOS-Prompt. Dazu suchen Sie sich ein bestehendes Symbol für das DOS-Prompt und klicken beispielsweise mit der rechten Maustaste darauf. Im Kontextmenü finden Sie dann einen Eintrag zur Erstellung einer Verknüpfung. 4. Benennen Sie die Verknüpfung um (z.B. JDK 1.0 oder JDK 1.3). 5. Wiederholen Sie den Vorgang für alle JDK-Versionen, die Sie verwenden wollen. 6. Nun müssen die PIF-Dateien entsprechend angepasst werden. Die Bearbeitung von solchen PIF-Dateien erfolgt in Windows-Versionen vor Windows 95 bzw. NT 4 in dem so genannten PIF-Editor. Das Eigenschaftenfenster ersetzt ab Windows 95 und Windows NT ab der Version 4.0 den PIF-Editor. Der PIF-Editor soll noch kurz am Ende des Abschnitts erklärt werden, wir setzen den Vorgang mit dem Eigenschaftenfenster fort. Klicken Sie mit der rechten Maustaste im Arbeitsplatz auf das Symbol für das Programm. 7. Klicken Sie im Menü DATEI oder im Kontextmenü auf EIGENSCHAFTEN. 8. Wählen Sie dort das Registerblatt PROGRAMM. 9. Tragen Sie die gewünschte Programmbezeichnung ein. 10. Legen Sie bei Bedarf ein Anfangsverzeichnis fest. 11. Jetzt kommt das Wichtigste: Im Textfeld STAPELVERARBEITUNGSDATEI muss die passende Batchdatei (am besten mit vollständiger Pfadangabe) als Programmaufruf-Parameter eingetragen werden. 12. Wenn Sie wollen, können Sie mit der Schaltfäche ANDERES SYMBOL noch ein schöneres Icon wählen. 13. Sie können festlegen, ob die JDK-BOX in einem Fenster oder als Vollbild gestartet wird, indem Sie die Eigenschaften für Ausführen in der Registerkarte BILDSCHIRM ändern. Vollbild ist in der Regel zu empfehlen. 14. Wiederholen Sie den Vorgang für alle JDK-Versionen, die Sie verwenden wollen. Die angegebenen Einstellungen werden mit der Batchdatei immer dann verwendet, wenn Sie das Programm durch Doppelklicken auf das zugehörige Symbol starten. Es sollten alle Pfad- und Set-Angaben nun für jede JDKVersion passen. In Windows werden Programme meist mit dem Startmenü gestartet. Deshalb wollen wir nun noch die JDK-Boxen dort eintragen: 1.
Klicken Sie mit der rechten Maustaste auf eine freie Stelle der Taskleiste.
Java 2 Kompendium
111
Kapitel 3
Das JDK 2.
Wählen Sie dort Eigenschaften aus.
3.
Wählen Sie das Registerblatt Programme im Menü »Start«.
4.
Wählen Sie die Schaltfäche Hinzufügen.
5.
Geben Sie die Befehlszeile für die DOS-Eingabeaufforderung ein oder verwenden Sie die Durchsuchen-Schaltfäche. Auf jeden Fall sollte eine PIF-Datei für die jeweilige Version des JDK dort verwendet werden.
6.
Im nächsten Schritt legen Sie fest, wo genau das Programmsymbol im Startmenü positioniert werden soll.
7.
Im letzten Schritt muss noch die Bezeichnung für die JDK-Box gewählt werden.
Den PIF-Editor unter Windows NT vor der Version 4.0 rufen Sie in der Programmgruppe Hauptgruppe oder mit dem Befehl PIFEDIT auf. Laden Sie dort die Datei DOSPRMPT.PIF als Schablone. Verfahren Sie dann analog wie oben beschrieben. Statt im Textfeld Stapelverarbeitungsdatei tragen Sie die passende Batchdatei (am besten mit vollständiger Pfadangabe) als die Batchdateien im Textfeld Programmparameter ein. Es funktioniert nicht, wenn Sie eine DOS-Box öffnen, dort eine der Batchdateien aufrufen und mit der einen JDK-Version arbeiten und anschließend dort eine der anderen Batchdateien aufrufen, um in der gleichen DOS-Box mit einer anderen JDK-Version zu arbeiten. Der Grund ist der Pfad, der dann nur um das andere JDK-Verzeichnis erweitert wird. Sie haben dann oben beschriebene Probleme mit dem Suchpfad. Sie müssen explizit eine neue DOS-Box öffnen.
3.4.4
»Class not found« und andere Fehlermeldungen
Häufig auftretende Fehlermeldungen beim Arbeiten mit dem JDK – hauptsächlich dem Java-Interpreter oder einem Browser – sind »Class not found«, »Unable to initialize threads« oder »Can’t find class ...«. Dies kann leider diverse Ursachen haben. Dabei ist die einfachste Lösung, dass Sie beim Aufruf des Java-Interpreters einen Schreibfehler gemacht haben oder die Groß- und Kleinschreibung beim Namen der Klasse nicht genau eingehalten haben. Java unterscheidet strikt zwischen groß und klein geschriebenen Namen. Ein fehlender Eintrag der JDK-Tools in den Suchpfad kann ebenso für diese Meldungen verantwortlich sein. Ein weiterer einfacher Fehler ist, dass beim Aufruf des Java-Interpreters die Erweiterung .class mit eingegeben wurde. Es darf aber nur der Klassen-
112
Java 2 Kompendium
Fehlervorbeugung und Fehlerbehebung beim JDK
Kapitel 3
namen spezifiziert werden und nicht die Klassenerweiterung. Dazu mehr bei der Besprechung der einzelnen JDK-Werkzeuge. Ein anderer Grund betrifft die Systemklassen. Stellen Sie sicher, dass sich Ihre Systemklassen auch in dem Verzeichnis befinden, in dem Sie sie installiert haben. Leider müssen diese unliebsamen Fehlermeldungen bei nicht richtig eingerichteten Entwicklungsplattformen unter Umständen noch anderen potenziellen Fehlerquellen zugeschrieben werden. Es gibt viel heimtückischere Fehlerkonstellationen, die eigentlich nichts direkt mit dem JDK zu tun haben. Besonders, wenn man mit Windows 95/98 und einigen » ä lteren« 16-BitProgrammen arbeitet (was heute zwar selten vorkommen sollte, aber nicht auszuschließen ist). Es gibt einige Arbeitsschritte, die bei solchen Mischkonstellationen .class-Dateien oder andere Objekte so beeinflussen, dass sie nicht mehr korrekt verwendet werden können. Zwei potenzielle Fehlersituationen folgen, aber das gilt in demselben Maße für analoge Situationen. Wenn man Dateien mit schnellen und einfachen DOS-(16-Bit-) Programmen kopiert, wird die 16-Bit-Software die Dateien zwar korrekt kopieren, dabei jedoch eventuell auftretende lange Dateinamen (unter anderem die Endung .class) verstümmeln (auf die Kennung .cla, und das langt weder einem Browser noch dem Java-Interpreter). Außerdem sind lange Namen vor dem Punkt auf maximal 8 Zeichen verkürzt, was jedoch nicht so schwerwiegend sein muss (dies hat beispielsweise keine Auswirkung, wenn der Name sowieso kürzer war). Deshalb ist es notwendig, Dateien mit langen Dateinamen unbedingt mit 32-Bit-Software zu kopieren. Dieses Problem entsteht übrigens in gleicher Weise, wenn man mit 16-Bit-Komprimierungsprogrammen oder anderen datenverarbeitenden 16-Bit-Programmen arbeitet. Eine weitere Fehlerquelle hat mit dem eben beschriebenen Problem viel gemeinsam, obwohl nicht mit 16-Bit-Software kopiert wird. Sie ist daher noch heimtückischer. Wenn auf einem PC mit DOS oder Windows 3.x neuere Windows-Versionen installiert werden, wird bei der Installation eine Hardware-Erkennung durchgeführt. Dieser Mechanismus arbeitet (vor allem unter Windows 95) nur mäß ig stabil, sodass ein CD-Laufwerk oft nicht korrekt identifiziert wird. Dennoch wird es unter Umständen anzusprechen sein, denn beim Start von Windows 95 werden die eventuell vorhandene alte AUTOEXEC.BAT und die CONFIG.SYS abgearbeitet und alte 16-BitTreiber für das CD-Laufwerk eingerichtet. In diesem Fall kann man relativ problemlos auf eine CD zugreifen. Wenn man wie üblich auf CDs arbeitet und höchstens ein Programm mit einem maximal 8 Zeichen langen Namen (normalerweise auf CDs ein Setup-Programm) startet, werden kaum Probleme auftreten.
Java 2 Kompendium
113
Kapitel 3
Das JDK Anders sieht es aus, wenn man zum Beispiel von einer CD Dateien kopiert. Dann werden die Dateinamen durch die 16-Bit-Treiber wie oben verstümmelt. Sogar, wenn man mit den Windows 95-eigenen Tools wie dem Windows Explorer oder dem Windows-Arbeitsplatz arbeitet. Diese Tools interpretieren durch die falsche Hardware-Erkennung des CD-Laufwerks alle Objekte auf einer CD nur mit den DOS-Namenskonventionen. Das führt beispielsweise dazu, dass ein Doppelklick auf eine auf der CD befindliche HTML-Datei den verknüpften Browser korrekt startet, die HTML-Datei wahrscheinlich sauber lädt, aber dann alle über lange Dateinamen angesprochene Objekte (Java-Applets, jedoch auch zum Beispiel Grafiken mit langen Namen) nicht finden kann. Erkennen kann man diese Situation relativ einfach, da lange Dateinamen in Anzeigefenstern in der typischen 8-Bit-Schreibweise mit dem ~-Zeichen auftreten. Leider hat man oft eine Anzeigeform gewählt, die dazu führt, dass man sich bei Auftreten des Zeichens nichts denkt und vor lauter Wald die Bäume nicht sieht.
Abbildung 3.16: Die Schlange bei den Dateinamen macht das Problem sogar im Windows Explorer deutlich.
Bei Vorliegen dieser Fehlerkonstellation muss das CD-Laufwerk neu unter Windows angemeldet werden. Dazu ist es am besten, wenn beim Starten von Windows die Abarbeitung von der alten AUTOEXEC.BAT und der CONFIG.SYS mit der F8-Taste unterbunden wird, dann aus dem Bootmenü die Einzelbestätigung ausgewählt und bei der Frage nach Ausführen der AUTOEXEC.BAT und CONFIG.SYS verneint wird. Wundern Sie sich nicht, wenn Ihr CD-Laufwerk anschließend nicht mehr erkannt wird, denn in der beschriebenen Fehlersituation war die Erkennung ja nur durch alte DOS-Treiber 114
Java 2 Kompendium
Fehlervorbeugung und Fehlerbehebung beim JDK
Kapitel 3
gewährleistet. Führen Sie die Hardware-Erkennung von Windows manuell durch. Danach ist das Problem (hoffentlich) beseitigt. Die Meldungen »Applet can't start« oder »Applet not initializied« gehören gleichfalls in diese Reihe von Meldungen, die oft auf eine Mischkonstellation bei der Software zurückzuführen sind. Ein weiterer Grund für Fehler beim Starten von Applets und für Fehler des Java-Interpreters kann sein, dass das Applet/die Java-Anwendung in einem falschen Verzeichnis gesucht wird. Im Fall von Applets und HTML-Quelltext ist die Situation beispielsweise die folgende: Es wird im <APPLET>-Tag mit CODEBASE=[Verzeichnis] auf ein konkretes Verzeichnis verwiesen (absolut oder meist relativ), wo sich das Applet befinden muss. Falls die CODEBASE=[Verzeichnis]-Angabe im <APPLET>-Tag der HTML-Datei fehlt (Regelfall), wird davon ausgegangen, dass sich das Applet im selben Verzeichnis wie die HTML-Datei befindet. Falls dies nicht der Fall ist, kommt eine Fehlermeldung. Die Fehlermeldung »net.socketException: errno = 10047« oder »Unsupported version of Windows Socket API« kann auftauchen, wenn Sie die falschen TCP/IP-Treiber verwenden. Die Meldung »System Error during Decompression« kann angezeigt werden, wenn nicht genug Platz auf der Festplatte mit Ihrem TEMP-Verzeichnis vorhanden ist. Die Meldung »This program cannot be run in DOS mode« erscheint, wenn die DOS-Shell falsch konfiguriert ist. Wenn der Appletviewer keine Applets lädt, müssen unter Umständen einige Umgebungsvariablen gesetzt werden. Etwa so: set HOMEDRIVE=c: set HOMEPATH=\
oder set HOME=c:\
und anschließendem Neustart des Appletviewers. Falls das nicht hilft, versuchen Sie über java -verbose sun.applet.AppletViewer herauszufinden, welche Klassen von wo geladen werden. Anhand der ausgegebenen Liste können Sie überprüfen, ob alle Klassen vorhanden sind. Damit kann auch die allgemeine Funktion des Appletviewers überprüft werden.
Java 2 Kompendium
115
Kapitel 3
Das JDK
3.4.5
Die Java-Umgebungsvariablen
Java besitzt einige Umgebungsvariablen, die in früheren Versionen des JDK gesetzt werden mussten (was immer noch von Interesse ist, wenn man verschiedene Versionen des JDK führen muss). Die meisten Umgebungsvariablen werden nicht mehr verwendet. Hauptsächlich der HotJava-Browser in der alten Version benötigt sie. Seit der parallel zum JDK 1.1.1 oder höher vertriebenen Version des HotJava ist es nicht mehr notwendig, die BrowserUmgebungsvariablen zu setzen. Die CLASSPATH- und die JAVA_HOME-Umgebungvariable allerdings haben nichts damit zu tun und sind oft bei fehlerhafter Zuweisung Quelle diverser Fehlermeldungen. Unter Umständen können Sie die folgende Auflistung von Umgebungsvariablen setzen und einige Probleme bei alten Versionen beheben. Schaden kann ein Setzen der Umgebungsvariablen selten. WWW_HOME: Setzt eine Standard-URL für die Start-Homepage des HotJava-
Browsers. HOTJAVA_HOME: Einstellung des Verzeichnisses, in dem HotJava nach den Dateien sucht, die notwendig sind, um den Browser korrekt ablaufen zu lassen. HOTJAVA_READ_PATH: Diese Umgebungsvariable legt für HotJava das Verzeichnis fest, in dem Applets auf Dateien einen Zugang mit Leserechten haben. Dies kann als Sicherheitsvorkehrung für Applets, die möglicherweise nicht autorisierte Informationen lesen könnten, verwendet werden. HOTJAVA_WRITE_PATH: Das Analogon zur HOTJAVA_READ_PATH-Umgebungsvariable für den Fall von Schreibvorgängen. CLASSPATH: Der Suchpfad für Klassen (wir sind darauf bereits eingegangen). JAVA_HOME: Normalerweise muss das Verzeichnis des JDK nicht explizit in
dieser Umgebungsvariable gespeichert werden. Falls Sie allerdings die Fehlermeldung »Error Message: Could not read properties file« erhalten, kann es in einigen Versionen des JDK daran liegen, dass diese Umgebungsvariable nicht gesetzt ist. In diesem Fall können Sie testweise JAVA_HOME auf das JDKVerzeichnis setzen. Auch die Meldung »Error message: Invalid JAVA_HOME« deutet auf einen Fehler in der Umgebungsvariable JAVA_HOME hin. Sie ist dann zwar gesetzt, jedoch auf das falsche Verzeichnis.
3.4.6
Unbekannte Fehler
Natürlich gibt es in dem JDK Fehler und Probleme, die man noch nicht erkannt bzw. beseitigt hatte, als die jeweilige Version der Software auf den Markt kam. Es gibt deshalb seit der ersten Version des JDK immer eine 116
Java 2 Kompendium
Die wichtigsten JDK-Tools
Kapitel 3
aktuelle Liste der wichtigen neu erkannten und der für das nächste Update bereinigten Fehler. Unter http://java.sun.com/products/jdk/ finden Sie aktuelle Fehlermeldungen. Besser ist jedoch, wenn Sie als registriertes Mitglied an die Bug-Database der Developer-Connection (unter http://developer.java.sun.com/developer/) kommen.
3.5
Die wichtigsten JDK-Tools
Das JDK besteht aus einer ganzen Reihe von Tools. Die wichtigsten Tools waren schon Bestandteil der 1.x-Versionen, viele kamen jedoch erst in der 1.1-Version dazu. Natürlich brachten Folgeversionen weitere Neuerungen (insbesondere wurden Tools der Version 1.1 wieder beseitigt, weil sie nicht so funktioniert hatten wie geplant oder weil sie nicht mehr benötigt wurden). Der Java-Interpreter und der Java-Compiler sind ja schon mehrfach aufgetaucht. Auch der Begriff Appletviewer ist schon gefallen. Es gibt aber noch weitere Tools, die sich im JDK-Unterverzeichnis \bin befinden sollten. An dieser Stelle wollen wir die Basis-Tools samt der wichtigsten Optionen besprechen. Im Anhang gehen wir dann noch intensiver auf die JDK-Programme ein. Zu den Basis-Programmen des JDK 1.3 (also denjenigen, die Sie zum Erstellen von Java-Applets und -Applikationen brauchen) zählen die folgenden: Der Appletviewer, um Java-Applets stand-alone zu betrachten Der Java-Compiler Javac Der Java-Interpreter Java und sein nahezu identisches Schwesterprogramm Javaw Diese Programme werden den Schwerpunkt der nachfolgenden detaillierteren Beschreibung der JDK-Tools bilden. Sie sind für die meisten Entwickler von zentraler Bedeutung. Neben den Basistools beinhaltet das JDK eine Vielzahl an weiteren Programmen, die die zusätzlichen Funktionalitäten von Java abdecken. Diese Programme werden wir im Anhang besprechen.
3.5.1
Ein paar allgemeine Hinweise zu den JDK-Programmen
Bei der Arbeit mit den JDK-Programmen gibt es ein paar Punkte zu beachten. Teilweise sind sie nur von allgemeinem Interesse, einige Details sollte man jedoch kennen. Und zu guter Letzt können Sie sich bei der Arbeit mit den JDK-Programmen mit ein paar Tricks viele monotone Arbeitsvorgänge ersparen.
Java 2 Kompendium
117
Kapitel 3
Das JDK Wenn man ein Programm des JDK ohne irgendwelche Parameter aufruft, bekommt man manchmal die notwendigen Angaben für die korrekte Syntax in Kurzform aufgelistet. Die Betonung liegt bedauerlicherweise auf »manchmal«. Die JDK-Programme erweisen sich in der Handhabung leider als nicht vollständig aufeinander abgestimmt. Ein Aufruf ohne Parameter schadet zwar nie, aber das Resultat ist nicht immer befriedigend. Der Interpreter beispielsweise erzeugt eine recht ausführliche Hilfe auf dem Bildschirm, der Compiler zumindest noch eine Kurzhilfe, während andere Programme in einigen Versionen des JDK mit Fehlermeldungen reagieren. Auch die manchmal existente Option -Help funktioniert in einigen JDK-Versionen beim Schwesterprogramm unter Umständen nicht. Die Option -version, die mal funktioniert, mal nicht, zählt zu den weiteren Kinderkrankheiten von einigen Versionen des JDK. Die unterschiedliche Handhabung bei der Angabe von Dateierweiterungen gehört ebenfalls zu dieser inkonsequenten Logik. Andererseits darf man bei einem kostenlosen Entwicklungswerkzeug dieser Mächtigkeit in Details keine Perfektion erwarten. Hauptsache, man kann Java nutzen. Für die eigentlichen Programmaufrufe ist eine Arbeit auf Befehlszeilenebene auf jeden Fall zu empfehlen (so lange man ohne IDE arbeitet). Zwar kann man aus einer grafischen Oberfläche wie Windows Befehle mit Optionen und Parametern eingeben. Beispielsweise mit dem AUSFÜHREN-Befehl im Windows-Startmenü. Anzuraten ist eine solche Vorgehensweise jedoch nicht, da eventuelle Rückmeldungen des Systems schlecht oder gar nicht auszuwerten sind. Meist ist das Fenster schon wieder geschlossen, bevor man die Rückmeldung gelesen hat. Ein permanent offenes Befehlszeilenfenster ist auf jeden Fall besser. Ein – wahrscheinlich ziemlich trivialer – Hinweis muss dabei jedoch unter Windows unbedingt beachtet werden: Sie dürfen wegen der langen Dateinamen auf keinen Fall mit einer alten 8-BitDOS-Version arbeiten, sondern nur mit der Windows-Eingabeaufforderung bzw. der DOS-Version für Windows 95/98. Migrationen Grundsätzlich muss man sich Gedanken machen, wenn man eine Migration von einem JDK auf eine Nachfolgeversion vollziehen will. Dabei kann es einmal vorkommen, dass bisher verwendete Programme in der neuen Version nicht mehr vorzufinden sind. So lagen einige der Programme des JDK 1.1 jeweils in zwei Versionen vor. Es handelte sich jedes Mal um eine Standardvariante und eine mit _g erweiterte, nicht optimierte Spezialversion, die hauptsächlich für das Debugging eingesetzt werden konnte (die Syntax war immer identisch mit der Standardversion). Im JDK 1.2 wurden sie weggelassen. Wenn so etwas vorkommt, werden die Standardprogramme meist um die Funktionalität der weggelassenen Programme erweitert oder die Funktionalität macht keinen Sinn mehr.
118
Java 2 Kompendium
Die wichtigsten JDK-Tools
Kapitel 3
Recht analog verhält es sich mit Optionen. Ab dem JDK 1.2 verstehen einige Tool so genannte Non-Standard-Options. Neben dem Satz von Standardoptionen gibt es nun die Möglichkeiten, einen Satz von weitergehenden Kommandozeilenoptionen zu verwenden. Diese Non-Standard-Options beginnen bis auf wenige, explizit gekennzeichnete Ausnahmen mit einem -X, etwa -Xdebug oder -Xverify. Wichtigster Unterschied zwischen den Standardoptionen und den Nicht-Standardoptionen ist, dass die Unterstützung für die Standardoptionen im jeweiligen JDK und allen zukünftigen Versionen der JVM durch Sun garantiert wird. Eine Unterstützung für die Nicht-Standardoptionen wird jedoch ausdrücklich für zukünftige Versionen der JVM nicht garantiert. Beim Wechsel auf das JDK 1.3 wurden wieder diverse Optionen weggelassen, verändert oder nicht mehr dokumentiert. Dafür gibt es dann aber einige neue Tools, deren Zweck die Simulation der alten Tools ist. Auf den Webseiten von Sun finden Sie einen Abschnitt mit den häufig zu den JDK-Programmen und Java gestellten Fragen (FAQ). Schauen Sie sich einfach mal die Seite http://java.sun.com/products/jdk/faq.html an. DOSKEY Bei der Arbeit auf Befehlszeilenebene kann man sich viel Arbeit und Zeit sparen, indem man einen Tastaturpuffer für die letzten Eingaben verwendet. Unter NT und Windows 2000 steht ein solcher Puffer immer zur Verfügung, nicht aber unter Windows 95 oder 98. DOSKEY ist ein solcher Tastaturpuffer, der seit DOS-Urzeiten vorhanden ist und Befehlseingaben bearbeitet, Befehle erneut aufruft und Makros erstellt. Für die Verwendung von DOSKEY gilt folgende Syntax: DOSKEY [/Optionen] [Makroname=[Text]]
Die Optionen können nach Wahl frei kombiniert werden und die Reihenfolge ist ebenfalls beliebig. Optionen
Beschreibung
/BUFSIZE:Größ e
Definiert die Grö ß e des Befehlsspeichers (Standardgrö ß e ist 512).
/ECHO:on|off
Aktiviert/deaktiviert das Echo von Makroerweiterungen (Standardeinstellung ist on).
/FILE:Datei
Datei mit einer Makroliste.
/HISTORY
Zeigt alle gespeicherten Befehle an.
Java 2 Kompendium
Tabelle 3.2: Die Optionen von DOSKEY
119
Kapitel 3 Tabelle 3.2: Die Optionen von DOSKEY (Forts.)
Das JDK
Optionen
Beschreibung
/INSERT
Neu eingegebener Text wird in den vorhandenen eingefügt.
/KEYSIZE:Größ e
Definiert die Grö ß e des Tastaturspeichers (Standardgrö ß e ist 15).
/LINE:Größ e
Definiert die maximale Grö ß e des Zeilenspeichers (Standardgrö ß e ist 128).
/MACROS
Zeigt alle DOSKEY-Makros an.
/OVERSTRIKE
Neu eingegebener Text überschreibt vorhandenen (Standard).
/REINSTALL
Installiert eine neue Kopie von DOSKEY.
[Makroname]
Name für ein zu erstellendes Makro.
[Text]
Befehle, die durch das Makro auszuführen sind.
Die Bedienung von DOSKEY erfolgt im Wesentlichen mit den Pfeiltasten, ESC und den Funktionstasten. Hier folgen die vollständigen Tastenkombinationen: Tabelle 3.3: Die Tastenkombinationen von DOSKEY
120
Tastenkombination
Beschreibung
NACH-OBEN
Ruft den vorherigen Befehl ab.
NACH-UNTEN
Ruft den nächsten Befehl (wenn vorhanden) ab.
ESC
Löscht den aktuellen Befehl.
F7
Zeigt die gespeicherten Befehle an.
ALT+F7
Löscht die gespeicherten Befehle.
[Zeichen]F8
Sucht nach einem Befehl, der mit [Zeichen] beginnt.
F9
Wählt einen Befehl anhand seiner Nummer aus.
ALT+F10
Löscht die Makrodefinitionen.
Java 2 Kompendium
Die wichtigsten JDK-Tools
Kapitel 3
In der Makrodefinition von DOSKEY gibt es einige besondere Zeichenfolgen: Zeichenfolge
Beschreibung
$T
Befehlstrennzeichen. Ermöglicht die Verwendung von mehreren Befehlen in einem Makro.
$1-$9
Ersetzbare Parameter. Sie entsprechen ungefähr den Parametern %1 – %9 bei Stapelverarbeitungsdateien.
$*
Symbol für Elemente, die in der Befehlszeile dem Makronamen folgen.
3.5.2
Tabelle 3.4: Die Makrodefinitionen von DOSKEY
Der Appletviewer
Kommen wir zum ersten JDK-Tool. Der Appletviewer ermöglicht es, ohne die Hinzuziehung eines Java-fähigen Browsers ein Applet während der Laufzeit zu betrachten. Er benötigt dazu nur eine rudimentäre HTMLDatei, die eigentlich ausschließlich ein Tag mit Angabe des anzuschauenden Applets sowie eventuell benötigten Parametern enthalten muss. Er lädt alle Applets innerhalb der Webseite und führt sie – sofern es mehrere korrespondierende Applets gibt – jeweils in einem separaten Fenster aus. Wenn eine HTML-Datei kein Tag mit Angabe eines Applets enthält, bewirkt der Aufruf des Appletviewers nichts. In den neuen Varianten versteht der Appletviewer sowohl das ursprünglich ausschließlich zu verwendende <APPLET>-Tag als auch die Tags und <EMBED> zum Einbinden. Der Appletviewer war ursprünglich das einzige Tool der JDK-Sammlung, das eine grafische Benutzerschnittstelle (GUI) besitzt. Im JDK 1.2 ist noch ein neues Sicherheitsprogramm mit GUI hinzugekommen, aber darüber hinaus finden Sie keine weiteren Programme mit GUI. Der Appletviewer hat nur wenige Aufrufoptionen. Es gibt nur die folgende einfache Syntax: Appletviewer [Optionen] [URL der HTML-Datei] [URL der HTML-Datei] wird in der Regel einfach die HTML-Datei ohne irgendwelche Pfadangaben (wenn man in dem entsprechenden Verzeichnis steht) oder mit einfachen Pfadangaben auf dem lokalen Rechner (absolut oder relativ) sein. Die allgemeine Formulierung soll jedoch darauf hinweisen, dass dies natürlich nicht zwingend ist und der Appletviewer ebenso in der Lage ist, bei einer Online-Verbindung eine HTML-Datei samt Applet
Java 2 Kompendium
121
Kapitel 3
Das JDK von einem beliebigen Server zu laden. Beachten Sie, dass die HTML-Datei mit der Endung einzugeben ist. Im Fehlerfall (auch bei falschen Optionen) erhalten Sie die Meldung »Make sure that [Name der HTML-Datei] is a file and is rendable« oder etwas Ähnliches. Ab der Version 1.2 des JDK gibt es eine Neuerung. Sie müssen beim ersten Start des Appletviewers die Lizensbedingungen von Sun akzeptieren. Folgende Optionen für den Appletviewer gibt es:
Tabelle 3.5: Die Optionen des Appletviewers
Optionen
Beschreibung
-debug
Diese Option gibt man an, wenn man den Appletviewer innerhalb des Java-Debuggers laufen lassen möchte. Als direkten Aufruf erzeugt der Appletviewer nur eine kurze Ausgabe auf Befehlszeilenebene.
-encoding [Verschlüssel_name]
Spezifiziert den Verschlüsselungsnamen der HTML-Datei, der bei der Konvertierung verwendet wird. Die Option ist erst seit der Version 1.1.x sinnvoll, denn erst dort wurde die Verschlüsselungsfähigkeit in verschiedene Zeichen in Java integriert. Dieser Verschlüssel_name-String muss als korrektes Argument der Klasse CharacterEncoding definiert sein.
-J Javaoption
Gibt den String Javaoption als ein einzelnes Argument an den Java-Interpreter weiter, der wiederum den Compiler startet. Das Argument darf keine Leerräume enthalten. Mehrere Argumente müssen jeweils mit dem Präfix -J beginnen. Dieses Präfix trennt die Argumente. Die Option ist erst seit der Version 1.1.x vorhanden.
Abbildung 3.17: Der Appletviewer
122
Java 2 Kompendium
Die wichtigsten JDK-Tools
Kapitel 3
Das Menü des Appletviewers Beim Menü des Appletviewers handelt es sich um ein einfaches GUI-Menü. Sämtliche Einträge verbergen sich hinter dem Hauptmenü APPLET. Von der ersten Version des Appletviewers bis zu den aktuellen Versionen sind einige Erweiterungen dazu gekommen. RESTART startet ein Applet mit den aktuellen Einstellungen neu. Eventuelle Veränderungen der zugehörigen Klassendatei (*.class) werden nicht berücksichtigt. RELOAD lädt das Applet erneut. Bei diesem Vorgang kommen im Gegensatz zum Restart die Veränderungen in der Klassendatei zum Tragen. CLONE dupliziert das aktuelle Applet und ein weiterer Appletviewer (also ein neues Fenster) mit den gleichen Einstellungen wird gestartet. PRINT... erzeugt einen Standarddialog zum Drucken des Applets. Der Eintrag ist nur in neueren Versionen des Appletviewers vorhanden und gibt Hintergrundinformationen des Applets aus. STOP ist nur in neueren Versionen des Appletviewers vorhanden und stoppt das Applet. Wichtig ist, dass dies nicht Beenden bedeutet, sondern das Applet nur angehalten wird. Mehr dazu im Abschnitt über Applets. Der Menüeintrag ist nur dann aktivierbar, wenn das Applet läuft. START ist nur in neueren Versionen des Appletviewers vorhanden und startet ein gestopptes Applet erneut. Wichtig ist, dass dies nur für per Stop angehaltene Applets funktioniert. Mehr dazu bei dem Abschnitt über Applets. Der Menüeintrag ist nur dann aktivierbar, wenn das Applet per STOP angehalten wurde. SAVE... erzeugt einen Standarddialog zum Speichern des Applets. Dabei wird eine Datei mit der Defaultendung .SER (das kommt von Serializible) erzeugt, die diverse Hintergrundinformationen (etwa Felder einer Komponente) des Applets speichert. Der Eintrag ist nur in neueren Versionen des Appletviewers vorhanden. Der Befehl TAG zeigt in einem eigenen Fenster sowohl das HTML-Tag zum Einbinden des Applets als auch die zugehörigen Parameter für das Applet an, also einen Ausschnitt aus der aufrufenden HTML-Datei. Innerhalb von Java kann man eine Standardmethode für Informationen über das Applet (public String getAppletInfo()) verwenden. Diese enthält beispielsweise Angaben über den Namen des Applets und den Autor. Der Menüeintrag INFO zeigt diese Angaben in einem eigenen Fenster an. Falls die Informationen in dem Applet-Programm gesetzt wurden, werden sie dort aufgelistet, ansonsten werden in dem InfoFenster nur »no applet info« und »no parameter info« angezeigt.
Java 2 Kompendium
123
Kapitel 3
Das JDK PROPERTIES zeigt in einem neuen Fenster die Sicherheitseigenschaften des Appletviewers an. Wie bereits erklärt, ist der Appletviewer in der Lage, HTML-Dateien und Applets aus einer Netzwerkumgebung zu laden. In diesem Menü ist er entsprechend zu konfigurieren. Die Angaben betreffen in der Version 1.1 des JDK sowie den meisten Betaversionen des JDK 1.2 einen Firewall-Proxy oder einen HTTP-Proxy. Ab der Finalversion des JDK 1.2 können Sie nur noch einen HTTP-Proxyserver konfigurieren. Sie können jeweils einen Proxyserver und den ProxyPort in den dafür vorgesehen Feldern eintragen. In dem Feld für den Netzwerkzugang können Sie den Netzwerktyp auswählen, auf den ein Applet im Appletviewer zugreifen darf. Sie können in den 1.1/1.2-Betaversionen wählen zwischen keinem Netzwerkzugang (None), Applet Host – also dem Host, von dem das Applet stammt – (Standardfall) und uneingeschränktem Netzwerkzugang (Unrestricted). Neuere JDK-Versionen lassen nur noch die Auswahl Restricted und Unrestricted zu. Anhand der Box für den Klassenzugang können Sie also für Ihren Appletviewer beschränkten oder uneingeschränkten Zugang zu anderen Klassen einstellen. Wenn Sie hier Eintragungen vornehmen, werden diese in einer Properties-Datei gespeichert, auf die gleich noch eingegangen wird. CHARACTER ENCODING gibt den Verschlüsselungscode für die Zeichen an, die in dem Applet verwendet werden. Wenn dieser Befehl aktiviert wird, sieht man am unteren Rand die verwendete ISO-Norm der Zeichen. 8859_1 ISO Latin-1 ist üblich im deutschsprachigen Raum. Dieser Befehl ist nur in neueren Versionen des Appletviewers vorhanden, denn in JDK-Version 1.0.x war diese unterschiedliche Verschlüsselung noch nicht vorhanden. CLOSE schließt ein Appletfenster. QUIT schließt wie üblich das Programm und beendet damit zugleich das Applet. Interne Arbeitsweise des Appletviewers Wenn der Appletviewer zum ersten Mal gestartet wird, generiert er – falls nicht vorhanden – unter Windows im JDK bis Version 1.2 die Datei \.hotjava\properties bzw. unter Unix die Datei ~/.hotjava/properties. Im JDK 1.3 wurde die Datei sowohl in .appletviewer umbenannt als auch auf das Unterverzeichnis .hotjava verzichtet. Sie befindet sich jetzt im Verzeichnis C:\Windows. Diese Datei liest das Programm bei jedem Start. Dort befinden sich die wichtigsten Sicherheitseinstellungen für das Laden fremder Applets. Dies betrifft unter anderem Schreib- und Leserechte, jedoch auch Einschränkungen der Verbindungsaufnahme von Applets. Da diese Sicherheitseinstellungen des Appletviewers gleichfalls für die Sicherheitseinstellungen eines
124
Java 2 Kompendium
Die wichtigsten JDK-Tools
Kapitel 3
Browsers von Bedeutung sind, ist eine etwas genauere Beschäftigung damit sicher ganz interessant. Zwar greifen wir damit dem Abschnitt über das Java-Sicherheitskonzept ein wenig vor, andererseits wird durch die praktische Anwendung im Appletviewer das Verständnis für diese Vorgänge leichter. Die Datei könnte etwa so aussehen: #AppletViewer #Sun Feb 28 19:09:42 GMT+01:00 2001 package.restrict.access.sun=false package.restrict.access.netscape=false appletviewer.version=1.2
Oder so: #User-specific properties for AppletViewer #Mon Jan 29 12:26:16 GMT+00:00 2001 http.proxyPort=80 package.restrict.access.sun=false http.proxyHost=
Bei der Generierung der Properties-Datei (erster Start des Appletviewers) werden Defaulteinstellungen für den Appletviewer in die Datei geschrieben, die so lange gelten, bis Sie diese verändern. Genaugenommen werden Sie innerhalb der Datei außer einer Datumsangabe und einer Versionsnummer des Appletviewers vorerst nicht viel finden. Sofern Sie jedoch individuelle Einstellungen für den Appletviewer vornehmen (Menüpunkt PROPERTIES), wird die Datei entsprechend gefüllt. Soweit zu den Sicherheitseinstellungen des Appletviewers. Die konkreten Einstellungen werden von einer abstrakten Java-Klasse, die als Hintergrundprozess immer mitläuft, wenn der Appletviewer gestartet ist, überwacht. Sie heißt SecurityManager und ist schon beim Start eines Browsers automatisch aktiv. Viele Browser haben ähnliche Sicherheitseinstellungsmöglichkeiten wie der Appletviewer. In manchen Bereichen sind Browser sogar noch restriktiver, in anderen dafür freier. Mehr zur Thematik Sicherheit folgt in einem eigenen Abschnitt.
3.5.3
Der Java-Compiler Javac
Der Java-Compiler kompiliert die als Parameter angegebene(n) Datei(en) mit der Dateierweiterung .java in den Java-Bytecode mit der Erweiterung .class. Der Namensstamm der Datei wird für die Klassendatei (nicht den Dateinamen) übernommen. Es können sowohl Applets als auch Anwendungen kompiliert werden.
Java 2 Kompendium
125
Kapitel 3
Das JDK Java ist keine kompilierte Programmiersprache wie C oder PASCAL. Der Quellcode wird also nicht in Maschinencode kompiliert, der spezielle Prozessorbefehle für die Zielplattform enthält. Java ist jedoch auch keine vollständig interpretierte Sprache wie Basic oder Lisp. Der Java-Quellcode wird also schon in gewisser Weise übersetzt, bevor er von einem Interpreter ausgeführt wird. Java ist statt dessen kompiliert und interpretiert und versucht damit, das Beste aus beiden Welten zu vereinen. Der Java-Compiler übersetzt den JavaQuellcode in einen Zwischencode namens Bytecode. Dieser Bytecode kann nicht direkt auf jeder Hardwareplattform ausgeführt werden. Der Code wird vielmehr vom Java-Interpreter interpretiert, der dabei eigenständig oder als Teil eines Browsers arbeiten kann. Java-Programme benötigen also immer einen Container. Es können im Prinzip beliebig viele Dateien als Parameter für die Übersetzung angegeben werden, die jedoch alle mit der Erweiterung .java enden müssen. Javac produziert für jede Datei eine Bytecode-Datei mit gleichem Namen wie die darin enthaltenen Klassen und der Erweiterung .class. Es allerdings ist zu beachten, dass Javac nur eine public-Klasse pro Quelldatei erlaubt. Ohne entsprechende Option erzeugt der Compiler die .class-Datei(en) in demselben Verzeichnis, in dem sich auch die Quelldateien befinden. Falls innerhalb einer Quelldatei auf eine Klasse verwiesen wird, die nicht in der Kommandozeile mit angegeben wurde, so durchsucht der Compiler alle Verzeichnisse oder die Systemklassen mithilfe seines Suchkonzeptes. Individuell können Sie den Parameter -classpath angegeben, um weitere Verzeichnisse zu spezifizieren. Javac-Syntax: javac [Optionen] [Dateiname] [@files]
Beachten Sie, dass der Dateiname zwingend mit der Erweiterung .java eingegeben werden muss. Bei neueren Versionen des Compilers kann über das Zeichen @ eine Datei angegeben werden, die selbst in jeder Zeile den Namen einer .java-Datei enthält. Damit kann verhindert werden, dass die maximale Länge der Kommandozeile überschritten wird. Im JDK 1.3 gibt es übrigens noch das Tool oldjavac, das für einige ältere Optionen eingesetzt werden kann.
126
Java 2 Kompendium
Die wichtigsten JDK-Tools
Kapitel 3
Die Optionen für den Compiler werden mit einem Minuszeichen eingeleitet und können beliebig kombiniert und angeordnet werden. Folgende Optionen gibt es: Optionen
Beschreibung
-nowrite
Eine Option, um die Syntax einer Java-Datei zu testen. Der Compiler wird bei gesetzter Option zwar den Code kompilieren, aber keine Datei mit der Erweiterung .class erstellen. Damit wird auch eine eventuell bereits bestehende Datei nicht überschrieben. Diese Option wird in den neuen Versionen des JDK nicht mehr unterstützt. Im JDK 1.2 wurde sie in offiziellen Quellen schon nicht dokumentiert, hat aber noch funktioniert.
-nowarn
Mit dieser Option können Sie Warnmeldungen des Compilers abstellen. Echte Fehlermeldungen werden jedoch weiter ausgegeben.
-verbose
Diese Option weist den Compiler und den Linker an, während des Kompilierungsvorgangs laufend Statusinformationen über die Quelldatei anzuzeigen. Diese Information umfasst alle geladenen Klassen, die nötige Zeit in Millisekunden, um die Klassen zu laden, und auch die Gesamtzeit, die für die Kompilierung des Quellcodes in Millisekunden wahrscheinlich benötigt wird.
-sourcepath sourcepath
Spezifiziert den Pfad zum Sourcecode.
-d [Verzeichnis]
Diese Option weist den Compiler an, das angegebene Verzeichnis als Zielverzeichnis für eine zu erstellende .class-Datei zu verwenden. Das Verzeichnis ist als Relativangabe zu verstehen. Ohne diese Option erzeugt der Compiler die classDatei(en) in demselben Verzeichnis, in dem sich auch die Quelldateien befinden. Alle class-Files werden in diesem Verzeichnis oder in einem darunter liegenden Verzeichnis abgelegt – in Abhängigkeit von dem Paketnamen der jeweiligen Klassen. Falls Verzeichnisstrukturen noch nicht vorhanden sind, legt der Compiler sie an.
Java 2 Kompendium
Tabelle 3.6: Die Optionen des Compilers
127
Kapitel 3 Tabelle 3.6: Die Optionen des Compilers (Forts.)
Das JDK
Optionen
Beschreibung
-classpath [Verzeichnisse]
Diese Angabe teilt dem Compiler mit, in welchen Verzeichnissen nach Klassen zu suchen ist. Die Verzeichnisse werden in der Unix-Syntax durch Doppelpunkte getrennt, Windows benutzt das Semikolon. Gleichfalls ist für Pfadangaben zu beachten, dass die Komponenten eines Pfades unter Unix mittels Slash und unter Windows mittels Backslash voneinander getrennt werden. Die angegebenen Pfade werden in der Reihenfolge ihres Auftretens durchsucht.
-debug
Diese Option lässt den Compiler im DebugModus laufen, geht den Quellcode durch und fügt Compiler-Kommentare ein. Wird ab dem JDK 1.3 nicht mehr vom Standardcompiler, sondern nur noch von oldjavac unterstützt.
-O
Durch diese Option wird die bestmögliche Optimierung der Klassendateien erreicht. Die statischen, finalen und privaten Methoden werden als Inlinemethoden behandelt und beschleunigen somit die Ausführung des generierten Codes, da der gesamte Methodenkörper eingefügt wird, wo auch immer er im Programm aufgerufen wird – anstatt mehrere Aufrufe zu haben, die sich auf eine Methode beziehen. Man sagt dazu ebenfalls, dass der Code zur Laufzeit ohne den zusätzlichen Overhead eines Funktionsaufrufs erzeugt wird. Ein kleiner Nachteil der Inlinekompilierung ist, dass der erzeugte Bytecode leicht grö ß er wird. Zusätzlich werden bei gesetztem Flag keinerlei Informationen für den Debugger generiert, was die Ausführung ebenfalls beschleunigt. Wichtiger Hinweis: Die Option -O hat seit dem JDK 1.2 eine andere Bedeutung als in den Vorgängerversionen und hat eine verschiedene Auswirkung auf den generierten Code. Außerdem schaltet die Option -O nun nicht mehr implizit die Option -depend an. Diese wird im JDK 1.3 nicht mehr unterstützt.
-deprecation
128
Zeigt eine Beschreibung von jedem verwendeten oder überschriebenen Klassenbestandteil oder einer Klasse an, die(r) als deprecated gekennzeichnet ist. Ohne die Option wird nur der Names der Quelldatei angezeigt, die ein solches Element verwendet.
Java 2 Kompendium
Die wichtigsten JDK-Tools
Kapitel 3
Optionen
Beschreibung
-g
Diese Option bereitet den Bytecode für das Debugging vor. Das bedeutet, dass der Compiler Zeilennummern und Informationen über lokale Variablen in dem Bytecode mit angibt. Es gibt die Option auch noch mit zusätzlichen Angaben none oder einer Liste von Schlüsselworten (per Doppelpunkt angefügt).
-encoding [encoding name]
Geben Sie hier den Source-Verschlüsselungsnamen (etwa EUCJIS \ SJIS) an. Wenn diese Option nicht angegeben wird, wird der defaultmä ß ige Converter der jeweiligen Plattform verwendet.
-depend
Bewirkt eine Rekompilierung von Klassendateien, auf die die Quelldateien einen rekursiven Aufruf implementiert haben. Ohne diese Option werden nur die fehlenden oder veralteten Dateien mit direktem Aufruf erneut kompiliert. Diese Option gab es in den 1.0.x-Versionen nicht, und das JDK 1.3 entfernt sie wieder.
-target version
Generiert .class-Dateien, die auf der spezifizierten JVM laufen. Defaulteinstellung ist, dass die .class-Dateien auf allen JVMs laufen sollen. Die Angabe einer niedrigen Version stellt sicher, dass die .class-Dateien sowohl auf JVMs der entsprechenden Version als auch der Folgeversionen laufen. Die Angabe einer höheren Version hingegen legt fest, dass die generierten .class-Dateien nur auf JVMs der höheren Versionen, nicht aber der Vorgängerversionen laufen.
-bootclasspath bootclasspath
Setzen des Suchpfades für die Systemklassen.
-extdirs directories
Setzen von Erweiterungsverzeichnissen. Jedes JAR-Archive in den angegebenen Verzeichnissen wird nach .class-Dateien durchsucht.
-JJavaoption
Die in der Hilfe des 1.3-Compilers undokumentierte Option gibt den String Javaoption als ein einzelnes Argument an den Java-Interpreter weiter, der von javac aufgerufen wird. Das Argument darf keine Leerräume enthalten. Mehrere Argumente müssen jeweils mit dem Präfix -J beginnen. Dieses Präfix trennt die Argumente. Die Option ist erst seit der Version 1.1.x vorhanden. Beispiel: -J-Xms48m setzt den Startupmemory auf 48 Megabytes. Obwohl diese Option nicht mit einem -X beginnt, ist sie keine Standardoption von Javac.
Java 2 Kompendium
Tabelle 3.6: Die Optionen des Compilers (Forts.)
129
Kapitel 3
Das JDK
3.5.4
Der Java-Interpreter
Java ist ein Kommandozeilen-Interpreter zur Ausführung von eigenständi-
gen Java-Anwendungen (keine Applets, dafür ist der Appletviewer zuständig). Jedes Java-Programm wird durch Klassennamen definiert. Dieser Klassenname muss dementsprechend vollständig mit dem Paketnamen der Klasse angegeben werden. Die Syntax für den Interpreter, den es in mehreren Versionen gibt: java [Optionen] [Klassenname] [Argumente] java [Optionen] -jar file.jar [ argument ... ] javaw [Optionen] [Klassenname] [Argumente] javaw [Optionen] -jar file.jar [ argument ... ] oldjava [Optionen] [Klassenname] [Argumente] oldjavaw [Optionen] [Klassenname] [Argumente]
Diese sechs Versionen vom Java-Interpreter müssen ein wenig erklärt werden. Erst einmal gibt es dabei vier verschiedene Versionen des Interpreters: java und javaw sind identisch bis auf eine Ausnahme: javaw erzeugt keine dem Befehlsaufruf folgende Ausgabe in einem Fenster (noconsole window). Die javaw-Variante des Interpreters gibt es nur unter Windows. oldjava und oldjavaw unterscheiden sich genauso wie der normale Interpreter (auch die oldjavaw-Variante des Interpreters gibt es nur unter Windows). Ihre Hauptfunktion ist die Ausführung von Applikationen, die über einen eigenen Security Manager verfügen, der auf dem originalen Sicherheitsmodell von Java 1.0/1.1 basiert. Dieser wird unter der Java-2-Plattform mit dem normalen Interpreter java eine Exception auswerfen und nicht starten. Auch sonstiger alter Java-Code kann unter Umständen damit ausgeführt werden, wenn der normale Interpreter damit nicht zurechtkommt.
Es gibt einige spezifische Unterschiede zwischen java und oldjava. oldjava unterstützt nicht den Extension-Mechanismus. Weiterhin nutzt oldjava den bootstrap-Classloader für alle Klassen, während java diesen nur für Systemklassen nutzt. Unter dem neuen java des JDK 1.2 spezifizieren die Optionen -classpath und -cp nur einen Suchpfad für anwenderdefinierte bzw. Nichtsystem-Klassen. Die Optionen können nicht dafür verwendet werden, solche bootstrap-Klassen zu lokalisieren. oldjava verwendet diese Optionen (wie auch früher java selbst), um alle Klassen zu lokalisieren. Dabei gilt es zu beachten, dass sich java und oldjava nicht in der Verwendung der Umgebungsvariablen CLASSPATH unterscheiden (ein entscheidender Unterschied zwischen dem alten java und oldjava). Bis die Variable durch -classpath oder -cp überschrieben wird, spezifiziert CLASSPATH immer den Ort, wo die anwenderdefinierten Klassen zu finden sind (aber nicht, wo die bootstrap 130
Java 2 Kompendium
Die wichtigsten JDK-Tools
Kapitel 3
Klassen sind). Obwohl oldjava im Wesentlichen zur Unterstützung von älterem Javasource gedacht ist, werden von ihm alle aktuellen Securityfeatures unterstützt. Ansonsten ist bei allen Syntaxangaben zu beachten, dass Optionen für den Interpreter diverse Übergabewerte sind und Klassenname der Name der auszuführenden Klasse. Defaulteinstellung ist, dass das erste Argument, das keine Option ist, von dem Interpreter als Name der Klasse interpretiert wird. Unbedingt zu beachten ist, dass beim Klassenname nicht nur die .classErweiterung nicht eingegeben werden muss, sondern sogar gar nicht darf. Dies ist deshalb eine häufige Fehlerquelle, weil es von der Syntaxlogik dem Compiler widerspricht, wo explizit die Erweiterung einzugeben ist. Wichtig ist, dass die Komponenten von Paket- und Klassennamen durch Punkt und nicht durch Slash oder Backslash getrennt werden. Die optionale Angabe file.jar ist der Name der .jar-Datei, die ausgeführt werden soll. Diese Anweisung kann nur in Verbindung mit der vorangestellten Angabe -jar verwendet werden. Argumente bezeichnen den oder die an die main()-Methode weitergegebene(n) Übergabewert(e). Jede Klasse, die in dem Aufruf durch den Klassenname spezifiziert wird, benötigt eine Methode namens public static void main(String argv[]). Sämtliche Argumente, die hinter dem Parameter Klassenname stehen, werden an die Methode main() in gleicher Reihenfolge übergeben, wenn java aufgerufen wird. Wenn mit main() Threads erzeugt werden, so läuft der Interpreter so lange, bis der letzte Thread beendet wird. Ansonsten endet java nach der vollständigen Abarbeitung von main(). Eventuell benötigte weitere Klassen werden automatisch geladen. Die nachzuladenden Klassen müssen sich allerdings im selben Verzeichnis oder im Zugriffspfad befinden. In ehemaligen Java-Versionen vorhandene Bugs konnten diese Restriktion zwar aushebeln, aber dies soll hier keine Rolle spielen.
Der Java-Interpreter hat bereits einige wichtige Sicherheitsvorkehrungen eingebaut. Diese gehören zum zentralen Sicherheitskonzept der Sprache. So überprüft der Java-Interpreter standardmäß ig jede Klasse während des Ladevorgangs. Die Klassen werden diversen Verifizierungen unterzogen. Darunter fällt auch ein Virentest. Durch einzelne folgende Parameter können die Verifizierungen ausgelöst, aber auch eingeschränkt werden, was die Programmausführung natürlich beschleunigt.
Java 2 Kompendium
131
Kapitel 3
Das JDK Bei den Optionen zeigen sich ganz massive Veränderungen zwischen den Interpreter-Version des JDK 1.1 und 1.2 (Beta) auf der einen Seite sowie Folgeversionen auf der anderen Seite. Die Standardoptionen des Interpreters wurden erheblich reduziert und viele Optionen, die früher dazu zählten, werden entweder nicht mehr unterstützt oder nur noch als Non-StandardOptionen geführt.
Tabelle 3.7: Die Standardoptionen des Interpreters
Optionen
Beschreibung
-classpath [Verzeichnis]
Diese Angabe korrespondiert wie bereits beim Compiler mit der gleichnamigen Umgebungsvariable und teilt dem den Interpreter mit, in welchen Verzeichnissen, JAR-Archiven und ZIP-Archiven nach .class-Dateien zu suchen ist. In Verbindung mit java oder javaw werden nur anwenderdefinierte Klassen spezifiziert, in Verbindung mit oldjava oder oldjavaw alle Klassen. Die Verzeichnisse werden in der Unix-Syntax durch Doppelpunkte getrennt, Windows benutzt das Semikolon. Auch ist für Pfadangaben zu beachten, dass die Komponenten eines Pfades unter Unix mittels Slash und unter Windows mittels Backslash voneinander getrennt werden. Die angegebenen Pfade werden in der Reihenfolge ihres Auftretens durchsucht.
-cp
-help -? -version -showversion
-verbose -v
Wie sehr oft üblich, kann mit diesen gleichwertigen Optionen eine Ausgabe sämtlicher Optionen erzwungen werden. Diese Optionen veranlasen den Interpreter, die Version des JDK anzuzeigen. Sie können diese Option auch ohne .class-Datei verwenden. Unterschied zwischen beiden Varianten ist, dass -version den Programmablauf abbricht, während -showversion das Programm startet. Diese Option weist den Interpreter an, während des Ladevorgangs laufend Statusinformationen über die Klassen anzuzeigen (jedes Mal, wenn eine neue Klasse geladen wird). Es gibt die folgenden Spezialfälle: -verbose:class – Anzeige jeder geladenen Klasse. -verbose:gc – Anzeige jedes Garbage collection-
Events. Diese Option weist den Interpreter an, jedes Mal eine Meldung auf dem Bildschirm auszugeben, wenn es zu einer Speicherbereinigung durch die Garbage Collection kommt. -verbose:jni – Anzeige über die Verwendung von
nativen Methoden und andere JNI-Aktivitäten. 132
Java 2 Kompendium
Die wichtigsten JDK-Tools
Kapitel 3
Optionen
Beschreibung
-[DpropertyName] = [NeuerWert]
Es gibt im Prinzip die Möglichkeit, selbst definierte Systemeigenschaften an ein interpretierendes Java-Programm zu übergeben, indem beliebige Eigenschaften und Werte als Optionen mit einem Minuszeichen und D abgetrennt an den Aufruf angehängt werden. Das Java-Programm muss natürlich diese Parameter verwenden können. propertyName ist der Name der Eigenschaft, deren Wert Sie verändern wollen und NeuerWert ist der geänderte Wert der Eigenschaft.
Tabelle 3.7: Die Standardoptionen des Interpreters (Forts.)
Beispiel: % java -Dawt.button.color=red ...
setzt den Wert der Eigenschaft awt.button.color auf red (rot) – das Prozentzeichen ist UNIX-Syntax. java akzeptiert im Prinzip eine beliebige Anzahl von -D-Optionen in der Befehlszeile. -jar
Führt ein Programm aus, das in ein JAR-Archiv verpackt ist. Das erste nachfolgende Argument ist der Name von einen JAR-Archiv (anstelle eines Klassennames). oldjava und oldjavaw verstehen diese Option nicht.
-X
Anzeige der nicht-standard-Optionen.
Neben den Standardoptionen des Interpreters gibt es zahlreiche Non-Standard-Optionen: Optionen
Beschreibung
-Xbootclasspath:bootclasspath
Spezifiziert einen durch Trennzeichen getrennte Suchpfad von Verzeichnissen, JAR-Archiven und ZIP-Archiven.
-Xnoclassgc
Diese Option (entspricht der in früheren Versionen verwendeten Option -noasyncgc, die nicht mehr unterstützt wird) unterbindet das asynchrone Ausführen der Garbage Collection. Die Folge ist, dass dieser nur dann ausgeführt wird, wenn er explizit im Programm aufgerufen wird (System.gc()) oder wenn der zu Verfügung stehende Speicherplatz zu gering wird. Wenn diese Option nicht gesetzt ist, verfolgt die Laufzeitumgebung alle Referenzen auf ein Objekt in dem Heap nach und gibt automatisch den Speicher frei, der von Objekten belegt wird, die nicht mehr länger referenziert werden. Dies geschieht, sobald die CPU dafür Kapazität frei hat und läuft als permanenter, paralleler Hintergrundthread mit geringer Priorität ab.
Java 2 Kompendium
Tabelle 3.8: Die Nicht-Standardoptionen des Interpreters
133
Kapitel 3 Tabelle 3.8: Die Nicht-Standardoptionen des Interpreters (Forts.)
Das JDK
Optionen
Beschreibung
-Xmx [MaximalMemory]
Mit dieser Option (früher nur -mx) stellt man die maximale Grö ß e des Heap-Speichers ein. MaximalMemory ist der maximale Wert in Bytes, den der Interpreter für dynamisch allokierte Objekte und Arrays nutzen kann. Die Grö ß enangabe kann auch in Kilobyte (k an die Zahl angehängt) oder in Megabyte (m an die Zahl angehängt) erfolgen. Voreinstellung ist jedoch die Einheit Byte. Die minimale Heap-Grö ß e ist ein Kilobyte (-Xmx 1k), die Standardgrö ß e des maximalen Heaps liegt bei 16 Megabyte (-Xmx 16m).
-Xms [Memory]
Diese Option (früher nur -ms) stellt die anfängliche Heap-Grö ß e ein, wenn der Interpreter startet. Memory ist die Angabe in Bytes, die bestimmt, wie viel Speicher der Interpreter allokieren soll. Die Grö ß enangabe kann auch in Kilobyte (k an die Zahl angehängt) oder in Megabyte (m an die Zahl angehängt) erfolgen. Voreinstellung ist jedoch die Einheit Byte. Die minimale Heap-Grö ß e ist ein Kilobyte (-ms 1k), die Standardgrö ß e des anfänglichen Heaps ist 1 Megabyte (-Xms 1m) und wird immer dann genommen, wenn der Parameter nicht angegeben wird. Bei großen Java-Programmen sollte die anfängliche Heap-Grö ß e erweitert werden, um die Performance zu verbessern.
-Xdebug
Diese Option (früher nur -debug) veranlasst den Interpreter ein Passwort zu erzeugen, nachdem er gestartet wurde. Es wird auf dem Bildschirm angezeigt und kann in der Passwortoption (-password) des Debuggers jdb verwendet werden, um sich nachträglich in die Sitzung einzuklinken (Remote-Zugriff).
-Xrunhprof[:help][:= ,...]
Erlaubt die Beobachtung von dem Prozessor, dem Heap oder Monitor.
-Xmixed
Mixedmode-Ausführung.
-Xint
Nur interpretierte Ausführung.
Es gab über die zahlreichen Varianten des JDK diverse Interpreter-Optionen, die mittlerweile nicht mehr dokumentiert werden. Diese werden aber teilweise immer noch unterstützt und sind natürlich dann interessant, wenn Sie mit einem älteren Interpreter arbeiten.
134
Java 2 Kompendium
Die wichtigsten JDK-Tools
Kapitel 3
Die nicht mehr unter dem JDK 1.3 dokumentierten Optionen: Optionen
Beschreibung
-Xrs
Reduziert Systemsignale.
-Xcheck:jni
Zusätzliche Checks für Java Native InterfaceFunktionen.
Optionen
Beschreibung
-verify
Wenn diese Option gesetzt ist, wird der Bytecode-Verifizierer des Java-Interpreters jedes Mal ausgeführt, sobald eine neue Klassen geladen wird. Diese Klasse wird damit explizit überprüft (Defaulteinstellung). Die Option wird an verschiedenen Stellen der JDK-Dokumentation auch als Nicht-Standardoption -Xverify geführt. Da ist die Dokumentation leider nicht einheitlich. Manche Interpreter-Versionen führen beide Varianten ohne Fehlermeldung aus.
-noverify
Diese Option stellt die Verifizierung der Klassen durch den Bytecode-Verifizierer ab.
-verifyremote
Diese Option weist den Interpreter an, Klassen, die über den Classloader geladen werden (und nur diese), mit dem Bytecode-Verifizierer zu überprüfen. Dies sind im Allgemeinen Klassen, die von einem fremden Host über das Netz importiert oder vererbt worden sind.
-ss [StackGröß e]
Dieser Parameter legt den Wert für die StackGrö ß e eines Threads für einen C-Prozess (Native Code) fest. Die Grö ß enangabe kann auch in Kilobyte (k an die Zahl angehängt) oder in Megabyte (m an die Zahl angehängt) erfolgen. Voreinstellung ist jedoch wie bei den anderen Optionen die Einheit Byte. Die Stack-Grö ß e muss mehr als 1 Kilobyte (-ss 1k) betragen. Standardwert ist 128 Kilobyte.
-oss [StackGröß e]
Dieser Parameter legt den Wert für die StackGrö ß e eines Java-Threads fest. Die Grö ß enangabe kann auch in Kilobyte (k an die Zahl angehängt) oder in Megabyte (m an die Zahl angehängt) erfolgen. Voreinstellung ist wie bei den anderen Optionen die Einheit Byte. Die StackGrö ß e muss mehr als 1 Kilobyte (-oss 1k) betragen. Standardwert ist 400 Kilobyte.
Java 2 Kompendium
Tabelle 3.9: Die zusätzlichen Nicht-Standardoptionen des Interpreters 1.2
Tabelle 3.10: Die nicht mehr unter dem JDK 1.2 dokumentierten Optionen
135
Kapitel 3
Das JDK Es gibt auch einige Optionen, die in neuen Versionen des JDK effektiv keinen Sinn haben, da dort die entsprechenden Tools nicht mehr vorhanden sind. In den früheren JDKs sind sie aber von Interesse.
Tabelle 3.11: Optionen des Interpreters vor 1.2
Optionen
Beschreibung
-prof
Anhand dieser Option wird der Interpreter angewiesen, so genannte Profilinformationen in die Datei \java.prof (Defaulteinstellung) auszugeben. Die Option funktioniert nur mit java_g und javaw_g.
-prof:[Datei]
Anhand dieser Option wird der Interpreter angewiesen, so genannte Profilinformation alternativ in eine sellbst definierte Datei auszugeben. Die Option funktioniert nur mit java_g und javaw_g.
-t
Gibt eine Spur der abgearbeiteten Anweisungen aus. Die Option funktioniert nur mit java_g und javaw_g und ist erst in der Version 1.1.x eingeführt worden.
3.6
Zusammenfassung
Wenn Sie in Java programmieren wollen, wird das JDK die Basis sein, auch wenn Sie auf ergänzende Tools umsteigen. Diese nutzen das JDK meist direkt. Das kostenlos von Sun bereitgestellte JDK beinhaltet eine Vielzahl von Tools, von denen der Compiler, der Interpreter und der Appeltviewer die wichtigsten sind. Die zusätzlich von Sun bereitgestellte Online-Dokumentation ist eine nahezu unumgängliche Basis zum Nachschlagen von Details zu Java oder den JDK-Tools.
136
Java 2 Kompendium
4
Unterschiede zwischen JavaApplikationen und -Applets
Nachdem nun die wichtigsten Voraussetzungen (Einiges an Grundlagentheorie und diverse Werkzeuge) besprochen wurden, wollen wir konkret zu einem ersten Einstieg in die Java-Programmierung kommen. Sie sollen ja nicht die Lust verlieren, weil wir einfach nicht zu Java kommen. Ein oft gemachter Fehler bei Java-Laien besteht darin, Java-Applikationen und Java-Applets gleichzusetzen. Um diesem Missverständnis vorzubeugen, soll an dieser Stelle der Unterschied zwischen Java-Applikationen und -Applets besprochen werden. Auf viele Details in der Programmierung wird zwar hier nicht eingegangen, jedoch einige Grundlagen sollen doch diskutiert werden. Ein Hauptgrund für die heutige Beliebtheit von Java beruht sicher auf den Java-Applets, die erst richtig Leben in das WWW gebracht haben. Solche Java-Applets können nur innerhalb eines Webbrowsers oder des Appletviewers ausgeführt werden. Ein solcher Container bildet quasi ein zweites Betriebssystem für Applets. Da beide Programme aber HTML-Seiten anzeigen, muss ein Applet über eine solche HTML-Seite referenziert werden. Eine entsprechende Referenz innerhalb einer HTML-Seite mit einem speziellen Tag – dem <APPLET>-Tag – erledigt die Einbettung in den Browser. Seit der HTML-Version 4.0 kann die Einbettung von Java-Applets in Webseiten ebenso mittels des allgemeineren -Tags erfolgen. Das ursprünglich nur für ActiveX-Controls verwendete -Tag dient seit HTML 4 zum Einbinden von allgemeinen Objekten. Darunter fallen u.a. Bilder, Video- und Tondateien und eben auch spezialisierte Applikationen wie Java-Applets. Damit werden im Prinzip einige bisherige Tags – so auch das <APPLET>-Tag – überflüssig. Statt vielen verschiedenen Tags ist nur noch das -Tag notwendig. Die Browser unterstützen die bisherigen Tags jedoch nach wie vor. Außerdem gibt es nachfolgend noch besprochene Gründe, warum die Verwendung von <APPLET> sinnvoller sein kann. Da Java-Applets innerhalb eines Browsers laufen, haben sie direkten Zugang zu den meisten Fähigkeiten desBrowsers. Dazu zählen u.a. Zugriffe auf Grafiken und Elemente der Benutzeroberfläche, aber auch Netzwerkfähigkeiten.
Java 2 Kompendium
137
Kapitel 4
Unterschiede zwischen Java-Applikationen und -Applets
Abbildung 4.1: Ein Applet innerhalb eines Browsers
Allerdings teilen sie auch die Einschränkungen des Browsers – oder genauer: Sie werden durch den Browser beschränkt. Das beinhaltet beispielsweise die fehlende Möglichkeit, das Dateisystem des Rechners zu lesen und dort Schreibvorgänge durchzuführen. Es sei denn, der Anwender erlaubt es, indem die Sicherheitseinstellungen des Containers entsprechend gelockert werden. Weiter können Applets normalerweise nur zu dem Host Verbindung aufnehmen, von dem sie geladen wurden. Auch hierzu sind in den Einstellungen des Appletviewers bzw. des Browsers entsprechende Veränderungen möglich, die dann eine noch geringere, aber auch weitergehende Kommunikation erlauben. Zwei weitere Einschränkungen von Applets gehen über diese Eigenschaften hinaus. Zum einen können Applets keine auf dem Client-System vorhandenen Programme starten, zum anderen können Sie keine nativen Programme und Bibliotheken der lokalen Plattform laden. Bugs in älteren Java-Versionen, die in diese Richtung Manipulationsmöglichkeiten eröffneten, werden beim Java-Sicherheitskonzept angesprochen. Darüber hinaus müssen JavaApplets noch diverse weitere Sicherheitsregularien einhalten, auf die ebenfalls noch näher eingegangen wird. Applets ist insbesondere die Unterbrechung der Ausführung des Java-Interpreters nicht erlaubt. Sie können also die Methode System.exit() nicht aufrufen.
138
Java 2 Kompendium
Die technischen Unterschiede
Kapitel 4
Dies alles mag erst einmal als Einschränkung und Argumente gegen Applets empfunden werden, aber genau das Gegenteil ist der Fall. Immerhin soll ein Anwender ja via Browser ein Programm auf seinen Rechner lassen, dessen Funktionalität er nicht kennen kann. Wenn man als Anwender sicher sein kann, dass zumindest die verwendete Technologie keinen Schaden anrichten kann, wird das die Akzeptanz erheblich erhöhen. Applets erlauben nur solche Aktionen, die einen Anwender so gut wie nicht schädigen können. Zumindest muss man sich mit Java-Applets erheblich anstrengen, wenn man einen Anwender ärgern will. Mit anderen Technologien geht das viel leichter. Java-Applikationen sind im Gegensatz zu Applets vollständig in Java programmierte und eigenständig lauffähige Programme. Sie benötigen im Kontrast zu den Applets keine Container mehr (beispielsweise einen Browser oder den Appletviewer). Sie laufen direkt in der virtuellen Maschine und haben dementsprechend weitergehende Möglichkeiten auf dem lokalen System. Das ist auch sinnvoll, denn sonst könnten keine konkurrenzfähigen Applikationen in Java entwickelt werden. Anders gesagt – eine Java-Applikation kann all das leisten, was sie auch können würde, wenn sie in einer anderen Programmiersprache erstellt worden wäre. Abbildung 4.2: Das Applet als eigenständige Applikation
4.1
Die technischen Unterschiede
Eine eigenständig lauffähige Java-Applikation benötigt immer eine zentrale Methode, deren Abarbeitung dem gesamten Programmlauf entspricht: die main()-Methode. Genau betrachtet besteht die Ausführung einer Java-Applikation nur aus dem Start, der Durchführung und dem Ende der main()Methode. Sämtliche Aktionen der Java-Applikation werden von da aus gesteuert und aufgerufen. Die genaue Syntax sieht so aus: public static void main (String args[])
Beim Java-Applet ist es etwas anders. Es gibt als wesentlichen Unterschied keine main()-Methode.
Java 2 Kompendium
139
Kapitel 4
Unterschiede zwischen Java-Applikationen und -Applets Ein Applet benötigt zwar keine Methode namens main(), das bedeutet aber nicht, dass dort eine solche Methode verboten ist. Insbesondere solche Applets, die alternativ als eigenständige Applikation lauffähig sein sollen, besitzen eine solche main()-Methode. Beim Start als Applet wird sie nur nicht verwendet. Das Leben eines Applets vollzieht sich in vier Schritten: Das Initialisieren des Applets. Dies erledigt die init()-Methode, deren vollständige Deklaration so aussieht: public void init(). Innerhalb dieser Methode können alle Schritte durchgeführt werden, die vor dem Anzeigen des Applets abgeschlossen sein sollen. Die Methode wird beim Laden des Applets automatisch aufgerufen. Das Starten des Applets. Nach dem Initialisieren wird das Applet gestartet. Um bestimmte Schritte während dieser Phase durchzuführen, kann die start()-Methode verwendet werden (public void start()). Diese Methode wird nach der Initialisierung automatisch aufgerufen, kann aber auch während des Lebens eines Applets mehrfach manuell oder automatisch erneut aufgerufen werden. Das Stoppen des Applets. Stoppen ist das Gegenstück zum Starten des Applets und geht mit diesem Hand in Hand. Nur ein gestartetes Applet kann wieder gestoppt werden. Jedes gestoppte Applet kann wieder ohne Neuinitialisierung gestartet werden (es sei denn, es ist zerstört). Diese Methode wird beispielsweise automatisch aufgerufen, wenn ein Anwender den Anzeigebereich eines Applets in einer Webseite verlässt. Es läuft jedoch – quasi im Leerlauf – weiter und kann reaktiviert werden (etwa automatisch in dem Moment, wo das Applet wieder im Browser angezeigt wird). Um ein Applet zu stoppen oder bestimmte Aktivitäten mit dem Stoppen eines Applets zu koppeln, kann die stop()-Methode verwendet werden (public void stop()). Das Zerstören des Applets. Zerstören bedeutet, dass das Applet eventuell laufende Threads beendet, Objekte freigibt und sonstige Aufräumarbeiten ausführt. Anschließend kann das Applet sein Leben aushauchen, d.h., es wird aus dem Speicher des Rechners entfernt. Wenn der Appletviewer oder der Browser geschlossen wird, wird die Methode automatisch aufgerufen. Das Gleiche passiert, wenn eine neue Webseite geladen wird. Aber auch manuell kann ein Applet zerstört werden. Um bestimmte Aktionen mit der Zerstörung eines Applets zu koppeln, sollten Sie die destroy()-Methode verwenden (public void destroy()). Die allgemeinere finalize()-Methode ist hier nicht anwendbar, da diese eher für ein einzelnes Objekt irgendeines Typs zum Aufräumen verwendet wird und vor allem, weil diese im Gegensatz zu der destroy()Methode nicht immer beim Beenden von einem Browser oder dem Neuladen eines Applets automatisch ausgeführt wird.
140
Java 2 Kompendium
Eine erste Beispielanwendung und ein erstes Applet
Kapitel 4
Wenn Sie sich das Menü des Appletviewers ansehen, werden Sie dort die Befehle Stop und Start wiederfinden.
Abbildung 4.3: Im Menü des Appeltviewers sind die Start- und StopAnweisungen wiederzufinden.
Es ist an der Zeit für Java-Praxis.
4.2
Eine erste Beispielanwendung und ein erstes Applet
Wir wollen an dieser Stelle eine erste Beispielanwendung und ein erstes Applet erstellen. Dabei ignorieren wir bewusst die Situation, dass bisher noch überhaupt kein systematischer Aufbau der Syntax, der Java-Konzepte oder der Befehle erfolgt ist. Auch von grundlegenden Vorgehensweisen bei der Programmierung in Java war noch keine Rede. Um jedoch nach so viel Theorie ein Erfolgserlebnis in der Praxis zu haben, ist es an dieser Stelle sicher sinnvoll, konkrete Beispiele dazwischenzufügen. Natürlich werden die beiden jetzt folgenden Beispiele sehr einfach sein.
4.2.1
Eine erste Applikation
Das klassische »Hello world«-Programm wollen wir als Basis für unser erstes Programm nutzen. Damit bewegen wir uns in bester Tradition fast aller Bücher über Programmiersprachen seit mystischer Basic-Zeit, die dieses Beispiel als Einstieg verwendet haben.
Java 2 Kompendium
141
Kapitel 4
Unterschiede zwischen Java-Applikationen und -Applets Die einzelnen Schritte sollten bei einem ersten Kontakt am besten sklavisch nachvollzogen werden (d.h. mit strenger Beachtung von Namen und Großund Kleinschreibung). Erklärungen der dahinter liegenden Prozesse folgen an späterer Stelle. Hier gilt nur: Hauptsache, »it works«. Da Java innovativ ist, wollen wir in demselben Maße eine innovative Änderung gegenüber dem klassischen »Hello world«-Programm vornehmen. Starten Sie bitte einen beliebigen ASCII-Editor und geben nach folgenden Quelltext ein.
Listing 4.1: Das erste Beispielprogramm
class HelloJava { public static void main (String args[]) { System.out.println("Hello Java!"); }}
Haben Sie die extrem innovative Änderung bemerkt ;-)? So, jetzt haben Sie Ihr erstes Java-Programm geschrieben, aber wie geht es weiter? Speichern ist der nächste notwendige Schritt. Es mag trivial erscheinen, aber die Betonung liegt wirklich auf »notwendig«, was bei integrierten Entwicklungsumgebungen nicht der Fall ist. Dort würde es heißen: »Speichern ist der nächste sinnvolle Schritt«. Da wir unter dem JDK zur Übersetzung des ASCII-Textes jedoch im Folgeschritt ein Programm aufrufen müssen, das eine Datei auf einem (beschreibbaren) Datenträger erwartet, muss unbedingt gespeichert werden, bevor wir mit dem Java-Source etwas anfangen können. Wenn wir davon ausgehen, dass Sie Ihre Pfadangaben entsprechend den Empfehlungen bei der Besprechung des JDK gesetzt haben, können Sie Ihr erstes Programm an einem beliebigen Platz (z.B. im extra erstellten Verzeichnis ErstesProgramm) speichern. Speichern Sie jetzt Ihr Programm in einem Verzeichnis Ihrer Wahl. Speichern Sie den Quelltext unter dem Namen ErstesJavaProgramm ab. War das korrekt so? Eigentlich sollten Sie einen Fehler bereits mit Ihren jetzigen Kenntnissen entdecken. Welche Erweiterung hat jeder Java-Source (nicht der Bytecode)? Wenn Sie sich eine Fehlermeldung bei dem nachfolgenden Aufruf des Compilers sparen wollen, dann speichern Sie das Programm besser mit der Erweiterung .java (Groß- und Kleinschreibung bitte unbedingt beachten), also unter dem Namen ErstesJavaProgramm.java ab.
142
Java 2 Kompendium
Eine erste Beispielanwendung und ein erstes Applet
Kapitel 4
Im nächsten Schritt versuchen wir, den gespeicherten Java-Source zu kompilieren. Öffnen Sie ein Befehlzeilenfenster und wechseln Sie dort in das Verzeichnis, wo der Java-Source abgelegt ist. Dort rufen wir den Java-Compiler auf. Geben Sie Folgendes ein: javac ErstesJavaProgramm
Was passiert? Etwa eine Fehlermeldung? Trotz richtig eingestellter JavaUmgebung? Natürlich, wir haben die Erweiterung vergessen. Also zweiter Versuch mit Erweiterung: javac ErstesJavaProgramm.java
Nun müsste alles glatt gegangen sein. Sollten Sie immer noch eine Fehlermeldung erhalten haben, so warten Sie noch einmal kurz ab. Ansonsten ist es Zeit, das in Bytecode übersetzte Programm auszuführen. Wie geht das? Sie müssen den Java-Interpreter aufrufen und die auszuführende Klasse als Parameter übergeben. Die Klasse bekommt nach der Definition von Java den gleichen Namen wie der Source und die Erweiterung .class. Geben Sie auf Befehlszeilenebene den nachfolgenden Aufruf ein. java ErstesJavaProgramm.class
Was passiert? Etwa schon wieder eine Fehlermeldung? Natürlich, wir haben diesmal die Erweiterung eingegeben und beim Interpreter darf sie nicht eingegeben werden. Also: Zweiter Versuch ohne Erweiterung. Geben Sie auf Befehlszeilenebene den nachfolgenden Aufruf ein. java ErstesJavaProgramm
So, jetzt müsste alles geklappt haben. Oder etwa nicht? Noch eine Fehlermeldung, dass die Klasse nicht gefunden wurde? Geben Sie auf Befehlszeilenebene den nachfolgenden Aufruf ein. java HelloJava
Nanu, jetzt steht Hello Java in der untersten Ausgabezeile oder nicht?! Herzlichen Glückwunsch, Sie haben Ihr erstes Java-Programm erstellt und
Java 2 Kompendium
143
Kapitel 4
Unterschiede zwischen Java-Applikationen und -Applets zum Laufen gebracht. Lassen Sie sich mal den Inhalt Ihres Testverzeichnisses anzeigen (etwa mit dem Befehl DIR direkt in der DOS-Box, dem Arbeitsplatz oder einem anderen Dateimanagement-Programm), dann erkennen Sie wahrscheinlich den Grund, warum es jetzt funktioniert und nicht bei java ErstesJavaProgramm. Es gibt keine Klasse mit dem Namen ErstesJavaProgramm.class, dafür aber eine Klasse mit dem Namen HelloJava.class. Der Compiler verwendet für die Generierung des Klassennamens nicht den Namen der Java-Sourcedatei, sondern den Namen, den Sie als Bezeichner Ihrer Klasse verwendet haben. Wie schon an verschiedenen Stelle erwähnt: Unter Java werden lange Dateinamen verwendet. In einer DOS-Box werden bei Verwendung des DIRBefehls unter Umständen nur die verkürzten Namen dargestellt, zumindest in der ersten Ausgabespalte. Dies ist eine gefährliche Fehlerquelle, wenn Sie da nicht aufpassen. Ich habe Sie mit der Wahl eines abweichenden Namens bewusst aufs Glatteis geführt, damit Ihnen dieser Zusammenhang klar wird. Wenn Sie wollen, können Sie Ihren Java-Source noch mal unter dem Namen Ihrer Klasse (also HelloJava.java) abspeichern und die Schritte noch einmal nachvollziehen. Prinzipiell sollten Java-Source-Dateien unter dem Namen Ihrer publicKlasse (einer gesondert markierten Klasse – Genaueres folgt später) gespeichert werden, um überhaupt keine Missverständnisse aufkommen zu lassen. Zu einer kurzen Erläuterung des Sources soll an dieser Stelle nur Folgendes gesagt werden: 1.
Das gesamte Programm befindet sich innerhalb der Klassendefinition.
2.
Die bei einer eigenständigen Applikation unbedingt notwendige main()Methode enthält nur einen Ausgabenaufruf auf das Standardausgabegerät.
4.2.2
Übergabe von Aufrufargumenten an ein Java-Programm
Natürlich kann man an ein Programm beim Aufruf Parameter übergeben, die das Programm dann verwenden kann. Damit kann man das im Prinzip abgeschlossene Programm beeinflussen. Dies dient etwa zur Ablaufsteuerung, zur Einstellung von Optionen, zur Weitergabe von zu verarbeitenden Werten usw. Argumente an ein Programm werden auf Befehlszeilenebene einfach an den Programmaufruf – mit Leerzeichen abgetrennt – angehängt. Verändern wir doch unser erstes Programm ein wenig, sodass es Argumente auswerten kann.
144
Java 2 Kompendium
Eine erste Beispielanwendung und ein erstes Applet
Kapitel 4
Geben Sie den nachfolgenden Quelltext ein.
class HelloJava2 { public static void main (String args[]) { System.out.println(args[0]); } }
Listing 4.2: Ein Programm, das Übergabeparameter verwendet
Speichern Sie das Programm unter HelloJava2.java. Kompilieren Sie es neu. Geben Sie auf Befehlszeilenebene den nachfolgenden Aufruf ein. javac HelloJava2.java
Geben Sie auf Befehlszeilenebene den nachfolgenden Aufruf zum Start des Programms ein. java HelloJava2 "HelloJava"
Die Ausgabe müsste identisch sein. Beachten Sie, dass das Programm nicht gegen das Fehlen von Übergabewerten geschützt ist. Der in Hochkommata eingeschlossene Übergabewert1 steht über das Zeichenketten-Array args (oder den sonst in der main()-Methode angegebenen Namen) zur Verfügung. Innerhalb der main()-Methode können Übergabeparameter beliebig behandelt werden. Der Index eines Arrays in Java beginnt mit 0. Wir werden später noch einmal auf die Übergabe von Werten an ein JavaProgramm zurückkommen. Insbesondere werden wir darauf eingehen, was man mit den übergebenen Werten alles machen kann und worauf Sie zu achten haben, wenn Sie nicht nur übergebene Zeichen als Text weiter verwenden wollen. Da die Argumentenliste eines Programms immer ein Zeichenketten-Array ist, werden alle Übergabeparameter an ein Java-Programms als Zeichenketten übergeben. Kommen wir nun zur Erstellung eines ersten Applets. 1
Das ist notwendig, da sonst das Leerzeichen vom Interpreter als Trennung von zwei Übergabewerten verstanden würde.
Java 2 Kompendium
145
Kapitel 4
Unterschiede zwischen Java-Applikationen und -Applets
4.2.3
Ein erstes Applet
Möglicherweise wider Ihre Erwartung ist die Erstellung eines einfachen Applets – mit ähnlicher Funktionalität wie das gerade behandelte Programm – komplizierter als die Erstellung der eigenständigen Applikation. Allerdings könnte auf Grund der technischen Unterschiede zwischen Applets und Applikationen schon eine Ahnung aufgekommen sein, dass Applet-Code nicht ganz so einfach sein kann. Eine eigenständig lauffähige Java-Applikation benötigt immer nur eine main()-Methode (siehe Beispiel), während das Java-Applet zwar keine main()-Methode, dafür gleichwohl beivoller Funktionsfähigkeit mindestens vier andere Methoden benötigt – die init()-Methode, die start()-Methode, die stop()-Methode und die destroy()-Methode. Allein daraus dürfte klar werden, dass da eine Menge mehr Source notwendig ist, wenn Sie ein vollständig funktionsfähiges Applet bauen wollen. Lassen Sie uns dennoch ein einfaches Applet erstellen, das genauso wie die eigenständige Applikation aus dem vorigen Beispiel Hello Java ausgibt. Der Quelltext wird trotz der einleitenden Warnung kaum grö ß er als bei der eigenständigen Applikation sein. Dies liegt daran, dass wir diese Methoden nicht direkt notieren müssen. Implizit stecken diese Methoden allerdings dennoch in dem nachfolgenden Beispiel. Es erbt sie über die Syntax extends java.applet.Applet von der Mutter aller Applets (was auch immer das jetzt bedeutet). Dafür müssen wir aber etwas anderes tun. Wir können nicht einfach wie bei einer eigenständigen Applikation einen Ausgabenaufruf auf das Standardausgabegerät nutzen. Ein Applet befindet sich innerhalb eines Containers und muss sich kooperativ zu ihm verhalten. Es muss die Ausgabemöglichkeiten nutzen, die mit dem Container kooperieren, also meistens Fenster im Rahmen einer grafischen Oberfläche. Wir werden das Applet als eine Instanz einer Klasse erstellen, die uns diese Funktionen zur Verfügung stellt. Innerhalb des Fensters wird dann auch nicht mehr geschrieben, sondern eher gemalt, was eine andere Methode zur Ausgabe erzwingt. Außerdem benötigen wir eine HTML-Datei, mit der wir das Applet dann in unseren Container implementieren. Gehen wir das erste Applet-Projekt systematisch an. Als Erstes erstellen wir ein eigenes Verzeichnis für das Projekt (Vorschlag ErstesApplet), in dem sich anschließend die HTML-, die Java- und die Klassendatei befinden sollen.
146
Java 2 Kompendium
Eine erste Beispielanwendung und ein erstes Applet
Kapitel 4
Starten Sie einen ASCII-Editor und geben Sie nachfolgenden Quelltext ein.
import java.awt.Graphics; public class HelloJavaApplet extends java.applet.Applet { public void paint(Graphics g) { g.drawString("Hello Java!", 5, 25); }}
Listing 4.3: Das erste Applet
Dies ist Ihr erstes Java-Applet. Und wie geht es weiter? Wie bei der Applikation mit Speichern als dem nächsten notwendigen Schritt. Speichern Sie Ihren Quelltext in einem Verzeichnis Ihrer Wahl. Speichern Sie den Quelltext unter dem Namen HelloJavaApplet.java
ab (wir müssen alle didaktisch bewusst gemachten Fehler von der Applikation ja nicht wiederholen, oder?!). Im nächsten Schritt werden wir den gespeicherten Java-Source wieder kompilieren. Öffnen Sie wieder ein Befehlzeilenfenster und wechseln Sie dort in das Verzeichnis, wo der Java-Source abgelegt ist.
Rufen Sie den Java-Compiler auf. Geben Sie Folgendes ein: javac HelloJavaApplet.java
Falls Sie keine Tippfehler gemacht haben, sollte die Übersetzung gelungen sein. Der Compiler gibt bei erfolgreicher Übersetzung keine Rückmeldung (nur im Fehlerfall), deshalb sollten Sie zur Sicherheit den Inhalt des Verzeichnisses überprüfen. Es müssen zwei Dateien vorhanden sein (HelloJavaApplet.java und HelloJavaApplet.class). Der Appletviewer (oder ein Java-fähiger Browser) benötigt zum Aufruf des Applets eine HTML-Datei als Gerüst.
Java 2 Kompendium
147
Kapitel 4
Unterschiede zwischen Java-Applikationen und -Applets Wir erstellen nun in einem beliebigen ASCII-Editor unsere HTML-Datei zum Aufruf des Applets. Auch hier verwenden wir nur eine extrem rudimentäre Struktur. Geben Sie Folgendes ein:
Listing 4.4: Die HTML-Datei zum Referenzieren des ersten Applets
<APPLET CODE="HelloJavaApplet.class" WIDTH=150 HEIGHT=25>
Speichern Sie nun Ihre HTML-Datei in dem gleichen Verzeichnis ab, wo sich die .class-Datei befindet. Bei Ihrer Wahl des Namens für Ihre HTML-Datei sind Sie hier vollkommen frei. Allerdings gilt immer: je sprechender, desto besser. Vorschlag: AppletAufruf.html. Schauen Sie sich nun das Applet an. Dazu können wir jeden beliebigen Java-fähigen Browser verwenden. Laden Sie beispielsweise die HTML-Datei per Doppelklick im Windows-Explorer in Ihren Standardbrowser oder öffnen Sie sie als lokale Datei in dem DateiMenü des Browsers. Sie sollten auf jeden Fall etwas Ähnliches sehen wie im nachfolgenden Screenshot. Abbildung 4.4: Das erste Applet im Internet Explorer
Sie können ebenfalls den im JDK integrierten Appletviewer zum Ansehen verwenden. 148
Java 2 Kompendium
Zusammenfassung
Kapitel 4
Schauen Sie sich das Applet im Appletviewer an. Geben Sie bitte zum Aufruf des Appletviewers Folgendes ein: appletviewer HelloJavaApplet
Was erhalten Sie? Natürlich wieder eine Fehlermeldung. Vorsicht Glatteis. Sie müssen die HTML-Datei aufrufen. appletviewer AppletAufruf.html
Das Resultat müsste dann so ähnlich wie im nachfolgenden Screenshot aussehen. Abbildung 4.5: Das erste Applet im Appletviewer
Natürlich können auch Übergabeparameter an Applets weitergegeben werden. Dies ist jedoch nicht so einfach wie bei eigenständigen Java-Applikationen. Wir werden diesen Vorgang deshalb in einem späteren Kapitel – wenn uns bereits mehr Java-Kenntnisse zur Verfügung stehen – besprechen. Dort finden Sie dann auch noch weitere Details zu Applets – vor allem der Einbindung in HTML-Seiten.
4.3
Zusammenfassung
Java-Applikationen und Java-Applets sind zwar eng verwandt, nicht aber gleichzusetzen. Insbesondere muss für Applets erst einmal etwas mehr Aufwand betrieben werden als für eigenständige Applikationen. Dies relativiert sich jedoch, wenn die Anwendungen komplexer werden. Applets können zwar nur innerhalb eines Webbrowsers oder des Appletviewers ausgeführt werden, ihnen steht aber dann die vollständige Funktionalität dieses Containers zur Verfügung. Andererseits werden sie von diesem Container in ihrer Funktionalität beschränkt.
Java 2 Kompendium
149
Kapitel 4
Unterschiede zwischen Java-Applikationen und -Applets Die Einbettung in den Browser-Container erfolgt über eine entsprechende Referenz innerhalb einer HTML-Seite mit einem speziellen Tag – dem <APPLET>-Tag. Alternativ kann beispielsweise das neuere -Tag verwendet werden, das aber alte Browser nicht verstehen und gegen dessen Verwendung auch einige weitere – noch später besprochene – Gründe sprechen. Allerdings gibt es natürlich auch Argumente für dessen Verwendung, die wir ebenso anführen werden. Java-Applikationen sind vollständig in Java programmierte und eigenständig lauffähige Programme, die im Gegensatz zu den Applets keine Container mehr benötigen (von der zwingend notwendigen Existenz der so genannten virtuellen Java-Maschine abgesehen). Die technischen Unterschiede konzentieren sich im Wesentlichen in der Tatsache, dass eine eigenständige Applikation immer eine main()-Methode benötigt, während ein Applet diese nicht verwendet.
150
Java 2 Kompendium
5
Java-Hintergründe
Nachdem wir uns in ein paar ersten Beispielen an Java herangetastet haben, wollen wir nun zu einem etwas abstrakteren Kapitel kommen, das andererseits viel zum Grundverständnis von Java beiträgt. Dabei sind die ersten beiden Abschnitte über die Theorie der Objektorientierung bzw. die spezielle Objektorientierung von Java zwar recht trocken, aber für das Erlernen von Java von fundamentaler Bedeutung. Der dritte Abschnitt über die Plattformunabhängigkeit von Java und den Aufbau der JVM ist dagegen als technische Hintergrundinformation zu verstehen. Für einen ersten Einstieg in die Java-Welt sind diese Informationen nicht unbedingt nötig, Sie können bei Bedarf später darauf zurückkommen. Das soll aber natürlich nicht heißen, dass Sie diesen Abschnitt nicht auch jetzt schon durcharbeiten dürfen. Ich möchte nur vermeiden, dass Sie bereits zu Anfang von zu vielen technischen Details erschlagen werden.
5.1
Was ist OOP?
Ein zentraler Aspekt von Java ist seine Objektorientiertheit (Object Orientation – OO). Deshalb ist eine grundsätzliche Beschäftigung mit diesem Begriff zwingend erforderlich. Nicht zuletzt deshalb, um den Begriff zu entmystifizieren. Außerdem erleichtert ein elementares Verständnis des objektorientierten Konzeptes einen Einstieg in Java erheblich. Zwar kann man Java ebenfalls lernen, ohne sich um das objektorientierte Konzept große Gedanken zu machen. Jedoch werden dann viele wesentliche und unumgängliche Java-Elemente (Klassen, Methoden usw.) quasi vom Himmel fallen, während sie sich aus dem objektorientierten Konzept zwingend ergeben. Dabei gilt es als Erstes festzuhalten, dass nicht die Eigenschaften einer Programmiersprache in der Regel Objektorientiertheit ausmachen, sondern der Denkansatz für die Lösung einer zu programmierenden Aufgabe. Unter dieser abstrakten Aussage können Sie sich vielleicht nicht viel vorstellen, aber ich möchte es so verdeutlichen: Man kann mit jeder Programmiersprache objektorientiert – zumindest im weiteren Sinne – arbeiten, sogar mit solch überholten, prozeduralen Sprachen wie Basic, Cobol oder Fortran. Auch das sehr weit verbreitete Turbo Pascal wurde von seinem Hersteller Java 2 Kompendium
151
Kapitel 5
Java-Hintergründe Borland schon ab der Version 5.5 objektorientiert genannt. Allerdings erleichtern bestimmte Sprachen die objektorientierte Programmierung, andere (wohl auch die oben zitierten) machen es ziemlich schwierig. Weitere Programmiersprachen wiederum nennen sich objektorientiert und lassen den Programmierern so viele (prozedurale) Schlupflöcher, dass das OOKonzept erfahrungsgemäß immer dann ausgehebelt wird, wenn ein nichtobjektorientierter Ansatz auf den ersten Blick sinnvoller und einfacher erscheint. Als Beispiele für solche Sprachen kann man C++ oder Delphi anführen. Die hier nicht zwingende und teilweise recht kompliziert aufgebaute Logik zur Arbeit mit Objekten hat viele Programmierer davon abgehalten, sich ein grundsätzliches Verständnis des objektorientierten Konzepts zu erarbeiten. Und dann gibt es einen Typ von Programmiersprachen, der Programmierern keine andere Wahl als einen objektorientierten Ansatz lässt. Dazu zählen die schon recht alten Sprachen Lisp oder Small Talk. Und eben Java. Der Hauptgrund, dass der Begriff »Objektorientierte Programmierung« (OOP) in der Programmiererwelt für eine große Unruhe und Verwirrung sorgte, ist wohl der, dass Programmierer – nahezu seit es den Beruf gibt – anders denken: prozedural. Aber wir haben immer noch nicht geklärt, was es mit dem Begriff Objektorientierung überhaupt auf sich hat. Im Zentrum der althergebrachten, prozeduralen Programmierung steht die Umsetzung eines Problems in ein Programm durch eine Folge von Anweisungen, die in einer vorher festgelegten Reihenfolge auszuführen sind. Einzelne zusammengehörende Anweisungen werden dabei maximal in so genannte Funktionen oder Prozeduren zusammengefasst. Die Datenstruktur befindet sich in einer von den Anweisungen getrennten Hierarchieebene. Versuchen wir einen Erklärungsansatz über die Entwicklung der EDV. Denn die Geschichte der Programmierung zeigt, dass sich die nicht-objektorientierte Denkweise lange Zeit bewährt hatte und vor allem historische Computerplattformen einfach nicht zu einer anderen Vorgehensweise in der Lage. Und so hatte sich das traditionelle Denkmodell mit der Zeit einfach in den Köpfen der Programmierer festgesetzt. Großer Nachteil dieser nichtobjektorientierten Philosophie ist die mangelnde Wartbarkeit, denn Änderungen in der Datenebene können Auswirkungen auf die unterschiedlichsten Programmsegmente haben. Außerdem entspricht ein solches Denkkonzept der Logik von Maschinen, nicht dem Abbild der realen Natur. Dies war natürlich zu Zeiten der allerersten Computersysteme besonders ausgeprägt. Ein erster Schritt hin zu einer mehr menschlichen Logik war das in den 70er-Jahren entstandene Denkmodell der »strukturierten Programmierung«. Darin wurde versucht, zusammengehörende Befehle als Einheit zu sehen und Programmfunktionen in Unterstrukturen bzw. Unterprogramme (Prozeduren und Funktionen) zusammenzufassen, die bereits über genau
152
Java 2 Kompendium
Die Definition von Objektorientierung
Kapitel 5
festgelegte Schnittstellen zu ihrer Umwelt verfügten. Bei Bedarf konnte man diese Unterstrukturen über ihren Namen aufrufen. Dieser Ansatz stellt zwar eine Verbesserung gegenüber Abläufen dar, die jeden einzelnen Befehl bei Bedarf aufrufen – auch wenn mehrere Programmschritte mehrfach in der gleichen Version benötigt werden. Jedoch ist diese strukturierte Programmierung noch immer nicht die konsequente Umsetzung von menschlicher Denkweise. Insbesondere sind Anweisungen und Daten (Variablen) noch immer getrennt. Hier setzt die Philosophie der Objektorientiertheit als folgerichtige Weiterentwicklung der strukturierten Programmierung an.
5.2
Die Definition von Objektorientierung
Objektorientierte Programmierung lässt sich am treffendsten darüber definieren, dass die Trennung von Datenebene und Anweisungenebene aufgehoben wird. Zusammengehörende Anweisungen und Daten bilden eine zusammengehörende, abgeschlossene und eigenständige Einheit. Man nennt sie – Objekte! Objekte bestehen also im Allgemeinen aus zwei Bestandteilen, den so genannten Objektdaten – das sind die Attribute bzw. Eigenschaften – und aus Objektmethoden. Man kann sagen, dass Attribute die Merkmale sind, durch die sich ein Objekt von einem anderen unterscheidet. Objektmethoden stellen die objektorientierte Ausdrucksweise für die bisherigen Elemente der Anweisungenebene (Funktionen und Prozeduren) dar. Abbildung 5.1: Schema eines Objekts
5.2.1
Objektmethoden
Objektmethoden (oder kurz Methoden genannt) sollen in unserer Vorstellung erst einmal irgendwelche Algorithmen (d.h. Berechnungsformeln) sein, mit denen die – im Prinzip abgeschlossenen und eigenständigen – Objekte miteinander kommunizieren und/oder ihre Objektdaten manipulieren können.
Java 2 Kompendium
153
Kapitel 5
Java-Hintergründe Als eine sinnvolle Definition von Methode kann im Allgemeinen die folgende Aussage gelten: Methoden realisieren die Funktionalität der Objekte, ihre Implementierung erfolgt in den Klassen. Das heißt, Methoden sind die programmtechnische Umsetzung der Objektfunktionalitäten. Wesentlich an der objektorientierten Programmierung ist, dass die Methoden und die Daten (Attribute) gemeinsam einem Objekt zugeordnet sind. Nur über dieses Objekt kommt man an Methoden und die Daten des Objekts heran. Es gibt keine freien Eigenschaften und Funktionalitäten. Dies schließt bei strenger Objektorientierung als wichtige Konsequenz beispielsweise eine Existenz von globalen Variablen aus. Die Zuordnung bedeutet weiterhin (ebenfalls sehr wichtig für das grundlegende Konzept von Java), dass es Methoden ohne zugehörige Objekte nicht geben darf. Obwohl wir nun etwas vorgreifen müssen, soll eine wesentliche – und die einzige – Ausnahme erwähnt werden. Diese Ausnahme scheinen diejenigen Methoden darzustellen, die außerhalb der konkreten Objekte, jedoch innerhalb der Klassen liegen – die so genannten Klassenmethoden. In die gleiche Kerbe schlägt die Existenz von Klassenvariablen, die eine Art von globalen Variablen darstellen. Diese scheinbaren Ausnahmen passen aber dennoch in das objektorientierte Konzept und widerlegen nicht die oben getroffenen Aussagen, sofern man das so genannte Metaklassen-Konzept berücksichtigt, das etwas weiter unten noch erläutert wird. Ein kleines Beispiel soll die Kopplung von Methoden und Eigenschaften an ein Objekt deutlich machen. Wenn man prozedural einen Bauernhof abbilden wollte, könnte man beispielsweise einen Hahn, ein Schwein und eine Kuh programmieren. Zusätzlich erstellt man Funktionen oder Prozeduren, die das Krähen, das Grunzen und das Muhen realisieren. Da diese jedoch nicht an das jeweils sinnvolle Objekt gekoppelt sind, könnte eine Kuh plötzlich krähen, ein Schwein muhen und ein Hahn grunzen. Im objektorientierten Ansatz ist so etwas nicht möglich, denn die Realisierung der Objekte Hahn, Kuh und Schwein beinhaltet bereits deren Funktionalitäten in Form von Methoden, die auch nur über das jeweilige Objekt anzusprechen sind. Auf der anderen Seite ist ein Objekt nach außen nur durch seine Methoden und Attribute definiert, es ist gekapselt, versteckt seine innere Struktur vollständig vor andern Objekten. Man nennt dies Information Hiding oder Datenkapselung. Der ganz entscheidende Vorteil von diesem Verfahren ist, dass ein Objekt sich im Inneren, d.h. bezüglich seiner Objektdaten und seiner inneren Attribute, vollständig verändern kann. Solange es sich nur nach außen unverändert zeigt, wird das veränderte Objekt problemlos in ein System integriert, wo es in seiner alten Form funktioniert hatte. Ob nun – um auf unseren Bauernhof zurückzukommen – die Methode »Krähen« über die Wiedergabe einer lokalen MP3-Datei oder einer MP3-Datei aus einem (hinreichend
154
Java 2 Kompendium
Die Definition von Objektorientierung
Kapitel 5
schnellen) Netzwerk realisiert wird, ändert nichts an der Funktionalität des Objekts. Ein Objekt ist also eine Art »Black Box«. Diese gesamte objektorientierte Philosophie entspricht viel mehr der realen Natur als der prozedurale Denkansatz, der von der Struktur des Computers definiert wird. Ein Objekt ist im Sinne der objektorientierten Philosophie eine Abstraktion eines in sich geschlossenen Elements der realen Welt. Dabei spricht man von Abstraktion, weil zur Lösung eines Problems normalerweise weder sämtliche Aspekte eines realen Elements benötigt werden, noch überhaupt darstellbar sind. Irgendwo muss immer abstrahiert werden – und wenn es erst auf der Ebene der Atome ist, jedoch dann wird es philosophisch. Ein Mensch bedient sich eines Objekts, um eine Aufgabe zu erledigen. Man weiß in der Regel nicht genau, wie das Objekt im Inneren funktioniert, aber man kann es bedienen (weiß also um die Methoden, um es verwenden zu können) und weiß um die Eigenschaften des Objekts (seine Attribute). Die Reihe von Beispielen kann man beliebig lang ausdehnen, wir wollen es beim Auto, der Waschmaschine oder der Kaffeemaschine (es geht ja in dem Buch um Java) belassen. Im Detail sind bei der Erklärung der Objektorientierung jetzt noch einige Festlegungen notwendig.
5.2.2
Who's calling?
Besonders wichtig ist der Begriff der Botschaften. Damit Objekte aktiv werden können, tauschen sie so genannte Botschaften (oder Nachrichten bzw. Messages) aus, die ausschließlich für die Kommunikation von Objekten verwendet werden. Andere Kommunikationswege – etwa die in anderen Programmiersprachen oft genutzten globalen Variablen – gibt es nicht (dies wären ja Attribute, die außerhalb von Objekten existieren würden und das ist explizit nicht erlaubt). Das sendende Objekt schickt dem Zielobjekt eine Aufforderung, eine bestimmte Methode auszuführen. Das Zielobjekt versteht (hoffentlich) diese Aufforderung und reagiert mit der zugehörigen Methode. Dieser Vorgang wird von einigen OO-Gurus als eine Art Bitte bezeichnet, mit der Objekte andere Objekte höflich zur Ausführung diverser Wünsche veranlassen. Die genaue formale Schreibweise solcher Botschaften in OO-Programmiersprachen ist natürlich im Detail verschieden, jedoch meistens wird das Schema Empfänger
Java 2 Kompendium
Methodenname
Argument
155
Kapitel 5
Java-Hintergründe verwendet. Punkt und Klammer trennen dabei meist die drei Bestandteile der Botschaft (die so genannte Punkt-Notation). Eine Botschaft sieht also meist so aus: Empfänger.Methodenname(Argument)
Dies kann aber von Programmiersprache zu Programmiersprache differieren. Das Argument stellt in dem Botschafts-Ausdruck einen Übergabeparameter für die Methode dar. Eine ähnliche Form wird auch für die Verwendung von Objektattributen gewählt. In der Regel sieht das dann so aus: Empfänger.Attributname
Ein fundamentaler Unterschied zwischen der prozeduralen und der objektorientierten Denkweise kann so umschrieben werden: Prozedural bedeutet typengestützt, objektorientiert heißt kommunikationsgestützt. Konkret drückt dies aus, dass eine Prozedur oder Funktion im herkömmlichen Sinn nur Variablen fest vorgegebener Datentypen verwenden kann. Falls eine bestimmte Operation für eine Menge von verschiedenen Datentypen benötigt wird (ein gutes Beispiel ist die Multiplikation), muss für jeden möglichen Typ eine Prozedur geschrieben werden, zumeist noch mit verschiedenen Namen. Die Folge ist in der Regel Fallunterscheidung nach dem Datentyp. Ein neuer Datentyp zieht eine weitere Prozedur/Funktion und – noch schlimmer – an diversen Stellen im Programm Änderungen nach sich. Um das Beispiel der Multiplikation zu nehmen, kann man die mathematischen Datentypen »Reelle Zahlen« und »Komplexe Zahlen« heranziehen und die zugehörigen – mathematisch abweichenden – Multiplikationsvorschriften in zwei Prozeduren abbilden. Die Prozedur für die Multiplikation der reellen Zahlen sollte in etwa so aussehen (das Listing ist nur als Schema zu verstehen): Listing 5.1: Schema einer Multiplikation reeller Zahlen
procedure multiplikation(double a,double b) { double ergebnis; ergebnis= a* b; return (ergebnis); }
Für komplexe Zahlen muss eine vollständig andere Prozedur definiert werden, etwa folgende (dabei seien ergebnis_real und ergebnis_imagiär global definierte Variablen):
156
Java 2 Kompendium
Klassen und Instanzen procedure multi_komplex(double realteil_1, double realteil_2, double imaginaer_teil_1, double imaginaer_teil_2) { ergebnis_real = (realteil_1* realteil_2) – (imaginaer_teil_1* imaginaer_teil_2); ergebnis_imaginaer = (realteil_1* imaginaer_teil_2) + (realteil_2 * imaginaer_teil_1); }
Kapitel 5 Listing 5.2: Schema einer Multiplikation komplexer Zahlen
Man sieht, dass sowohl die Zahl der Übergabeparameter abweicht als auch der eigentliche Berechungsalgorithmus. Zwei – in der Regel unterschiedlich benannte – Prozeduren sind die Folge. In der objektorientierten Sichtweise hingegen steht das Objekt im Mittelpunkt. Die Operation ist in der Klasse implementiert, zu der das Objekt gehört. Die Ausführung der Multiplikation übernehmen die Objektmethoden, der Anwender muss sich darum nicht kümmern. Soll nun eine bestehende Operation (in unserem Beispiel sei das die Multiplikation der reellen Zahlen) um eine neue Funktionalität erweitert werden (Berechnungsvorschrift für komplexe Zahlen), so werden die Veränderungen in der Klasse vorgenommen und die zugehörigen Methoden dort geschrieben. Die neue Form der Multiplikation wird einfach als Erweiterung innerhalb der Klasse hinzugefügt. Nach außen erscheint das Objekt wie schon erwähnt unverändert und lässt sich wie gehabt unter einem Namen ansprechen. Weitergehende Änderungen im Programm sind nicht notwendig.
5.3
Klassen und Instanzen
Der Begriff der Klasse ist von elementarer Wichtigkeit, wenn man in Java oder einer anderen OO-Sprache programmieren möchte. In der OOP werden ähnliche Objekte zu Gruppierungen zusammengefasst, was eine leichtere Klassifizierung der Objekte ermöglicht. Die Eigenschaften der Objekte werden also in den Gruppierungen gesammelt und für eine spätere Erzeugung von realen Objekten verwendet. Diese Beschreibungen oder Baupläne für konkrete Objekte nennt man Klassen, die Objekte selbst sind im OOSprachgebrauch Instanzen dieser Klassen. Oft werden Klassen mit Schablonen verglichen. Man könnte sich Klassen auch als Backformen vorstellen, mit denen Plätzchen (Instanzen) aus einem (Computer-)Teig gestochen werden. Zentrale Bedeutung hat dabei die hierarchische Struktur der Gruppierungen von allgemein bis fein (zumindest bei der Einfachvererbung, auf deren genaue Bedeutung wir noch zu sprechen kommen).
Java 2 Kompendium
157
Kapitel 5
Java-Hintergründe
Abbildung 5.2: Klassen-InstanzenBeziehungen
Beispiel: Ein Objekt Saxophon als konkrete Instanz gehört zu der Klasse der Holzblasinstrumente1. Diese Holzblasinstrumente wiederum gehören zu einer höheren Klasse, den Musikinstrumenten, und diese sind allgemein der Klasse der »Musik erzeugende Dinge« zugehörig. Abbildung 5.3: HierarchieBeziehungen
1
158
Um Lesermails vorzubeugen – das ist in der Tat so: Saxophone zählen zur Familie der Klarinetten. Sie sind zwar aus Metall, besitzen aber ein Holzblättchen zur Tonerzeugung und fallen deshalb unter die Klasse der Holzblasinstrumente.
Java 2 Kompendium
Klassen und Instanzen
Kapitel 5
Gemeinsame Erscheinungsbilder sollten also in der objektorientierten Philosophie in einer möglichst hohen Klasse zusammengefasst werden. Erst wenn Unterscheidungen möglich bzw. notwendig sind, die nicht für alle Mitglieder einer Klasse gelten, werden Untergruppierungen, also untergeordnete Klassen, gebildet. Jede (gewöhnliche) Klasse kann eine Vielzahl von Unterklassen und konkreten Instanzen (Objekten) haben. Abbildung 5.4: Eine Klassenhierarchie
5.3.1
Superklasse und Subklasse
Damit wir jetzt schon in der späteren Java-Sprache sprechen (außerdem ist es gültiger OO-Dialekt), führen wir für eine übergeordnete Klasse den Begriff Superklasse, eine untergeordnete Klasse den Begriff Subklasse ein. Die ineinander geschachtelten Klasse bilden einen so genannten Klassenbaum. Dieser kann im Prinzip beliebig tief werden. Eben so tief, wie es notwendig ist, um eine Problemstellung detailliert zu beschreiben. Jede Subklasse erbt alle Eigenschaften und Methoden ihrer Superklasse. Sie beinhaltet also immer mindestens die gleichen Eigenschaften und Methoden wie die Superklasse. Daher sollte dort sinnvollerweise mindestens eine Eigenschaft oder Methode hinzugefügt werden, die die Superklasse nicht beinhaltet (sonst sind Sub- und Superklasse ja identisch). Mit dieser Erklärung haben wir zum ersten Mal den Begriff der Vererbung herangezogen, der in der OOP von fundamentaler Bedeutung ist. Gleich mehr zur Vererbung, doch vorher klären wir noch zwei Begriffe aus dem Klassenbereich.
Java 2 Kompendium
159
Kapitel 5
Java-Hintergründe
5.3.2
Metaklassen
In Java sind alle Klassen Ableitungen einer einzigen obersten Klasse – der Klasse Object. Andererseits soll Java ein konsequent realisiertes objektorientiertes System sein. In einem solchen System darf es nur Objekte geben, daher müssen selbst Klassen ebenfalls Objekte sein. Das Problem ist jetzt, dass damit auch die oberste Klasse selbst die Instanz einer Klasse sein muss. Jedes Objekt ist nach Definition die Instanz einer Klasse, also muss gleichfalls jede Klasse – sogar die oberste Klasse – Instanz einer anderen Klasse sein. Eigentlich ist dies für die oberste Klasse ein Widerspruch. Die Frage ähnelt ein wenig der Problematik, was sich außerhalb des Universums befindet2. Im Grunde befindet sich keine Klasse mehr außerhalb dieses Objektuniversums. Was also tun, um in dem Modell konsistent zu bleiben? Man definiert zur Einhaltung des streng objektorientierten Konzeptes deshalb so genannte Metaklassen. Die einzigen Objekte von Metaklassen sind Klassen, und sie vererben die Klassenmethoden und Klassenvariablen. Diese werden nicht an das Objekt selbst, sondern an die Objektklasse vererbt (Methoden zum Erzeugen von Objekten oder zur Initialisierung von Klassenvariablen). Neben der Konsistenz des Konzepts ist ein weiterer Vorteil des Metaklassenkonzepts, dass die Klassenmethoden in den Klassen überladen werden können. Gibt es für alle Klassen nur eine einzige Metaklasse, dann gibt es für alle Klassen dieselben Klassenmethoden. Dieses Metaklassenkonzept ist recht ungewöhnlich. Viele andere objektorientierte Sprachen verzichten darauf. C++ verfügt beispielsweise über keine Klassenmethoden und unterstützt das Metaklassenkonzept nicht. An ihre Stelle treten Systemfunktionen, wie etwa ein Operator mit Namen new, um die Instanz einer Klasse zu erzeugen. Diesen new-Operator gibt es in Java ebenfalls. Sogar mit der gleichen Aufgabe. Er ist nur bedeutend schlüssiger in das logische Konzept integriert.
5.3.3
Abstrakte Klassen
Als letzten Klassentyp besprechen wir an dieser Stelle die abstrakten Klassen. Unter einer abstrakten Klasse versteht man eine Klasse, von der nie eine direkte Instanz benötigt wird und auch keine Instanz direkt erstellt werden kann. Eine solche abstrakte Klasse dient zu einem oder mehreren allgemeinen Verweis(en). Signifikante Eigenschaft ist, dass abstrakte Klassen keine Implementierung einer Methode enthalten dürfen und damit auch nicht instanzierbar sind. Java charakterisiert abstrakte Klassen mit dem Schlüsselwort abstract.
2
160
Oder, was zuerst da war: die Henne oder das Ei?
Java 2 Kompendium
Vererbung
Kapitel 5
Abstrakte Klassen werden im Allgemeinen zur Strukturierung eines Klassenbaums verwendet. Eine weitere Aufgabe von ihnen ist die Vereinbarung von Methodenunterschriften, auch wenn die Methoden selbst erst zu einem späteren Zeitpunkt implementiert werden. Die Namen der Methoden und deren Parameter sind dann trotzdem in den Unterklassen schon bekannt. Abstrakte Klassen dienen außerdem dazu, zwei oder mehrere Klassen zusammenzufassen, die zwar einige Gemeinsamkeiten besitzen, aber nicht unabhängig voneinander herzuleiten sind und wo es ist nicht offensichtlich ist, welche die Superklasse der anderen ist. In einer abstrakten Oberklasse können die Gemeinsamkeiten zusammengefasst werden.
5.4
Vererbung
Die Beziehung der ursprünglichen Klasse, der Superklasse, zur abgeleiteten, der Subklasse, ist immer streng hierarchisch und heißt Vererbung. Abgeleitete Klassen übernehmen die Eigenschaften und Methoden aller übergeordneter Klassen, wobei Übernehmen nicht bedeutet, dass eine Subklasse die Befehle und Eigenschaften der Superklasse in ihre eigene Deklaration kopiert. Statt dessen gibt es nur eine formale Verknüpfung zwischen den Klassen (eine Art von Zeiger). Mit anderen Worten: Die abgeleitete Klasse verwendet bei Bedarf die Methoden oder Eigenschaften der Superklasse. Den Mechanismus kann man sich analog dem Verwenden von Bibliotheken und dort implementierten Funktionalitäten vorstellen. Die Methoden- bzw. Eigenschaftenauswahl in einer Klassenhierarchie muss natürlich geregelt sein. Sie erfolgt nach einer einfachen Regel. Ist der Nachrichtenselektor (der Methodenname einer Botschaft) in der Objektklasse des Empfängers nicht vorhanden, so wird die gewünschte Methode oder Eigenschaft in der nächst höheren Superklasse des Nachrichtenselektors gesucht. Ist sie dort nicht vorhanden, erfolgt die Suche in der nächst höheren Klasse, bis die oberste Superklasse der Klassenbaums erreicht ist (bei Java die Klasse Object). Die Ausführung der Methode bzw. der Zugriff auf die Eigenschaft erfolgt also in der ersten Klasse, in der sie gefunden wird (von der aktuellen Klasse in der Hierarchie aufwärts gesehen). Gibt es im Klassenbaum keine Methode bzw. Eigenschaft des spezifizierten Namens, so kommt es zu einer Fehlermeldung. Klassen auf derselben Ebene oder in anderen Zweigen werden nicht durchsucht (siehe Abbildung 5.5). Subklassen werden ihre Erbschaft in der Regeln nicht unverändert lassen. Sie können mit ihren ererbten Eigenschaften und Methoden Einiges anstellen, wie beispielsweise: Attribute hinzufügen neue Methoden spezifizieren
Java 2 Kompendium
161
Kapitel 5
Java-Hintergründe vorhandene Methoden überladen geerbte Methoden ergänzen
Abbildung 5.5: Vererbung über mehrere Ebenen
5.4.1
Mehrfachvererbung
Der Begriff Mehrfachvererbung gehört zu einer Betrachtung der OOP einfach dazu, obwohl Java explizit auf Mehrfachvererbung verzichtet. Aus gutem Grund, wie man bei solchen Sprachen sieht, die Mehrfachvererbung realisiert haben (etwa C++). Bei der oben beschriebenen Vererbung gilt für die Klassenhierarchie in einer baumartigen Struktur die Voraussetzung, dass eine Subklasse immer nur genau eine Superklasse hat. Man nennt eine solche Struktur Einfachvererbung (Single Inheritance). Einige objektorientierte Programmiersprachen (beispielsweise C++) bieten jedoch die Möglichkeit, eine Klasse mit mehreren (bzw. beliebig vielen) Superklassen durch die Vererbung zu verknüpfen. Dies nennt man Mehrfachvererbung (Multiple Inheritance). Objekte der Subklasse erben Eigenschaften aus verschiedenen Superklassen. Es gibt unbestritten einige gute Gründe für die Mehrfachvererbung. Der entscheidende Vorteil der mehrfachen Vererbung liegt in der Möglichkeit, die Probleme der realen Welt einfacher beschreiben zu können, denn auch dort hat ein Objekt Eigenschaften und Fähigkeiten aus verschiedenen übergeordneten logischen Bereichen.
162
Java 2 Kompendium
Vererbung
Kapitel 5 Abbildung 5.6: Mehrfachvererbung
Ein Saxophon gehört nicht nur zu der Klasse der Holzblasinstrumente und diese wiederum zur Klasse der »Musik erzeugende Dinge«. Ein Saxophon kann man ebenso zur Superklasse der »Nervenden Krachmacher« – für Nachbarn :-) – und zusätzlich in gleicher Weise zur Superklasse der »Stress erzeugenden Ereignisse« – ebenfalls für Nachbarn und vor allem für Saxophonlehrer bei entsprechend begabten Schülern ;-) – zählen. Es erbt seine Eigenschaften aus den diversen Superklassen. Damit lässt sich das Beziehungsgeflecht des realen Objekts durch die Sammlung der Superklassen (relativ) vollständig beschreiben. Ein weiterer Vorteil ist, dass man leicht Dinge ergänzen kann, die man bei der ersten Realisierung einer Klasse vergessen hat. Wenn bestimmte Eigenschaften und Methoden in einer Subklasse vergessen wurden, nimmt man einfach eine weitere Superklasse hinzu, die die fehlenden Elemente vererben kann. Diese Vorteile einer relativ vollständigen und einfachen Abbildung der Natur stehen aber in keinem Verhältnis zu den damit eingekauften Nachteilen. Die schlimmsten Nachteile sind sicher die kaum nachvollziehbaren Beziehungsgeflechte in komplexeren Programmen. C++-Programmierer (vor allem diejenigen, die ein Programm übernehmen mussten) wissen davon ein Lied zu singen. Wartbarkeit wird bei exzessivem Einsatz der Mehrfachvererbung zum Fremdwort. Java arbeitet deshalb ohne Mehrfachvererbung. Dafür bietet Java einen ähnlich leistungsfähigen Mechanismus, der dennoch die Wartbarkeit erhält – das Konzept der Schnittstellen. Diese gehören aber weniger in die Abhandlung über die allgemeinene Begriff der OOP, sondern werden später an geeigneten Stellen ausführlich diskutiert.
Java 2 Kompendium
163
Kapitel 5
Java-Hintergründe
5.5
Überschreiben
Eine weitere fundamentale Möglichkeit der OOP ist das Überschreiben von bereits im Klassenbaum vorhandenen Methoden, um damit eine Spezialisierung von Objekten zu erreichen. Überschreiben (Overriding) bedeutet, dass bei Namensgleichheit von Methoden in Superklasse und abgeleiteter Klasse die Methoden der abgeleiteten Klasse die der Superklasse überdecken. Überschreiben heißt im Englischen eigentlich Overwriting. Overriding ist jedoch kein Schreibfehler, sondern es bedeutet im Grunde Überdefinieren, wird jedoch im Deutschen zum besseren (?) Verständnis als Überschreiben übersetzt. Aus der Regel für die Methodenauswahl in einer Klassenhierarchie (ist der Nachrichtenselektor in der Objektklasse des Empfängers nicht vorhanden, so wird die gewünschte Methode in der nächst höheren Superklasse des Nachrichtenselektor gesucht usw., also von unten nach oben) folgt ein solches Überschreiben-Konzept im Prinzip zwingend, denn eine Methode wird auf der Ebene ausgeführt, wo sie zuerst gefunden wird.
5.6
Polymorphismus
Wir haben bereits gesehen, dass Operationen in der OOP denselben Namen haben können, sogar, wenn sie auf dasselbe Objekte angewandt werden können, aber auch, wenn sie zu verschiedenen Objekte gehören. Dies hätte in der prozeduralen Welt mehrere unterschiedliche (auch namentlich) Operationen (Funktionen oder Prozeduren) zur Folge. Das Beispiel der Multiplikation machte es deutlich. Dabei ist es in der OOP nicht unbedingt notwendig, dass die verschiedenen Objekte, auf die die Operation angewandt wird, zur selben Klasse gehören. Auch auf Objekte, die zu unterschiedlichen Klassen gehören, lässt sich dieselbe Operation definieren. Immer wenn ein und dieselbe Operation sich verschieden auswirken kann und die konkrete Erzeugungsklasse irrelevant ist, wird dies Polymorphismus genannt. Wichtig ist, dass es dabei vollkommen unerheblich ist, ob diese Objekte durch Vererbung in Beziehung zueinander stehen oder nicht. Java ist polymorph.
164
Java 2 Kompendium
Binden
5.7
Kapitel 5
Binden
Es ist bereits angesprochen worden, dass Objekte gegenseitig per Botschaften in Kontakt treten. Die gerade angesprochenen Möglichkeiten des Polymorphismus und der Vererbung generieren aber ein gewisses Problem. Wie soll eine Nachricht die physikalische Adresse eines Objekts im Speicher finden? Durch Polymorphismus und Vererbung ist der Methodennamen (oft Selektor genannt) ja meist nur eine Verknüpfung zur physikalischen Implementierung (dem Programmcode) der Methode und nicht die Adresse des eigentlichen Speicherbereichs. Damit eine Nachricht die Ausführung einer Methode bewirken kann, muss allerdings irgendwann einmal (vor der Ausführung der Methode zur Laufzeit) eine Zuordnung zwischen dem Selektor und dem tatsächlichen Programmcode der Methode stattfinden. Diesen Vorgang der Zuordnung nennt man Binden oder Linken. Dabei unterscheidet man in der OOP zwei Formen des Bindens, die von dem Bindezeitpunkt abhängig sind: das frühe Binden und das späte Binden.
5.7.1
Frühes Binden
Frühes Binden (Early Binding oder oft statisches Binden genannt) bedeutet, dass ein Compiler schon zum Zeitpunkt der Übersetzung des Programms die tatsächliche physikalische Adresse der Methode dem Methodenaufruf zuordnet. Grundsätzlicher Vorteil ist, dass schon zur Zeit der Kompilierung fehlerhafte Angaben vom Compiler gefunden werden können. Außerdem sind solche früh gelinkten Programme schnell, da die physikalische Zieladresse eines Methodenaufrufs zur Programmlaufzeit schon festliegt und nicht immer wieder neu berechnet werden muss. Grö ß ter Nachteil des frühen Bindens ist die mangelnde Flexibilität bei interaktiven Aktionen, also wenn durch Aktionen – etwa durch einen Anwender – neue Objekte in einem Programm erzeugt und verwaltet werden müssen. Das klingt vielleicht abstrakt, ist jedoch in vielen Programmen notwendig. Denken Sie beispielsweise an Grafikprogramme, wo durch die diversen Zeichenoperationen permanent neue Instanzen von Objekten erzeugt werden können. Ein objektorientiertes Grafikprogramm kann unter anderem aus einer Anzahl von Klassen in einem Grafiksystem bestehen: Linie, Kreis, leeres Rechteck, gefülltes Rechteck usw. Jede dieser Klassen beinhaltet nun beispielsweise eine Methode zur Ausgabe der jeweiligen Figur auf dem Bildschirm. Gemeinsamkeiten dieser Zeichnen-Methoden sind sinnvollerweise in einer Superklasse definiert. Die konkreten Figuren befinden sich in Subklassen und erben die gemeinsamen Merkmale. Für die Verwaltung aller verwendeten Grafikobjekte werden oft so genannte Listen verwendet. Diese Listen könnten nun so aufgebaut sein, dass dabei Elemente einer Liste Verweise aufeinander enthalten. Die Struk-
Java 2 Kompendium
165
Kapitel 5
Java-Hintergründe tur ähnelt einer Kette. Jedes Element hat zusätzlich einen Verweis auf die konkrete Instanz eines Objekts. In einem Grafikprogramm erstellt der Benutzer nun interaktiv am Bildschirm seine Figuren. Beim Zeichnen eines neuen Objekts durch einen Anwender wird das Listenelement adressiert, eine Verknüpfung mit der Liste hergestellt und die Datenadresse der Instanz eingetragen. Das heißt, der Typ des Listenelements ist ein Verweis auf die Superklasse. Zur Laufzeit wird hier jedoch eine Referenz auf die Subklasse – das konkrete Zeichenobjekt – vermerkt. Zum Zeitpunkt der Kompilierung kann der konkrete Listenplatz von einem Objekt (etwa einem gefüllten Reckeck) und seinen Methoden nicht bekannt sein, da es ja noch nicht existiert. Also muss beim frühen Linken eine andere, umständliche Variante der Objektverwaltung verwendet werden.
5.7.2
Spätes Binden
Wenn das Linken nun später erfolgen würde, wäre das System bedeutend flexibler. Man nennt diesen Vorgang dann spätes Binden (Late Binding oder dynamisches Binden genannt). Der Begriff ist bereits bei den Eigenschaften von Java aufgetaucht, denn Java verwendet diesen Mechanismus. Hier wird erst zur Laufzeit eines Programms die tatsächliche Verknüpfung zwischen dem Selektor und dem Code hergestellt. Die richtige Verbindung übernimmt das Laufzeitsystem der Programmiersprache. Ein solches System ist viel leichter erweiterbar. Es muss nur bei einer Interaktion eine weitere Subklasse von der grafischen Superklasse und die zugehörige ZeichnenMethode definiert werden. Die Implementierung ändert sich nicht, da erst zur Laufzeit die Verbindung erfolgt. Damit nimmt man allerdings eine schlechtere Performance in Kauf. Außerdem steigt bei ungesicherten Entwicklungsumgebungen mit spätem Binden die Gefahr von Fehlern, da fehlende oder falsche Methodenaufrufe erst zur Laufzeit des Programms bemerkt werden. Für Java besteht allerdings keine Gefahr, denn dort wird explizit mit diversen Sicherheitsmechanismen beim Kompilieren und Linken dieses Problem beseitigt.
5.8
Die Grundprinzipien der objektorientierten Programmierung
Aus den bisherigen Ausführungen können wir jetzt die Grundprinzipien der OOP herleiten. Objekte heben die Trennung von Daten und Anweisungen auf. Objekte tauschen Nachrichten aus und reagieren darauf. Andere Kommunikationswege gibt es nicht.
166
Java 2 Kompendium
Objektorientierte Analyse und objektorientiertes Design
Kapitel 5
Subklassen erben Fähigkeiten und Eigenschaften der zugehörigen Superklasse. Subklassen können geerbte Fähigkeiten und Eigenschaften erweitern und verändern. Objekte erhalten ihre Fähigkeiten und Eigenschaften als Instanz einer konkreten Klasse. Wenn Objekte zu verschiedenen Klassen gehören, werden sie auf die gleiche Nachricht unterschiedlich reagieren (Polymorphismus).
5.9
Objektorientierte Analyse und objektorientiertes Design
Im Rahmen der objektorientierten Philosophie tauchen immer wieder zwei weitere Begriffe auf, die eng daran gekoppelt sind und zur Vorbereitung der OOP dienen – die objektorientierte Analyse (OOA) und das objektorientierte Design (OOD). Zwar sprengt eine ausführliche Darstellung dieser beiden vorbereitenden Techniken den Rahmen des Buchs, der Versuch einer kurzen Beschreibung ist der Vollständigkeit halber aber sicher sinnvoll.
5.9.1
Die objektorientierte Analyse
Unter einer Analyse versteht man eine gründliche Untersuchung eines Problems, bevor man versucht, das Problem zu lösen. Vereinfacht gesagt: Nachdenken, bevor man vollendete Tatsachen schafft, die ein Problem nicht optimal (im günstigsten Fall) oder gar nicht lösen. Die OOA ist nun eine – vielfach als sehr abgehoben und theoretisch bezeichnete – Technik, mit der eine solche Problemanalyse in der objektorientierten Welt in ein strukturiertes Konzept gefasst werden soll. Sie soll nur ganz kurz angerissen werden. Die OOA umfasst fünf Grundvorgänge: Die Suche und Festlegung von möglichst optimalen Klassen und Objekten zur Beschreibung des Problems Die Identifikation von relevanten Strukturen in der Problemstellung Die Identifikation von relevanten Subjekten in der Problemstellung Die Identifikation und Definition von Methoden Die Identifikation und Definition von Diensten
Java 2 Kompendium
167
Kapitel 5
Java-Hintergründe Es gibt sogar ein aus vier Ebenen bestehendes, sehr theoretisches Schichtenmodell für die OOA, das sich aus den genannten Grundvorgängen ableitet.
Tabelle 5.1: Das OOASchichtenmodell
Das OOA-Schichtenmodell 1
Klassen- und Objektschicht
2
Strukturschicht
3
Attributschicht
4
Serviceschicht
Die OOA steht in der Theorie der Softwareentwicklung immer am Anfang.
5.9.2
Das objektorientierte Design
Das OOD ist der mittlere Zyklus der Softwareentwicklung und wird nach der OOA durchgeführt. In der OOA wird das Problem analysiert und so aufbereitet, dass es nun im OOD für bestimmte Hard- und Softwareplattformen umgesetzt werden kann. Im OOD gibt es vier Grundvorgänge: Design einer Anwendungsgebiets-Komponente Design einer Benutzeroberflächen-Komponente Design einer Verarbeitungs-Komponente Design einer Datenmanagement-Komponente Unter der Berufssparte der OO-Designer werden wahrscheinlich die wenigsten Java-Freunde anzutreffen sein. Java ist durch die Plattformneutralität ein potenter Jobkiller für die Tätigkeit dieser hochspezialisierten OO-Designer. Oder unter Kostenaspekten positiv ausgedrückt: Java reduziert den Aufwand und damit die Kosten für den Mittelbau in der Softwareerstellung erheblich. Statt für diverse Plattformen zu designen, muss unter Java jeder OOD-Vorgang nur einmal durchgeführt werden. Wie dem auch sei, am Ende dieser beiden Prozesse steht dann die OOP.
168
Java 2 Kompendium
Die Objektorientierung von Java
5.10
Kapitel 5
Die Objektorientierung von Java
Java ist eine rein objektorientierte Sprache. Sie ist sogar eine äußerst strenge objektorientierte Sprache. Hier sollen kurz, d.h. ohne allzu ausführliche Diskussion der konkreten Java-Syntax, die wesentlichen Java-Konzepte erläutert werden.
5.10.1
Java-Klassen
Jedes Java-Programm besteht aus einer Sammlung von Klassen. Der gesamte Code, der bei Java verwendet wird, wird in Klassen eingeteilt. Jede Klasse, abstrakt oder nicht, definiert das Verhalten eines Objekts durch verschiedene Methoden. Verhalten und Eigenschaften können von der einen Klasse zur nächsten weitervererbt werden. Alle Klassen in Java haben eine gemeinsame Oberklasse, die Klasse Object. Diese wiederum verfügt nur über eine Metaklasse, aber damit bleibt das objektorientierte Konzept voll konsistent. Die Einteilung des Codes in Klassen bedarf einer näheren Erläuterung. Um Java möglichst einfach zu halten, gibt es eine Ausnahme, die jedoch geschickt in das Konzept eingepasst wurde. Boolesche Operatoren, Zahlen und andere einfache Typen sind in Java erst einmal keine Objekte. Aber Java hat für alle einfachen Typen so genannte Wrapper-Objekte implementiert. Ein Wrapper-Objekt ist eine spezielle Klasse, die eine Objektschnittstelle für die in Java vorhandenen primitiven Typen darstellt. Diese Wrapper-Objekte erlauben es, dass alle einfachen Typen wie Klassen verwendet werden können.
5.10.2
Polymorphismus und Binden
Java unterstützt die OO-Grundprinzipien Polymorphismus und das Konzept des Late Binding. Gerade das Late Binding trägt zur Sicherheit und Stabilität von Java erheblich bei.
5.10.3
Schnittstellen und Pakete statt Mehrfachvererbung
Obwohl Java streng objektorientiert ist, heißt das nicht, dass in Java alles, was in der OO-Theorie erlaubt ist, tatsächlich realisiert wurde. So unterstützt Java keine Mehrfachvererbung. Das erscheint zunächst als eine große Einschränkung, macht Java-Programme allerdings stabil und leicht wartbar. Der Verzicht auf Mehrfachvererbung erzwingt eine gründlichere Vorbereitung, bevor man losprogrammiert (oder feiner ausgedrückt – eine saubere objektorientierte Analyse). Hier bietet sich dann auch ein neues Beschäftigungsfeld für einige der OO-Designer, die Java um ihren Job gebracht hat. Es ist kaum zu zählen, wie viele Programme nur deshalb so schlecht wartbar Java 2 Kompendium
169
Kapitel 5
Java-Hintergründe und instabil sind, weil man sich am Anfang zu wenig Gedanken gemacht hat und bei fehlenden Funktionalitäten zu stricken angefangen hat. Unter Basic gab es dazu den berüchtigten GOTO-Befehl, andere Sprachen ließen die Implementierung fehlender Daten an beliebigen Stellen zu und bei C++ konnte mehr schlecht als recht einfach noch eine Superklasse hinzugefügt und dort die fehlenden Funktionalitäten untergebracht werden. Statt der Mehrfachvererbung gibt es unter Java einen anderen Mechanismus, der fast genauso flexibel, jedoch nicht so gefährlich ist. Es wird ein Schnittstellenkonzept verfolgt. Klassen können nicht nur von einer Superklasse erben, sondern auch eine beliebige Anzahl an Schnittstellen implementieren. Java-Schnittstellen sind wie die IDL-(Interface Description Language)Schnittstellen – ein Schnittstellenstandard zum Informationsaustauch verschiedener Programmiersprachen – aufgebaut. Eine Schnittstelle ist in der Java-Sprache eine Sammlung von Methodennamen ohne konkrete Definition. Obwohl eine einzelne Java-Klasse nur genau eine Superklasse haben kann (und genau eine haben muss!), können in einer Klasse mehrere Schnittstellen implementiert werden.
5.10.4
Überladen, Überschreiben und Überschatten
Bei diesen drei Begriffen handelt es sich um Grundfunktionalitäten der OOP, die für das Verständnis von Java absolut grundlegend sind. ÜBERLADEN von Methoden bedeutet, dass in einer Klasse mehrere Methoden mit identischem Namen definiert werden, die sich nur durch verschiedene Parameterlisten unterscheiden. Welche Methode dann vom Programm bei Aufruf angesprochen wird, hängt von der dort verwendeten Anzahl und dem Typ der Parameter ab. Java erlaubt zwar ein solches Überladen von Methoden, jedoch kein Überladen von Operatoren. Die Theorie der Objektorientierung erlaubt ein Überladen von Operatoren im Prinzip analog, jedoch unterstützt Java diesen Mechanismus nicht. Obwohl wir später noch auf das Überladen von Methoden zurückkommen, soll das nachfolgende kleine Programmbeispiel zwei innerhalb einer Klasse definierte Methoden mit gleichem Namen zeigen. Beide unterscheiden sich nur durch die Anzahl der Parameter. Listing 5.3: Überladen von Methoden
170
public class MeineKlasse() { public int meinemethode(int arg1) { int berechnung; berechnung = arg1 + 1; return berechung; } public int meinemethode(int arg1; int arg2) {
Java 2 Kompendium
Die Objektorientierung von Java
Kapitel 5
int berechnung; berechnung = arg1 + arg2; return berechung; } }
Überladen kann sich auch über Superklassen erstrecken. Wenn sich die verschiedenen Methoden mit gleichem Namen und unterschiedlichen Parameterlisten in verschiedenen, über Vererbung verbundenen Klassen befinden, funktioniert die Geschichte ebenso. Dies liegt im Wesentlichen daran, dass der Aufruf einer Methode in der Subklasse mit der Parameterliste der Methode aus der Superklasse dort keinen Erfolg hat und die Methode dann in der nächst höheren Ebene (der Superklasse) gesucht wird. Methoden, die mit dem Schlüsselwort static deklariert werden (Klassenmethoden), können nicht überladen werden. Der Mechanismus des Überladens von Methoden sollte nicht mit dem bereits beschriebenen ÜBERSCHREIBEN (Overriding) verwechselt werden, obwohl der Vorgang sehr ähnlich ist. Auch Überschreiben ist in Java möglich und erluabt eine Spezialisierung von Objekten. Es bedeutet ebenfalls, dass Methoden einer Subklasse die Methoden der Elternklasse komplett überschreiben. Der Unterschied zu Überladen ist mehr formeller als inhaltlicher Natur. Eine Methode zu überschreiben bedeutet, dass die Methode in der Subklasse den identischen Deklarationskopf (also neben der Namensgleichheit auch vollkommen identische Argumente) wie die Methoden der Superklasse erhält (Überschreiben funktioniert also nur zwischen einer Super- und einer Subklasse), während beim Überladen der Methoden zwar die Namen in Super- und Subklasse, aber nicht die Argumente identisch sind. Auch darf beim Überschreiben der Rückgabewert einer Methode nicht geändert werden. Das nachfolgende kleine Beispiel soll den Vorgang skizzieren: public class Superklasse() { public int supermethode(int Superarg1) { int berechnung; berechnung = Superarg1 + 1; return berechnung; } } public class Subklasse() extends Superklasse { public int supermethode(int Superarg1) { int berechnung; berechnung = Superarg1 + 2; return berechnung; } }
Java 2 Kompendium
Listing 5.4: Überschreiben von Methoden
171
Kapitel 5
Java-Hintergründe Die verdeckten Methoden einer Superklasse sind übrigens trotzdem immer noch zu verwenden. In Java macht dies das Schlüsselwort super möglich, also z.B. mit super.supermethode(3);. Die letzte der drei Überlagerungstechniken in Java ist das ÜBERSCHATTEN. Überschattet werden Variablen. Wenn eine Variable in einer Superklasse mit einem bestimmten Typ deklariert wird, so kann in der Subklasse durchaus eine Variable gleichen Namens (mit einem identischen oder auch abweichenden Typ) deklariert werden, die dann dort die Variable der Superklasse überlagert. Das nachfolgende Beispiel skizziert das Verfahren: class Superklasse() { int berechnung; } class Subklasse extends Superklasse { double berechnung; }
Der Zugriff auf die Variable der Superklasse kann unter anderem wieder mit dem Schlüsselwort super erreicht werden.
5.10.5
Globale Vereinbarungen
Als letzte gravierende (positive) Einschränkung verfügt Java nicht über die Möglichkeit, globale Konstanten, Variablen oder Funktionen zu definieren, was jedoch bei strenger Auslegung der OO-Theorie zwingend ist. Bezüglich der Konstanten gilt die Aussage aber nicht ganz so streng, wie sie im ersten Moment erscheint. Über Schnittstellen kann man Konstanten so festlegen, dass sie insofern als global verfügbar gelten, als dass jede Klasse diese Schnittstelle implementiert.
5.11
Zusammenfassung
Java ist explizit objektorientiert, ohne jedoch sämtliche Möglichkeiten des objektorientierten Konzeptes zu realisieren (etwa Verzicht auf Mehrfachvererbung und Überladen von Operatoren). Dieses Fehlen von einzelnen Bestandteilen steht jedoch in keinerlei Widerspruch zu den objektorientierten Paradigmen.
172
Java 2 Kompendium
Plattformunabhängigkeit von Java und die JVM
5.12
Kapitel 5
Plattformunabhängigkeit von Java und die JVM
Java ist sowohl eine Programmiersprache als auch Bestandteil eines modernen EDV-Systems, dessen wichtigster Bestandteil ein neues, vollkommen plattformunabhängiges Betriebssystem für ein nahezu unbeschränktes Einsatzfeld ist. Statt plattformunabhängig wird gerne der Begriff architekturneutral genommen, was jedoch dasselbe bedeutet. Eine erheblich grö ß ere Fehlertoleranz, eine leichtere Bedienbarkeit und eine bedeutend bessere Stabilität als bei bisherigen Betriebssystemen sind Charakteristika der JavaUmgebung. Java-Applikationen können auf Rechnern unterschiedlichster Konfiguration laufen, sogar auf sämtlichen elektrischen Geräten, die irgendwelche Daten verarbeiten sollen. Denken Sie nur an Videorecorder, die bereits seit geraumer Zeit (mehr schlecht als recht) zu programmieren sind. Java erlaubt es, die Zahl und Funktionalität der Geräte auszudehnen. Vorstellbar sind Kaffeemaschinen, die von unterwegs aus dem Auto heraus per Internet gestartet werden, sodass der Kaffee in dem Moment fertig ist, wenn man zur Tür hereinkommt. Es gibt die verschiedensten Anwendungsmöglichkeiten für Java. Der Zukunftsmarkt ist auf Grund der Plattformunabhängigkeit gigantisch. Wie realisiert nun Java diese Plattformunabhängigkeit? Java ist sowohl auf der so genannten Quellebene, als auch der Binärebene plattformunabhängig. Beschäftigen wir uns zunächst mit der binären Plattformunabhängigkeit.
5.12.1
Die binäre Plattformunabhängigkeit von Java
Bevor eine Java-Anwendung oder ein Java-Applet ausgeführt werden kann, muss der Java-Quellcode in so genannten Bytecode kompiliert werden. Dies erledigt der Java-Compiler, wie wir bereits mehrfach gesehen und angewendet haben. Der daraus resultierende Bytecode ist binär, aber immer noch architekturunabhängig. Der Preis dafür ist, dass dieser Bytecode nicht vollständig und noch nicht lauffähig ist. Bytecode kann man sich als eine Reihe von binären Anweisungen vorstellen, die zwar wie Maschinencode aussieht, jedoch im Gegensatz dazu nicht spezifisch an den Befehlssatz eines bestimmten Prozessors gebunden ist. Diese konkreten Anweisungen fehlen noch. Das daraus resultierende Problem ist dann natürlich, dass Java-Bytecode keine expliziten Prozessorbefehle enthalten kann. Java-Bytecode wird deshalb gelegentlich als ein architekturneutrales Object-Codeformat bezeichnet. Schauen wir uns den Fall an, wenn beim Kompilieren von Quellcode bereits auf einem Prozessor lauffähiger Code entsteht. Beim Kompilieren eines in einer Sprache wie PASCAL oder C/C++ geschriebenen Quelltextes entsteht
Java 2 Kompendium
173
Kapitel 5
Java-Hintergründe ein solcher maschinenabhängiger Code, dessen Anweisungen vom jeweiligen Zielprozessor direkt interpretiert werden können. Den Unterschied zu Java-Bytecode kann man sich über die Mengenlehre verdeutlichen. Lauffähiger Maschinencode ist eine nicht deckungsgleiche Obermenge von Bytecode. Der noch fehlende Teil muss bei Bytecode also noch irgendwie hinzugefügt werden. Dies übernimmt die virtuelle Maschine. Da der Java-Bytecode wie gesagt noch nicht direkt den Befehlssatz eines Prozessors steuern kann, benötigt er einen prozessorabhängigen Container, eine Java-Laufzeitumgebung. Aus diesem Grund verfügt Java immer über einen Java-Interpreter. Der Bytecode ist innerhalb dieser Java-Laufzeitumgebung lauffähig und wird von dieser interpretiert. Erst hier werden die plattformabhängigen Befehle hinzugebunden. Die jeweilige Laufzeitumgebung ist plattformspezifisch und kennt den Befehlssatz des jeweiligen Prozessors, also arbeitet das endgültige Produkt auf dieser spezifischen Plattform. Java-fähige Browser sind beispielsweise solche plattformspezifischen Java-Laufzeitumgebungen, die erst die Verwendung von Java-Applets auf den unterschiedlichsten Plattformen des WWW ermöglichen.
5.12.2
Die JVM-Architektur
Die Interpretation des Java-Bytecodes basiert, wie bereits angedeutet, auf der Definition einer so genannten virtuellen Maschine (Java Virtual Machine, abgekürzt JVM), einem virtuellen Computer, der nur im Speicher eines Rechners zur Laufzeit resistent ist und der vom Bytecode gesteuert wird. Man nennt einen solchen Prozess oft eine Emulation. Die JVM wird gerne als Herz von Java gesehen und muss auf jedem Rechner vorhanden sein, der Java-Anwendungen ausführen möchte. Die virtuelle Maschine stellt eine Abstraktionsschicht zwischen dem kompilierten Programm, der zugrunde liegenden Hardwareplattform und dem Betriebssystem dar und realisiert die gerade beschriebene Umsetzung des Bytecodes in ausführbare Prozessorbefehle. Die virtuelle Maschine kümmert sich anderseits auch um grundlegende Java-Operationen wie die Objekterstellung und die Müllbeseitigung (Garbage Collection, sprich die Speicherfreigabe mit einer Papierkorbfunktion). Wir beziehen uns hier im Wesentlichen auf die JVM von Sun bzw. JavaSoft. Es gibt aber noch andere JVMs von weiteren Herstellern, die nicht unbedingt voll kompatibel sein müssen und sich vom Aufbau her unterscheiden können. Die wichtigsten Punkte werden jedoch übereinstimmen. Die JVM ist sehr kompakt und verbraucht wenig RAM-Speicherplatz. Sie soll ja in beliebige elektronische Geräte passen. Konkret läuft der Prozess 174
Java 2 Kompendium
Plattformunabhängigkeit von Java und die JVM
Kapitel 5
vom Java-Sourcecode zum lauffähigen und architekturneutralen Java-Programm wie folgt ab: 1.
Eine Java-Sourcedatei wird erstellt und in der Regel unter dem Namen ihrer öffentlichen Klasse mit der Erweiterung .java gespeichert.
2.
Der Java-Compiler liest die Dateien mit der Erweiterung .java ein und konvertiert den Java-Sourcecode in Bytecode. Die resultierende Bytecode-Datei bekommt den gleichen Namensstamm, jedoch die Erweiterung .class.
3.
Die JVM liest den Bytecode-Datenstrom aus der .class-Datei als Folge von Anweisungen (der so genannte Bytecode-Strom). Dieser Strom von Anweisungen hat immer die gleiche Struktur. Jede Anweisung beginnt mit einem opcode (Operationscode). Dieser ist ein Bit lang und ein spezifischer und wieder erkennbarer Befehl. Auf Grund der Länge kann er nur null (keine weiteren Operanden) oder ungleich null (mehrere Operanden, mit denen der opcode vervollständigt werden muss, folgen) sein. Der opcode instruiert die virtuelle Maschine, was genau zu tun ist. Wenn die JVM mehr als nur den opcode für die Ausführung einer Aktion benötigt, ist dieser wie gesagt ungleich null und es folgt dem opcode ein oder mehrere Operand(en).
Die JVM besteht aus vier Teilen: 1.
Stack
2.
Register
3.
Garbage Collection Heap
4.
Methodenbereich
Gelegentlich wird die JVM auch in fünf Basisteile untergliedert: 1.
Bytecode-Anweisungen
2.
Stack
3.
Register
4.
Heap
5.
Methodenbereich
Manche Quellen nehmen also die Bytecode-Anweisungen direkt in die Gliederung mit auf, was jedoch keinen wesentlichen Unterschied ausmacht. Schauen wir uns die Details genauer an. Zuerst soll der Java-Stack betrachtet werden. Die virtuelle Maschine basiert im Wesentlichen auf diesen Stacks. Unter einem Stack versteht man einen Befehlsstapel. Der Rahmen
Java 2 Kompendium
175
Kapitel 5
Java-Hintergründe eines Java-Stacks ist mit dem Stack in herkömmlichen Programmiersprachen durchaus vergleichbar. Er enthält den Zustand eines Methodenaufrufs und den Zustand von verschachtelten Methodenaufrufen. Da die JVM primär auf Stacks basiert, werden die Parameter in der virtuellen Maschine dort gespeichert. Das Weitergeben und Empfangen von Argumenten läuft in der JVM immer über den Stack. Ein Stack in Java dient also der Bereitstellung von Parametern für Bytecodes und Methoden zum Aufnehmen der Ereignisse. Die Grö ß e einer Adresse in der JVM beträgt immer 4 Byte, also 32 Bit. Deshalb können bis zu 4 Gigabyte Speicher adressiert werden (2 hoch 32, also 4.294.967.296 Bit). Die oben genannten Komponenten der JVM (der Stack, der Garbage Collection Heap und der Methodenbereich) befinden sich innerhalb dieser 4 Gigabyte. Die Java-Methoden sind auf 32 Kilobyte als Grö ß e für jede einzelne Methode beschränkt. Alle Prozessoren benötigen für ihre Funktionalität so genannte Register. Register beinhalten einen Zustand eines Computers, beeinflussen den Betrieb und werden nach jeder Ausführung von Befehlen aktualisiert. Die virtuelle Maschine von Java verwaltet den System-Stack mit den folgenden Registern: pc (Program Counter): Ein Programmzähler, der verfolgt, wo genau das Programm sich in der Ausführung befindet, d.h. welcher Bytecode gerade ausgeführt wird. Weiter enthält der Programmzähler die Adresse des Bytecodes, der als nächstes ausgeführt werden soll. Zusätzlich wird das pc-Register dafür verwendet, wenn Ausnahmen und catch-Klauseln abgearbeitet werden. optop: Ein Zeiger oder Pointer, der auf die Spitze des Operanden-Stack zeigt. Er dient im Wesentlichen zur Auswertung arithmetischer Ausdrücke. frame: Ein Pointer, der auf die aktuelle Ausführungsumgebung der gerade ausgeführten Methode zeigt. Dieser Pointer enthält einen Aktivierungsdatensatz für den Aufruf dieser Methode und diverse Debugging-Informationen. vars: Ein weiterer Pointer, der auf die erste lokale Variable der aktuell ausgeführten Methode zeigt. Weitere Register verwendet Java nicht. Die JVM gebraucht dementsprechend nur vier Register mit der Länge eines Bytes, simuliert also einen Prozessor mit einem 32-Bit-Register. Aus diesem Grund benötigen die primitiven Datentypen double und long durch ihre Datenbreite von 64 Bit jeweils zwei Positionen auf dem Operanden-Stack (jeweils 32 Bit groß) zur Darstellung.
176
Java 2 Kompendium
Plattformunabhängigkeit von Java und die JVM
Kapitel 5
Von dem Java-Programm wird der Bytecode an die JVM weitergegeben und erzeugt einen Stack-Frame für jede Methode. Jeder Frame enthält drei Arten von Informationen – die lokalen Variablen für den Methodenaufruf, die Ausführungsumgebung und den Operanden. Diese jeweiligen Datenmengen können unter Umständen auch leer sein. Die Grö ß e der ersten beiden Elemente steht zu Beginn eines Methodenaufrufs fest, während die Grö ß e des Operanden bei der Ausführung des Bytecodes der Methode variieren kann. Die lokalen Variablen: Sie werden in einem Array von 32-Bit-Variablen gespeichert, auf die von dem vars-Register gezeigt wird (im Word-Offset). Die Ausführungsumgebung: In der Ausführungsumgebung (Execution Environment) eines Stacks wird die Methode ausgeführt. Das frameRegister zeigt auf die Ausführungsumgebung. In der Ausführungsumgebung ist ein Pointer auf den vorherigen Stack, ein Pointer auf die lokale Variable des aktuellen Methodenaufrufs und je ein Pointer auf das momentane obere und untere Ende des Stacks im Speicher enthalten. Diese Informationen können neben einigen weiteren Hinweisen gut zum Debugging genutzt werden. Besonders für das dynamische Linken ist die Ausführungsumgebung wichtig, denn erst durch die Referenzen auf die Symboltabellen des Interpreters für die aktuelle Methode und die aktuelle Klasse wird dieses erst ermöglicht. Der Operanden-Stack: Er arbeitet nach dem FIFO-(First-in, First-out)Prinzip und ist 32 Bit groß. Verwendet wird er zum Speichern der Parameter und Rückgabewerte der meisten Bytecode-Anweisungen und er enthält noch weitere Argumente, die für den opcode notwendig sind. Jeder primitive Java-Datentyp hat eindeutige Anweisungen, wie er sich beim Herausziehen, Verwenden und Zurückschieben der Operanden des jeweiligen Typs zu verhalten hat. Die Typen in einem Stack und deren Anweisungen müssen kompatibel sein, was unter Java natürlich sicher gestellt ist. Die Spitze des Operanden-Stack wird von dem optopRegister indexiert und ist in der Regel mit der Spitze des gesamten JavaStacks identisch. Der Garbage Collection Heap oder auch nur Heap (Haufen) ist derjenige Teil des Hauptspeichers, dem neu erstellte Klasseninstanzen (Objekte) zugewiesen werden. Jedesmal wenn ein neues Objekt erstellt wird, kommt dieser Speicher aus dem Heap. In Java hat der Heap meist eine feste, voreingestellte Grö ß e, sobald das Java-Laufzeitsystem gestartet wird. Bei Computersystemen, wo die Technik des virtuellen Speichers (also Auslagerung von Hauptspeicher auf die Festplatte bei Bedarf) unterstützt wird (etwa Windows), kann die Grö ß e des Heap entsprechend ausgedehnt werden. Die Freigabe des Heap, wenn ein Objekt nicht mehr benötigt wird, übernimmt ein automatischer Prozess, der Garbage Collection (automatische Speicherbereinigung) genannt wird.
Java 2 Kompendium
177
Kapitel 5
Java-Hintergründe Diese Speicherbereinigung mit der Garbage Collection erleichtert die Arbeit mit Objekten gegenüber vielen anderen objektorientierten Sprache erheblich. Da in Java Objekte einer automatischen Müllbereinigung unterliegen, braucht sich ein Programmierer normalerweise nicht darum zu kümmern und den Speicher nicht mehr – wie etwa unter C/C++ oder Delphi – manuell freigeben. Eine manuelle Speicherfreigabe ist unter Java sogar gar nicht vorgesehen, Sie können höchstens den Garbage-Collection-Prozess direkt aufrufen. Jedoch ist dies in den meisten Fällen nicht notwendig und auch nicht zu empfehlen. Die Laufzeitumgebung verfolgt alle Referenzen auf ein Objekt in dem Heap und gibt automatisch den Speicher frei, der von Objekten belegt wird, die nicht mehr länger referenziert werden, sobald die CPU dafür Kapazität frei hat. Auf Java-Objekte wird zur Laufzeit nur indirekt über so genannte Handles – eine Art Pointer auf den Heap – zugegriffen. Deshalb können Bereinigungsprozesse parallel ablaufen. Diese laufen als eigener Thread ab und können nach eigenem Gutdünken (der JVM) Objekte löschen oder verschieben. Speicherbereinigungen laufen also in der Regel als ein permanenter Hintergrundthread ab. Die JVM verfügt neben den bereits diskutierten Speicherbereichen über zwei weitere Speicherbereiche, die als Methodenbereich von Java bezeichnet werden: Der eigentliche Methodenbereich Der Constant Pool (Konstantenpool) Wie bei konventionell kompilierten Programmen speichert der eigentliche Methodenbereich bei Java die Java-Bytecodes. Zusätzlich werden dort die zum dynamischen Verknüpfen benötigten Symboltabellen und andere Informationen (etwa Debug-Informationen) gespeichert. Der Constant Pool existiert in Java als ein an jede Klasse angehängter Heap. Diese normalerweise vom Java-Compiler erzeugten Konstanten codieren alle Namen (von Methoden, Variablen usw.), die von einer Methode der jeweiligen Klasse verwendet werden. Die Grö ß e des Constant Pool differiert je nach Anzahl der zu kodierenden Namen für jede Klasse. Die Klasse »weiß « um die Menge der Konstanten und spezifiziert mit einem Symbol, wo in der Klassenbeschreibung das Konstanten-Array beginnt. Die Konstanten werden mit speziell codierten Bytes typisiert und haben innerhalb der .class-Datei ein genau definiertes Format. Es gibt in Java keine Begrenzung, ab wo und bis wohin diese beiden Speicherbereiche existieren müssen. Damit wird die JVM portabler und sicherer.
178
Java 2 Kompendium
Plattformunabhängigkeit von Java und die JVM
5.12.3
Kapitel 5
Die Datentypen der JVM
Java-Datentypen unterscheiden sich in einigen Details von gewohnten Datentypen aus vielen anderen Programmiersprachen. Im Wesentlichen unterscheiden sie sich in der Länge (besonders auffällig ist der char-Typ, der nicht wie sonst meist üblich aus einem Byte besteht), aber auch in ein paar weiteren Einzelheiten. Die JVM verarbeitet im Detail die folgenden Typen primitiver Daten: Typ
Länge
Kurzbeschreibung
byte
8 Bit
Kleinster Wertebereich mit Vorzeichen. Wird zum Darstellen von Ganzzahlwerten von (- 2 hoch 7 = -128) bis (+ 2 hoch 7 – 1 = 127) verwendet.
short
16 Bit
Wertebereich mit Vorzeichen. Kurze Darstellung von Ganzzahlwerten von (- 2 hoch 15 = – 32768) bis (+ 2 hoch 15 – 1 = 32767).
int
32 Bit
Standardwertebereich mit Vorzeichen zur Darstellung von Ganzzahlwerten. Bereich von (- 2 hoch 31 = – 2147483648) bis (+ 2 hoch 31 – 1 = 2147483647).
long
64 Bit
Größter Wertebereich mit Vorzeichen zur Darstellung von Ganzzahlwerten. Bereich von (- 2 hoch 63 = – 9,223372036855e+18) bis (+ 2 hoch 63 – 1 = 9,223372036855e+18 – 1).
float
32 Bit
Kürzester Wertebereich mit Vorzeichen zur Darstellung von Gleitkommazahlwerten. Es existiert ein Literal zur Darstellung von plus/minus unendlich, sowie der Wert NaN (Not a Number) zur Darstellung von nicht definierten Ergebnissen.
double
64 Bit
Größter Wertebereich mit Vorzeichen zur Darstellung von Gleitkommazahlwerten. 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.
char
16 Bit
Vorzeichenlose Darstellung eines Zeichens des UnicodeZeichensatzes.
boolean
1 Bit
Kann nur die Werte true (wahr) oder false (falsch) annehmen.
Tabelle 5.2: Primitive Datentypen der JVM von Sun
Falls Sie bereits Kenntnisse in anderen Programmiersprachen haben, fällt Ihnen vielleicht auf, dass in der Liste der primitiven Datentypen keine Arrays vorhanden sind. Dies liegt daran, dass Arrays in Java als Objekte gesehen werden und keine eigene Darstellung besitzen. Dies ist übrigens ein nicht zu unterschätzender Sicherheitsaspekt von Java.
Java 2 Kompendium
179
Kapitel 5
Java-Hintergründe Es gibt – wie schon angedeutet – virtuelle Maschinen, die nicht von Sun stammen (etwa von Microsoft). Einige dieser anderen virtuellen Maschinen besitzen noch zwei weitere Datentypen:
Tabelle 5.3: Optionale primitive Datentypen einiger JVM
Typ
Länge
Kurzbeschreibung
object
32 Bit
Referenz auf eine Java-Instanz.
returnAddress
32 Bit
Wert mit Anweisungen für die Befehle jsr/ret/jsr_w/ret_w.
5.12.4
Die Java-Plattformunabhängigkeit auf Quellebene
Plattformunabhängigkeit auf Quellebene bedeutet, dass die primitiven Datentypen einer Programmiersprache konsistente Grö ß en auf allen Entwicklungsplattformen haben. Wenn ein primitiver Datentyp wie beispielsweise ein Integer-Wert unter der einen Plattform die Grö ß e von einem Byte hätte, unter einer anderen Plattform die Grö ß e von zwei Byte, wäre die Plattformunabhängigkeit auf Quellebene nicht mehr gegeben. Bei fast allen anderen Programmiersprachen gibt es diese Probleme. Java hingegen enthält umfangreiche Klassenbibliotheken, um Standards für Datentypen festzulegen und damit das Schreiben von Code für verschiedene Plattformen überflüssig zu machen. In Java wird die Grö ß e jedes einfachen Datentyps in den umfangreichen Klassenbibliotheken genau spezifiziert. Genauso wichtig ist jedoch auch die Arithmetik, d.h. wie sich diese einfachen Datentypen gegenüber Berechnungsvorschriften verhalten. Auch hier legt Java in den Klassenbibliotheken genaue Vorschriften fest. Fast alle wichtigen Prozessoren unterstützen diese Java-Spezifikation und außerdem enthalten die Java-Bibliotheken portierbare Schnittstellen für die wichtigsten Plattformen in der Computerwelt. Damit wird – bis auf ganz wenige Ausnahmen – eine nahezu perfekte Plattformunabhängigkeit auf Quellebene gewährleistet.
5.13
Zusammenfassung
In diesem Kapitel haben wir für das Verständnis von Java unabdingbare Hintergründe besprochen, insbesondere das Konzept der objektorientierten Programmierung. Objektorientierung hebt die Trennung von Daten und Anweisungen auf. Dabei tauschen Objekte Nachrichten aus und reagieren darauf. Klassen und daraus erzeugte Objekte erben Fähigkeiten und Eigenschaften der zugehörigen Superklasse, und Subklassen können geerbte Fähigkeiten und Eigenschaften erweitern und verändern. Dabei erhalten die Objekte ihre Fähigkeiten und Eigenschaften durch Zugehörigkeit zu der
180
Java 2 Kompendium
Zusammenfassung
Kapitel 5
Klasse, aus der sie als Instanz erstellt werden. Wenn Objekte zu verschiedenen Klassen gehören, werden sie auf die gleiche Nachricht unterschiedlich reagieren (Polymorphismus). Grundsätzlich ist Java eine äußerst strenge objektorientierte Sprache, schränkt aber das denkbare Maximum in den Bereichen ein, wo es sinnvoll ist.
Java 2 Kompendium
181
Java – die Hauptbestandteile der Sprache
6
Wir haben nun viele Vorbereitungen getroffen und wollen zu den wichtigsten Bestandteilen von Java selbst kommen. Dabei sollen in diesem umfangreichen Kapitel – es wird unser dickster Brocken in dem Buch – die folgenden Themenschwerpunkte behandelt werden: Token Typen Ausdrücke Anweisungen Klassen Schnittstellen Pakete Dieses Kapitel wird nicht nur umfangreich, sondern leider auch recht theoretisch. Dies lässt sich nicht ganz vermeiden. Da müssen Sie durch. Dies ist aber sicher nicht Java-spezifisch, sondern trifft auf jede Programmiersprache zu. Damit das Kapitel aber nicht ausschließlich Theorie beinhaltet, werden wir einige Beispiele angehen. Diese Beispiele verwenden am Anfang gelegentlich Java-Techniken, die Ihnen unter Umständen noch nicht vertraut sind oder bis zu der jeweiligen Stelle noch nicht explizit eingeführt wurden. Dies betrifft beispielsweise Schleifen, die eine Wiederholung von Programmschritten erlauben. Wir werden etwa die for-Schleife an mehreren Stellen benutzen. Es ist bei diesen Beispielen jedoch nicht unbedingt erforderlich, dass Sie die Syntax zu diesem Zeitpunkt schon vollständig verstehen. Die Verwendung der etwas weitergehenden Java-Techniken dient nur dazu, die Beispiele interessanter zu gestalten und das eigentliche Ziel des Beispiels in einen passenden Kontext zu setzen.
6.1
Token
Token bedeutet übersetzt Zeichen oder Merkmal. Wenn ein Compiler eine lesbare Datei in Maschinenanweisungen übersetzt, muss er zunächst herausfinden, welche Token oder Symbole im Code dargestellt sind. Ein Token kann man als Sinnzusammenhang verstehen. So ist etwa in der menschliJava 2 Kompendium
183
Kapitel 6
Java – die Hauptbestandteile der Sprache chen Sprache ein Wort nicht nur die Summe seiner Buchstaben oder Zeichen, sondern besitzt einen konkreten Sinnzusammenhang, den das interpretierende System (der menschliche Geist) mit einer bestimmten Bedeutung assoziiert. Allerdings muss das interpretierende System auch die Sprache verstehen, sonst bleibt ein Token einfach nur die Summe seiner Buchstaben oder Zeichen. Beispiel: Jeder gute Hesse wird aus der Summe der Zeichen B,e,m,b,e,l sofort den Sinn erfassen und mit dem Token Bembel einen Krug für Apfelwein (und vielleicht laue Sommernächte in einer Äpplerkneipe und den nachfolgenden Kater am nächsten Morgen) assoziieren, während die Summe der Zeichen B,e,m,b,e,l für die meisten Norddeutschen wahrscheinlich einfach nur eine bedeutungslose Aneinanderreihung von Zeichen bleibt. Wenn von dem Java-Compiler der Quellcode kompiliert werden soll, muss er ihn dabei in einzelne kleine Bestandteile (Token) zerlegen. Quelltext muss sich dabei in logisch sinnvolle Einheiten zerlegen und in gültige Arten von Token einordnen lassen. Die Sprachelemente werden auf ihre Richtigkeit geprüft. Außerdem werden Leerzeichen und Kommentare aus dem Text entfernt. Dieser Teil der Übersetzung eines Quelltextes zum Bytecode wird von dem so genannten Parser übernommen. An der Stelle des Parsers greift auch die erste Kontrolleinheit des Sicherheits- und Stabilitätskonzepts von Java. Wenn in einem Quelltext einem Token Ganzzahl (ein Variablenbezeichner) der Datentyp short zugeordnet ist (eine 16-Bit-Zahl), dann erkennt der Compiler den Token jedes Mal, wenn es in diesem Zusammenhang benutzt wird, und benutzt die spezifisch zugeordneten 16 Bits des Speichers. Alle Operationen mit Ganzzahl werden mit dem spezifischen Wert, der in dieser 16-Bit-Adresse enthalten ist, durchgeführt. Also nicht mit den Buchstaben des Tokens selbst, sondern mit dem, was der Compiler mit dem Token »assoziiert«. Es gibt in Java fünf Arten von Token:
184
1.
Bezeichner oder Identifier
2.
Schlüsselworte
3.
Literale
4.
Operatoren
5.
Trennzeichen
Java 2 Kompendium
Token
Kapitel 6
Diese Einteilung in fünf Tokenarten gibt es übrigens für die meisten anderen Computersprachen analog. Kommentare oder Leerraum (Leerzeichen, Tabulatoren und Zeilenvorschübe) sind in der Aufzählung nicht enthalten, aber sie existieren natürlich ebenfalls, werden sozusagen als selbstverständlich vorausgesetzt. Technisch gesehen sind sie sogar eigentlich keine Token (wie man beispielsweise daran erkennen kann, dass sie vom Compiler entfernt werden).
6.1.1
Der Unicode-Zeichensatz
Java benutzt auf Quelltextebene keinen 8-Bit-ASCII-Code, sondern den 16-Bit-Unicode-Zeichensatz. Damit können zusätzlichen Zeichen kodiert werden, die nicht im lateinischen/englischen Alphabet enthalten sind. Zeichenketten nehmen zwar doppelt so viel Platz ein, jedoch wird die Internationalisierung leichter. Es gibt dort sogar ein Zeichen für den Euro1. Die Unicode-Spezifikation ist ein zweibändiger Listensatz mit zig Tausenden von Zeichen. Sie werden sicher einsehen, dass ein Auflisten der Zeichen zwar das Buch füllen, Ihnen jedoch nicht viel nützen würde. Da jedoch die ersten 256 Zeichen dem normalen ASCII-Zeichensatz entsprechen (Byte 1 ist auf 0 gesetzt), brauchen Sie sowieso normalerweise darauf kaum Rücksicht zu nehmen. Sie können einfach wie bisher die Zahlen und Buchstaben für Variablen-, Methoden- oder Klassennamen verwenden. Eine ASCIICodierung wird einfach durch eine kanonische Übersetzung (d.h. die Reihenfolge und Anordnung der ASCII-Codierung ist auch in der neuen Codierung als Block wiederzufinden) mit Voranstellen der Zeichenfolge \u00 und folgender Hexadezimalzahl in das dazu passende Unicode-Zeichen übersetzt. Dies definiert in Java eine Escape-Sequenz, mit der alle Unicode-Zeichen verschlüsselt werden können. Sobald die Unicode-Sequenz beendet ist, werden die folgenden Zeichen nahtlos angefügt. Durch die mögliche Unicode-Escape-Darstellung von Zeichen im Quelltext ist es in Java im Gegensatz zu anderen Programmiersprachen erlaubt, Umlaute und andere Sonderzeichen in Bezeichnern zu verwenden. Dabei kann die Angabe dieser Sonderzeichen (und natürlich auch gewöhnlicher Zeichen) sowohl direkt als auch über Angabe der Escape-Sequenz erfolgen. Alle Literale (d.h. alle vorkommenden Zeichen wie Buchstaben, Zahlen, Sonderzeichen usw.) in Java bestehen aus Unicode. Das JDK-Tool native2unicode übersetzt bei Bedarf 8-Bit-Native-Code in Unicode. Der Java-Compiler erwartet auf jeden Fall Unicode, jedoch müssen Sie sich bei der Arbeit mit einem gewöhnlichen ASCII-Editor darum meistens nicht kümmern. Vor der Kompilierung wird der Quelltext vom Compiler als Ers1
Bemerkenswert, weil ihn doch niemand außer Politikern will ;-).
Java 2 Kompendium
185
Kapitel 6
Java – die Hauptbestandteile der Sprache tes automatisch in eine Folge von Unicode-Zeichen transformiert. Erst nach der Transformation erfolgt dann die eigentliche Kompilierung. Zwei Bezeichner gelten in Java dann und nur dann als identisch, wenn ihre Unicode-Darstellung übereinstimmend ist. Dies ist auch der Grund, warum in Java unbedingt zwischen Groß- und Kleinschreibung zu unterscheiden ist. Die beiden folgenden Bezeichner sind also identisch: float Übung; float \u00DCbung;
Wenn Sie nun der vollständige Unicode-Zeichensatz doch interessiert, so nutzen wir Java und schreiben ein kleines Programm, das diesen – zumindest teilweise – ausgibt. Auch hier werden wir noch nicht zu detailliert auf die Syntax eingehen, aber doch schon ein wenig erläutern, was in dem Programm passiert. Wir verwenden hier zum ersten Mal eine Schleife. Für Sie sollte an der Stelle nur wichtig sein, dass sie die Anweisungen in dem zugeordneten Block so oft wiederholt, wie es in der Schleife angegeben ist. Geben Sie den nachfolgenden Quelltext ein.
Listing 6.1: Ausgabe von Unicodezeichen
/* Beispielprogramm zur Bildschirmausgabe vom ASCII- bzw. Unicode-Zeichensatz */ class Unicode { public static void main (String args[]) { int i; for(i=0; i < 1500;i++) System.out.print((char)i); } }
Speichern und kompilieren Sie das Programm.
Starten Sie das Programm. Wenn Sie das Programm eingeben, speichern (unter dem Namen Unicode.java), kompilieren und dann mittels des java-Interpreters ausführen, bekommen Sie auf dem Standardausgabegerät (meist der Bildschirm) die bekannten Zeichen des ASCII-Codes und dann einen Teilbereich des Unicode-Zeichensatzes ausgegeben (viele, viele Fragezeichen, d.h. noch nicht definiert).
186
Java 2 Kompendium
Token
Kapitel 6 Abbildung 6.1: Zeichen des Unicode-Zeichensatzes
Das Programm hat eine ähnliche Struktur wie unser HelloJava-Programm. Es besteht nur aus einer Methode, der main()-Methode, die jedes eigenständige Java-Programm benötigt. Sie erinnern sich vielleicht, dass jede Klasse, die in dem Aufruf durch den Klassennamen spezifiziert wird, eine Methode namens main() (public static void main(String args[])) benötigt. Der Interpreter java endet nach der vollständigen Abarbeitung von main(). Innerhalb der main()-Methode finden Sie wie bei unserem HelloJava-Programm eine Methode zur Ausgabe auf dem Standardausgabegerät – System.out.print(). Der Unterschied zu System.out.println() ist der, dass in hier kein Zeilenvorschub nach der Ausgabe jedes Zeichens erfolgt. Eine weitere Änderung innerhalb der Methode System.out.print() ist offensichtlich. Es wird kein Text in Hochkommata eingeschlossen ausgegeben, sondern mit (char)i eine Variable. Dabei ist i die vorher mit int i; eingeführte Zählvariable einer Schleife (der for-Schleife), die von 0 bis kleiner 1500 zählt. Würden wir nun nur diese Zählvariable innerhalb der Methode System.out.print() notieren, würde auf dem Standardausgabegerät eine Zahlenkolonne von 0 bis 1499 ausgegeben. Da wir das nicht wollten, muss der numerische Wert in das zugehörige Zeichen des Unicodes überführt werden. Man nennt diesen Vorgang Casting. Das in Klammern vorangestellte (char) verwandelt die Zählvariable für die Ausgabe entsprechend. Sie können so etwas für alle primitiven Datentypen in Java machen (Achtung – boolean Typen sind eine Besonderheit). Dazu folgt gleich mehr.
Java 2 Kompendium
187
Kapitel 6
Java – die Hauptbestandteile der Sprache
6.1.2
Die UTF-8-Codierung
Mit UTF-8-Codierung wird ein Verfahren bezeichnet, das eine Folge von den 16 Bit langen Unicode-Zeichen zur effizienteren Speicherung in eine Datei codiert, indem die Unicode-Zeichen in Abhängigkeit von ihrem Wert in einem, zwei oder drei Byte verschlüsselt werden. Das Verfahren ist dem Huffman-Algorithmus (siehe Anhang) bei allgemeinen Komprimierungsverfahren nicht ganz unähnlich. Jedoch besteht hier nicht das Problem, zuerst die Häufigkeit einzelner Zeichen bestimmen zu müssen. Hier setzt man einfach voraus, dass die Zeichen des ASCII-Zeichensatzes (\u0001 – \u007F) besonders häufig vorkommen (und natürlich das erste Byte sowieso auf 0 gesetzt ist) und deshalb eine besonders kurze Darstellung in einem Byte benötigen. Die Zeichen von \u0080 bis \u07FF werden in zwei Byte und die Zeichen von \u0800 bis \uFFFF werden in drei Byte verschlüsselt. In Java stellen unter anderem die Klassen DataOutputStream und DataInputStream Methoden zum Speichern und Lesen von Zeichen im UTF8-Code zur Verfügung. Wir werden diese im Kapitel über Ein- und Ausgabe in Java näher behandeln.
6.1.3
Kleiner Vorgriff auf die Datentypen von Java
Wir werden etwas weiter hinten die Datentypen von Java im Detail beleuchten, aber da wir bereits bei den Token mehrfach auf die verschiedenen Datentypen zu sprechen kommen, soll ein kleiner Vorgriff für Klarheit sorgen. Java besitzt acht primitive Datentypen: 1.
Vier Ganzzahltypen mit unterschiedlichen Wertebereichen (byte, int, short, long)
188
2.
Einen logischen Datentyp (boolean)
3.
Zwei Gleitzahltypen mit unterschiedlichen Wertebereichen (float, double)
4.
Einen Zeichentyp (char)
Java 2 Kompendium
Token
6.1.4
Kapitel 6
Die Java-Token-Details
Java unterscheidet seine Token nach bestimmten Kriterien: Token
Beschreibung
Beispiele
Schlüsselworte
Alle Worte, die ein essenzieller Teil der Java-Sprachdefinition sind.
public, class, static, void, String, else, if, synchronized, this, while
Bezeichner oder Identifier
HelloWorld, main, args, Namen für Klassen, Objekte, System, out, println, j, Variablen, Konstanten, Bezeichnungsfelder, Methoden n32e20 usw., zusammengesetzt aus alphanumerischen UnicodeZeichen. An der ersten Stelle eines Bezeichners darf keine Zahl stehen.
Literale
Mit einem Literal können Vari- "HelloJava", 42, false, true ablen und Konstanten bestimmte Werte zugewiesen werden. Dies können sämtliche in der Java-Sprachdefinition erlaubten Arten von Werten sein (numerische Werte, boolesche Werte, Buchstaben, Zeichenketten).
Trennzeichen
Unter Trennzeichen versteht man alle Symbole, die dazu benutzt werden, Trennungen und Zusammenfassungen von Code anzuzeigen.
Operatoren
Operatoren sind Zeichen oder +, –, *, /, , >>>, <<< Zeichenkombinationen, die eine auszuführende Operation mit einer oder mehreren Variablen oder Konstanten angibt. Es gibt diverse Typen von Operatoren.
Java 2 Kompendium
Tabelle 6.1: Die Token-Typen
( ) } [ ] ; , .
189
Kapitel 6 Tabelle 6.1: Die Token-Typen (Forts.)
Java – die Hauptbestandteile der Sprache
Token
Beschreibung
Beispiele
Leerräume
[Space], [Tab], Zeichen, die in beliebiger [Zeilenende], Anzahl und an jedem Ort zwischen allen Token mit Funktion [Formularvorschub] platziert werden können und keinerlei andere Bedeutung haben, als den Quellcode übersichtlich zu gestalten.
Kommentare
Ein Kommentar dient zur übersichtlichen Gestaltung des Quellcodes und dazu, dass man bei späterer Kontrolle überhaupt noch etwas versteht. Der Compiler ignoriert Kommentare. Eine Besonderheit ist der javadoc-Kommentar, der vom Java-DokumentationsTool ausgewertet wird.
// Kommentar bis zum nächsten Zeilenende /*Eingebetteter Kommentar*/ /** javadoc-Kommentar */
Wir werden nun die Token im Detail beleuchten. Schlüsselworte Unter Schlüsselworten versteht man in Java alle Buchstabenfolgen, die ein wesentlicher Teil der Java-Sprachdefinition sind und die in Java eine besondere Bedeutung haben. Es gibt zusätzlich einige Token, die vorsorglich für spätere Versionen von Java reserviert wurden (byvalue, cast, const, future, generic, goto, inner, operator, outer, rest und var). Dass dies Sinn machen kann, zeigt das Schlüsselwort transient, das in der Version 1.0 noch ohne Bedeutung war. Mittlerweile hat es in Java einen wohldefinierten Wert. Die meisten dieser derzeit nicht verwendeten, jedoch vorsorglich reservierten Schlüsselworte werden Programmierern, die von C/C++ oder PASCAL kommen, bekannt vorkommen. Es folgt eine alphabetische Tabelle mit den Java-Schlüsselworten (ohne nähere Erläuterung, die folgt später). Tabelle 6.2: Die Java-Schlüsselworte
190
abstract
boolean
break
byte
case
cast
catch
char
class
const
continue
default
do
double
else
extends
false
final
finally
float
Java 2 Kompendium
Token
Kapitel 6
for
future
generic
goto
if
implements
import
inner
instanceof
int
interface
long
native
new
null
operator
outer
package
private
protected
public
rest
return
short
static
super
switch
synchronized
this
throw
throws
transient
true
try
var
void
volatile
while
Tabelle 6.2: Die Java-Schlüsselworte (Forts.)
Die Bedeutungen der einzelnen Schlüsselworte sollen wie bereits angedeutet an anderen Stellen erläutert werden, aber einige Anmerkungen sind jetzt bereits notwendig: Es wird ein Schlüsselwort auffallen, das in grauer Basic-Urzeit für viel Freude sorgte: goto. Es ist in Java zwar reserviert, indes ohne Bedeutung. Es besteht sogar berechtigte Hoffnung, dass es für immer ohne Bedeutung bleibt. Denkbar ist sogar, dass es nur deshalb als Schlüsselwort ohne Bedeutung reserviert wurde, damit es niemals mehr in einem Java-Quelltext auftaucht. Schlüsselworte haben in Java eine spezifische Bedeutung, können also nicht als Bezeichner für irgendetwas anderes wie beispielsweise Variablen, Konstanten, Klassennamen usw. benutzt werden. Beachten Sie jedoch, dass für Schlüsselworte (wie auch sonst) Groß- und Kleinschreibung zu unterscheiden ist. Ein Schlüsselwort Var gibt es beispielsweise nicht, und Sie könnten es z.B. im Extremfall als Name für eine Variablen nehmen. Davon ist allerdings dringend abzuraten. Java wird damit keine Probleme haben, aber Personen, die den Quelltext lesen und warten sollen. Sie können jedoch Schlüsselworte als Teil eines längeren Tokens verwenden, um damit die Bedeutung deutlich zu machen. Die Token true und false sind rein technisch betrachtet nur Werte für boolesche Variablen und Konstanten. Daher sollte man davon Abstand nehmen, sie als Identifier zu verwenden (benutzerdefinierte Namen und Beschriftungen).
Java 2 Kompendium
191
Kapitel 6
Java – die Hauptbestandteile der Sprache Java besitzt diverse Standardpakete. Dort sind viele Elemente enthalten, deren Namen zwar keine Schlüsselworte sind, die aber bei anderweitiger Verwendung die Lesbarkeit des Quelltextes gewaltig reduzieren (was in der Tat denkbar ist, wenn man die jeweiligen Pakete nicht importiert). Zu Paketen siehe Seite 331. Bezeichner und Namenskonventionen Unter einem Bezeichner oder Identifier versteht man in Java ein Wort, das durch Zuordnung zu einer Variablen, Konstanten, Klasse, Objekt, Beschriftung oder Methode für Java zu einem Token wird. Es gibt in Java – wie in allen Programmiersprachen – einige Regeln zur Namenvergabe bei Bezeichnern, die Sie zwingend einhalten müssen: 1.
Wie schon vorher erwähnt, dürfen Bezeichner nicht mit Java-Schlüsselworten und sollten nicht mit Namen von Java-Paketen identisch sein.
2.
Bezeichner sind in Java Zeichenketten, bestehend aus Unicode-Buchstaben und Zahlen, die im Prinzip eine unbeschränkte Länge haben dürfen (bis auf technische Einschränkungen durch das Computersystem).
3.
Das erste Zeichen eines Bezeichners muss ein Buchstabe, der Unterstrich (_) oder das Dollarzeichen ($) sein. Alle folgenden Zeichen müssen entweder Buchstaben oder Zahlen sein. Es müssen jedoch nicht unbedingt lateinische Buchstaben oder Zahlen sein. Sie können jedes Alphabet, das von Unicode unterstützt wird, benutzen.
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 Tokens identisch ist, d.h. den vollkommen identischen Unicode-Wert hat. Java unterscheidet deshalb Groß- und Kleinschreibung. Die beiden Token Ganzzahl und ganzzahl sind nicht identisch. Tabelle 6.3: Beispiele für gültige und ungültige Bezeichner
Bezeichner
gültig
ungültig
Begründung, falls ungültig
HelloJava
x x
Der Bezeichner beginnt mit einer Zahl.
Rock&Roll
x
& ist kein Zeichen eines Alphabets.
Rock und Roll
x
Leerzeichen sind verboten.
x
Der Bezeichner beginnt mit einer Zahl.
42HelloJava safetyfirst
Rock_Roll 42
192
x
x
Java 2 Kompendium
Token
Kapitel 6
Bezeichner
gültig
_42
x
a-b
ungültig
Begründung, falls ungültig
x
Das Minuszeichen ist kein Zeichen eines Alphabets. Java würde zwei verschiedene Bezeichner interpretieren, mit denen eine Operation durchgeführt werden soll.
Tabelle 6.3: Beispiele für gültige und ungültige Bezeichner (Forts.)
Neben den zwingenden Namenskonventionen gibt es einige Regeln, die man bei der Vergabe von Namen einhalten sollte. Es hat sich eingebürgert, diese Namenskonventionen in Java einzuhalten, was zwar nicht zwingend, aber für eine Lesbarkeit des Quelltexts auf Grund allgemeiner Bekanntheit sinnvoll ist: 1.
Man sollte möglichst sprechende Bezeichner zu verwenden. Ausnahmen sind Schleifen, wo meistens nur ein Buchstabe als Zählvariable (normalerweise i oder j) verwendet wird.
2.
Konstanten (Elemente mit dem Modifier final) sollten vollständig groß geschrieben werden.
3.
Die Identifier von Klassen sollten mit einem Großbuchstaben beginnen und anschließend klein geschrieben werden. Wenn sich ein Bezeichner aus mehreren Worten zusammensetzt, dürfen diese nicht getrennt werden. Die jeweiligen Anfangsbuchstaben werden jedoch innerhalb des Gesamtbezeichners jeweils groß geschrieben.
4.
Die Identifier von Variablen, Methoden und Elementen beginnen mit Kleinbuchstaben und werden auch anschließend klein geschrieben. Wenn sich ein Bezeichner aus mehreren Worten zusammensetzt, dürfen diese nicht getrennt werden. Die jeweiligen Anfangsbuchstaben von den folgenden Worten werden jedoch innerhalb des Gesamtbezeichners jeweils groß geschrieben.
Es folgen einige unübliche (keine falschen) Bezeichner. Da es bei Bezeichnern von der Art des Elements abhängt, ob der gewählte Bezeichner den gängigen Namenskonventionen entspricht, ist in der Tabelle die Art des Elements aufgeführt.
Java 2 Kompendium
193
Kapitel 6 Tabelle 6.4: Beispiele für einige unübliche Bezeichner
Java – die Hauptbestandteile der Sprache
Art
unüblicher Bezeichner
Begründung
Klasse
Hellojava
Das zweite Wort des Bezeichners sollte groß geschrieben werden.
Klasse
hellojava
Der Bezeichner enthält gleich zwei Verstö ß e gegen die guten Sitten. Der erste Buchstabe des Bezeichners und der des zweiten Worts innerhalb des Bezeichners sollten groß geschrieben werden.
Klasse
HELloJava
Zu viele Buchstaben sind am Anfang groß geschrieben. Ein solcher Bezeichner würde auch bei Methoden, Variablen oder anderen Elementen aufstoßen.
Klasse
i
Kein sprechender Name, außerdem klein geschrieben.
Konstante
Ende
Nicht vollständig groß geschrieben.
Variable
Wert
Bei Variablen sollte der erste Buchstabe klein geschrieben werden.
Variable
werterfassung
Auch bei Variablen sollte der Beginn eines zweiten Wortes in dem Bezeichner groß geschrieben werden.
Namensräume Java stellt eine Technik zur Verfügung, mit der Namenskonflikte aufgelöst werden können, wenn es zwei identische Bezeichner im Quelltext gibt. Java arbeitet mit so genannten Namensräumen. Man versteht unter einem Namensraum einen Bereich, in dem ein bestimmter Bezeichner benutzt werden kann. Namensräume sind in Java einer Hierarchie zugeordnet. Es gilt dabei die Regel, dass ein Bezeichner einen identischen Bezeichner in einem übergeordneten Namensraum überdeckt. Außerdem trennt Java die Namensräume von lokalem und nicht-lokalem Code. Die Hierarchie der Namensräume gliedert sich wie folgt:
194
Java 2 Kompendium
Token
Kapitel 6
1.
Außen (aus Sicht der Mengenlehre zu sehen) steht der Namensraum des Pakets, zu dem die Klasse gehört.
2.
Danach folgt der Namensraum der Klasse.
3.
Es folgen die Namensräume der einzelnen Methoden. Dabei überdecken die Bezeichner von Methodenparametern die Bezeichner von Elementen der Klasse. Sollten Elemente der Klasse überdeckt werden, können sie immer noch mit dem Verweisoperator this qualifiziert werden.
4.
Innerhalb von Methoden gibt es unter Umständen noch weitere Namensräume in Form von geschachtelten Blöcken (etwa try/catchBlöcke). Variablen, die innerhalb eines solchen geschachtelten Blocks deklariert werden, sind außerhalb des Blocks unsichtbar.
Um einen Bezeichner zuzuordnen, werden die Namensräume immer von innen nach außen aufgelöst. Wenn der Compiler einen Bezeichner vorfindet, wird er zuerst im lokalen Namensraum suchen. Sofern er dort nicht fündig wird, sucht er im übergeordneten Namensraum. Das Verfahren setzt sich analog bis zum ersten Treffer (ggf. bis zur obersten Ebene) fort. Es gelten immer nur die Vereinbarungen des Namensraums, wo der Treffer erfolgt ist.
6.1.5
Literale
Literale sind spezielle Token, die zu speichernde Werte als Datentypen byte, short, int, long, float, double, boolean und char darstellen. Darüber hinaus werden Literale dazu benutzt, Werte darzustellen, die in Zeichenketten gespeichert werden. Um es etwas verständlicher auszudrücken – Literale sind das, was bei einer Zuweisung einer Variablen zugewiesen bzw. das, was als Wert direkt bei einer Operation verwendet wird. Ganzzahl-Literale Die Datentypen int und long können dezimal, aber auch hexadezimal sowie oktal beschrieben werden. Man nennt diese beiden Datentypen Integer-Literale oder auch Ganzzahl-Literal. Die Voreinstellung ist dezimal und gilt immer dann, wenn die Werte ohne weitere Angaben dargestellt werden. Hexadezimale Darstellung beginnt immer mit der Sequenz 0x oder 0X. Oktale Darstellungen beginnen mit einer führenden Null. Negativen Ganzzahlen wird (nicht gerade überraschend) ein Minuszeichen vorangestellt. Integer-Literale haben per Voreinstellung den Typ int. Durch Anhängen von l oder L kann man jedoch explizit den Typ long wählen. Sofern man für einen int-Datentyp einen Wert wählt, der den zulässigen Wertebereich überschreitet, muss dies sogar erfolgen. Die nachfolgende Tabelle zeigt einige Beispiele mit Zahlen in verschiedenen Darstellungen.
Java 2 Kompendium
195
Kapitel 6 Tabelle 6.5: Ganzzahl-Literale
Java – die Hauptbestandteile der Sprache
Integer-Literal
Dezimalwert
Hexadezimalwert
Oktalwert
Typ
123
123
7B
173
int
0123
83
53
123
int
0x123
291
123
443
int
0x123L
291
123
443
long
Gleitpunkt-Literale Die beiden Gleitzahltypen vom Datentyp float und double werden Gleitzahl-Literale oder Gleitpunkt-Literale genannt. Der Dezimalpunkt trennt Vor- und Nachkommateil der Gleitzahl. Standardeinstellung ist double. Wenn ein Gleitzahl-Literal als float interpretiert werden soll, muss ein f oder ein F angehängt werden. Daher wird die Zuweisung float gleitzahl = 0.123;
einen Compilerfehler erzeugen, während die drei folgenden Zuweisungen korrekt sind: float gleitzahl = 0.123F; float gleitzahl = 0.123f; double gleitzahl = 0.123;
Negativen Gleitzahlen wird wieder ein Minuszeichen vorangestellt. Mit dem nachgestellten e oder E, gefolgt von einem Exponenten (ein negativer Exponent ist ebenso erlaubt), können für Gleitzahl-Literale Exponenten verwendet werden. Allerdings nur dann, wenn kein Dezimalpunkt vorhanden ist. Zeichenliterale Zeichenliterale werden durch ein einzelnes, zwischen hochgestellten und einfachen Anführungszeichen stehendes Zeichen ausgedrückt. Das gilt für alle Zeichenwerte, egal ob es sich dabei um ein lateinisches Zeichen oder ein anderes Unicode-Zeichen handelt. Als einzelne Zeichen gelten alle druckbaren Zeichen mit Ausnahme des Bindestrichs (-) und des Backslash (\). Die Zeichen werden in Unicode-Format gespeichert. Zeichenliterale lassen sich aber auch in Escape-Format darstellen. Die Escape-Zeichenliterale beginnen immer mit dem Backslash-Zeichen. Diesem folgt eines der Zeichen (b, t, n, f, r, ", ‘ oder \) oder eine Serie von Oktalziffern (3-stellig) oder ein u gefolgt von einer 4-stelligen Serie Hexadezimalziffern, die für ein nicht zeilenbeendendes Unicode-Zeichen stehen. Die vier Stellen der hexadezimalen Unicode-Darstellung (/u0000 bis /uFFFF) stehen damit für 65.535 mögliche Kodierungen. Damit ist es insbesondere möglich solche Zeichen innerhalb von Zeichenketten darzustellen, die ohne diese Maskierung eine besondere 196
Java 2 Kompendium
Token
Kapitel 6
Funktion haben. Dies ist z.B. der Fall, wenn Sie das doppelte Hochkommata innerhalb einer Zeichenkette ausgeben wollen (es dient normalerweise als Begrenzung von Zeichenketten). In der nachfolgende Tabelle sehen Sie einige Beispiele: EscapeLiteral
UnicodeOktal-Sequenz Steuersequenz
Bedeutung
\b
\u0008
\010
Rückschritt (Backspace)
\t
\u0009
\011
Tab
\n
\u000a
\012
Neue Zeile
\f
\u000c
\014
Formularvorschub (Formfeed)
\r
\u000d
\015
Wagenrücklauf (Return)
\"
\u0022
\042
Doppeltes Anführungszeichen
\’
\u0027
\047
Einfaches Anführungszeichen
\\
\u005c
\134
Backslash
Tabelle 6.6: Zeichenliterale in verschiedenen Darstellungen
Das nachfolgende kleine Beispiel zeigt die Verwendung von so maskierten Sonderzeichen, die innerhalb von Zeichenketten verwendet werden. Das Beispiel bewirkt nicht mehr, als Text auf dem Bildschirm auszugeben, der von Sonderzeichen durchsetzt ist. Geben Sie den nachfolgenden Quelltext ein.
class Literale { public static void main(String argv[]) { System.out.println("Tab\tTab\tTab"); System.out.println("Einfache Hochkommata \'"); System.out.println("Eine neue Zeile\n\042, die mit einem doppelten Hochkomma beginnt und endet.\""); System.out.println( "Jetzt kommt noch ein Backslash\\."); } }
Java 2 Kompendium
Listing 6.2: Ausgabe von Zeichenliteralen
197
Kapitel 6
Java – die Hauptbestandteile der Sprache
Abbildung 6.2: Ausgabe von Sonderzeichen über Zeichenliterale
Speichern und kompilieren Sie die Datei.
Lassen Sie das Programm laufen. Die als oktale Escape-Literale bezeichneten Zeichenliterale können zur Darstellung aller Unicode-Werte von \u0000 bis \u00ff (alte ASCII-Begrenzung) benutzt werden. Bei oktaler Darstellung mit Basis 8 ist diese Darstellung auf \000 bis \377 begrenzt. Beachten Sie, dass Oktalzahlen nur von 0 bis einschließlich 7 gehen. Die Unicode-Zeichenliterale werden schon zu einem sehr frühen Zeitpunkt vom Java-Compiler javac interpretiert. Wenn man daher die Escape-Unicode-Literale dazu verwendet, ein zeilenbeendendes Zeichen, wie zum Beispiel Wagenrücklauf oder neue Zeile, darzustellen, wird das ZeilenendeZeichen vor dem schließenden einfachen Anführungszeichen erscheinen. Das Resultat ist dann ein Kompilierfehler. Benutzen Sie also besser nicht das \u-Format, um ein Zeilenende-Zeichen darzustellen. Verwenden Sie statt dessen die Zeichen \n oder \r. Zeichenketten-Literale Zeichenketten-Literale sind aus mehreren Zeichenliteralen zusammengesetzte Ketten (Strings)2. Bei Zeichenketten-Literalen werden null oder mehr Zeichen in (doppelte) Anführungszeichen dargestellt. Java erzeugt Zeichen2
198
So wie in unserem letzten Beispiel, wo wir darauf vorgegriffen haben.
Java 2 Kompendium
Token
Kapitel 6
ketten als Instanz der Klasse String. Damit stehen alle Methoden der Klasse String zur Manipulation einer Zeichenkette zur Verfügung. Obwohl viele Merkmale von Zeichenketten identisch zu den Merkmalen von Zeichenarrays sind, sind sie im Unterschied zu C/C++ keine einfachen Zeichenarrays. Zeichenketten-Literale stehen zwischen zwei doppelten Anführungszeichen und können Steuerzeichen wie Tabulatoren, Zeilenvorschübe, nichtdruckbare Unicode-Zeichen oder druckbare Unicode-Spezialzeichen enthalten. Bei den verwendeten Zeichen kann es sich wie bei Zeichenliterale gleichfalls um Escape-Sequenzen handeln. Beispiele: "Safety First" "2 CV" "DS 21" "" // leere Zeichenkette
Beide Anführungszeichen müssen in derselben Zeile des Quellcodes stehen, damit die Zeichenkette nicht automatisch ein Zeichen für eine neue Zeile enthält. Wenn Sie den Effekt einer neuen Zeile innerhalb einer Zeichenkette erreichen wollen, müssen Sie eine Escape-Sequenz wie zum Beispiel \n oder \r benutzen. Die Zeichen für doppelte Anführungszeichen (") und Backslash (\) müssen ebenfalls durch die Verwendung von Escape-Sequenzen (\" und \\) dargestellt werden (wir haben es in dem letzten Programmbeispiel verwendet). Beispiele: "Tic \t Tac \t Toe " "2 \n CV"
Einige Anmerkungen zu Zeichenketten: Da Zeichenketten in Java native Datentypen sind, lässt sich eine Addition von Zeichenketten mit dem Verknüpfungsoperator (+) realisieren. Wenn Sie eine Zeichenkette aus mehreren kleinen Zeichenketten zusammengesetzten wollen, können Sie diese einfach mit dem Zeichenkettenverknüpfungs-Operator (+) verbinden. Wenn ein Wert, der keine Zeichenkette ist, mit einer Zeichenkette verbunden wird, so wird er vor dem Verbinden automatisch zu einer Zeichenkette konvertiert. Das bedeutet, dass beispielsweise auch ein numerischer Wert einer Zeichenkette hinzugefügt werden kann. Der
Java 2 Kompendium
199
Kapitel 6
Java – die Hauptbestandteile der Sprache numerische Wert wird zu einer entsprechenden Ziffernzeichenfolge konvertiert, die der ursprünglichen Zeichenkette dann hinzugefügt wird. Zeichenketten sind in Java auf der anderen Seite Objekte, oder genauer: Referenztypen. Als Instanzen der Klasse String lassen sich auf Zeichenketten viele nützlich Methoden dieser Klasse anwenden. Diese Methoden können zum Vergleichen oder Durchsuchen von Zeichenketten verwendet werden, oder dienen zum Extrahieren einzelner Zeichen. Wichtige Methoden sind equals(), die Zeichenketten auf Gleichheit überprüft, oder length(), die die Länge eines Strings zurückgibt. Boolesche Literale Es gibt, wie schon mehrfach erwähnt, nur zwei boolesche Literale: true und false. Es gibt keinen Nullwert und kein numerisches Äquivalent. Trennzeichen Trennzeichen sind in Java speziellen Token, die nur aus einem einzigen Zeichen bestehen, und andere Token trennen. Java kennt neun Trennzeichen:
Tabelle 6.7: Java-Trennzeichen
200
Token
Beschreibung
(
Der Token »KlammerAuf« wird sowohl zum Öffnen einer Parameterliste für eine Methode als auch zur Festlegung eines Vorrangs für Operationen in einem Ausdruck benutzt.
)
Der Token »KlammerZu« wird sowohl zum Schließen einer mit dem Token »KlammerAuf« geöffneten Parameterliste für eine Methode als auch zur Beendigung eines mit dem Token »KlammerAuf« festgelegten Vorrangs für Operationen in einem Ausdruck benutzt.
{
Der Token »GeschweifteKlammerAuf« wird zu Beginn eines Blocks mit Anweisungen oder einer Initialisierungsliste gesetzt.
}
Der Token »GeschweifteKlammerZu« wird an das Ende eines mit dem Token »GeschweifteKlammerAuf« geöffneten Blockes mit Anweisungen oder einer Initialisierungsliste gesetzt und schließt den Block wieder.
[
Der Token »EckigeKlammerAuf« steht vor einem Ausdruck, der als Index für ein Datenfeld dient.
]
Der Token »EckigeKlammerZu« folgt einem Ausdruck, der als Index für ein Datenfeld dient und beschließt den Index.
;
Das Semikolon dient sowohl zum Beenden einer Ausdrucksanweisung, als auch zum Trennen der Teile bei einer for-Anweisung.
Java 2 Kompendium
Token
Kapitel 6
Token
Beschreibung
,
Der Token »Komma« ist multifunktional und wird in vielen Zusammenhängen als Begrenzer verwendet.
.
Der Token »Punkt« wird zum einen als Dezimalpunkt, zum anderen als Trennzeichen von Paketnamen, Klassennamen oder Methodenund Variablennamen benutzt.
6.1.6
Tabelle 6.7: Java-Trennzeichen (Forts.)
Operatoren
Operatoren sind dazu da, eine Aktion zu spezifizieren, die mit einem oder mehreren gegebenen Operanden durchgeführt werden soll. In Java werden die Operatoren in mehreren Kategorien eingeteilt. Es gibt fünf arithmetische Operatoren (+, –, *, /, %), sechs Zuweisungsoperatoren (=, +=, *=, -=, / =, %=), einen Dekrementoperator (--), einen Inkrementoperator (++), vier bitweise arithmetische Operatoren (&, |, ^, ~), drei bitweise Verschiebungsoperatoren (<<, >>, >>>), sechs bitweise Zuweisungsoperatoren (&=, |=, ^=, <<=, >>=, >>>=), fünf Vergleichsoperatoren (==, !=, <>, <=, >=), drei logische Vergleichsoperatoren (&&, ||, !) und zwei Operatoren, die innerhalb if-then-else-Konstrukten als Ersatz für eine ausführliche Schreibweise fungieren, wenn sie zusammen benutzt werden (?, :). Dazu kommen noch der Typcast-Operator, der instanceof-Operator und der new-Operator, die aber an dieser Stelle nicht besprochen werden sollen. Die anderen JavaOperatoren sollen nun nach Kategorien getrennt beschrieben werden. Arithmetische Operatoren Arithmetische Operatoren benutzen ein oder zwei Operanden. In Fall von zwei Operanden sind diese entweder ganzzahlige Werte oder Fließkommazahlen. Als Rückgabe einer arithmetischen Operation erhalten Sie einen neuen Wert, dessen Datentyp sich auf Grund der Datentypen der Operanden wie folgt ergibt: Zwei ganzzahlige Datentypen (byte, short, int oder long) als Operanden ergeben immer einen ganzzahligen Datentyp als Ergebnis. Dabei kann als Datentyp des Ergebnisses immer nur ein Datentyp int oder long entstehen. byte und short sind nicht möglich, und der Datentyp long entsteht dann und nur dann, 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 Fließkommatypen als Operanden ergeben immer einen Fließkommatypen als Ergebnis. Die Anzahl der Stellen des Ergebnisses ist immer das Maximum der Stellenanzahl der beiden Operanden. Wenn die Operanden ein ganzzahliger Typ und ein Fließkommatyp sind, ist das Ergebnis immer ein Fließkommatyp. Java 2 Kompendium
201
Kapitel 6
Java – die Hauptbestandteile der Sprache Java kennt die folgenden arithmetischen Operatoren mit zwei Operanden:
Tabelle 6.8: Die arithmetischen Java-Operatoren mit zwei Operanden
Operator
Bedeutung
Beispiel
+
Additionsoperator
3 + 4
-
Subtraktionsoperator
4 – 3
*
Multiplikationsoperator
2 * 3
/
Divisionsoperator
2 / 3
%
Modulo-Operator (gibt den Rest einer Division zurück)
15 % 9
Die meisten Operatoren sind sicher klar. Eine Ausnahme ist möglicherweise der Modulo-Operator. Dieser ist oft nur Profis bekannt, was aber seine Bedeutung auf keinen Fall mindert. Viele Verschlüsselungs- und Komprimierungsverfahren kommen ohne ihn nicht aus und auch sonst ist er – gerade bei etwas komplexeren Anwendungen – unabdingbar. Der arithmetische Modulo- oder Rest-Operator gibt den Rest einer Division zurück. Wenn der Ausdruck x%y ausgewertet wird, ist der Rückgabewert der Operation der Rest der Division. Der Begriff Modulo stammt aus der Mathematik und wird hauptsächlich in Algebra und Zahlentheorie verwendet. Zwei ganze Zahlen werden als »kongruent modulo einem Faktor n« bezeichnet, wenn ihre Differenz ein Vielfaches von dem Faktor n ist. Sofern sie nicht »kongruent modulo einem Faktor n« sind, bleibt ein Rest. Alles klar? Mathematik ist doch einfach schön, oder ;-)?! Ein paar Beispiele zur Verdeutlichung: Tabelle 6.9: Modulo-Beispiele mit Ganzzahlen
Operation
Rest
Erklärung
1%7
1
Die Zahl 7 geht 0-mal in die Zahl 1 und als Rest bleibt 1. 1%7 = (0*7) – (0*7) + 1 = 1
4%7
4
Die Zahl 7 geht 0-mal in die Zahl 4 und als Rest bleibt 4. 4%7 = (0*7) – (0*7) + 4 = 4
8%7
1
Die Zahl 7 geht 1-mal in die Zahl 8 und als Rest bleibt 1. 8%7 = (1*7) – (1*7) + 1 = 1
9%7
2
Die Zahl 7 geht 1-mal in die Zahl 9 und als Rest bleibt 2. 9%7 = (1*7) – (1*7) + 2 = 2
13%7
6
Die Zahl 7 geht 1-mal in die Zahl 13 und als Rest bleibt 6. 13%7 = (1*7) – (1*7) + 6 = 6
202
Java 2 Kompendium
Token
Kapitel 6
Operation
Rest
Erklärung
14%7
0
Die Zahl 7 geht genau 2-mal in die Zahl 14. Es bleibt kein Rest übrig.
Tabelle 6.9: Modulo-Beispiele mit Ganzzahlen (Forts.)
14%7 = (2*7) – (2*7) + 0 = 0 7000000%7
Auch hier gilt trotz der großen Zahl vor dem Operator: die Division geht ohne Rest auf.
0
7000000/7 = (1000000*7) – (1000000*7) + 0 = 0 13%11
Noch ein Beispiel mit Rest und einem anderen ModuloFaktor. Die Zahl 11 geht 1-mal in die Zahl 13 und als Rest bleibt 2.
2
13/11 = (1*11) – (1*11) + 2 = 2
C-Programmierer kennen den Modulo-Operator von Ganzzahlen und Java erweitert den Rest-Operator sogar, weil auch Operationen mit Fließkommazahlen definiert sind! Es ist einfach die natürliche Fortsetzung der Operation auf die Menge der Fließkommazahlen. Der ausgegebene Wert ist immer noch der »Rest nach der Division«. Auch hier ein paar Beispiele zur Verdeutlichung: Operation
Rest
Erklärung
12%2.5
2.0
Die Zahl 2.5 geht 4-mal in die Zahl 12 und als Rest bleibt 2. Allerdings als Fließkommazahl.
Tabelle 6.10: Modulo-Beispiele mit Fließkommazahlen
12/2.5 = 2.0 4%3.3
0.7
Die Zahl 3.3 geht 1-mal in die Zahl 4 und als Rest bleibt 0.7. Natürlich als Fließkommazahl. 4/3.3 = 0.7
Die technische Formel, um den Wert von x%y zu bestimmen, wobei wenigstens x oder y ein Fließkommatyp ist, sieht folgendermaßen aus: x-((int)(x/y)*y) Der (int)-Operator ist ein Beispiel für den Casting-Operator, zu dem wir später noch kommen werden. Java kennt auch einstellige arithmetische Operatoren. So gibt es zwei einstellige arithmetische Operatoren (d.h. mit nur einem Operanden) in Java, die eine recht offensichtliche Bedeutung haben und dem Operanden einfach vorangestellt werden:
Java 2 Kompendium
203
Kapitel 6
Java – die Hauptbestandteile der Sprache Die einstellige arithmetische Negierung: Das Gegenteil der arithmetischen Negierung: + Die einstellige Negierung ergibt die arithmetische Vorzeichenumdrehung ihres numerischen Operanden. Daher ergibt ein Ausdruck wie beispielsweise -x das arithmetisch Negative von jedem beliebigen Wert x. Der einstellige Operator + tauchte das erste Mal in ANSI C auf und wurde hauptsächlich aus Symmetriegründen eingeführt. Er gibt ganz einfach den Wert seines Operanden aus; mit anderen Worten: Er tut gar nichts! Zu den einstelligen arithmetischen Operatoren werden auch die Inkrement-/ Dekrement-Operatoren gezählt. Sie werden nur in Verbindung mit einem ganzzahligen oder einem Fließkommaoperanden benutzt. Inkrement- und Dekrement-Operatoren sind C-Programmierern altvertraut. Sie werden zum Auf- und Abwerten eines einzelnen Wertes verwendet und sind zwei der Bestandteile einer Programmiersprache wie C, die bei häufiger Verwendung den Begriff »Lesbarkeit« bei einem Quelltext ad absurdum führen. Viele C-Profis werden mir da widersprechen, aber Abkürzungen helfen meist nur demjenigen, der einen Quelltext erstellt und nicht demjenigen, der das Programm warten soll (dabei schließe ich mit ein, dass es sich um dieselbe Person handelt). Die Verwendung von rein aus Abkürzungsgründen eingeführten Konstrukten muss bei einer professionellen Programmierung mit einer solch umfangreichen Dokumentation innerhalb des Quelltexten per Kommentare einhergehen, sodass die Ersparnis gegenüber normaler Zuweisung aufgehoben wird. Bezüglich der Performance wird kein Vorteil erzielt, denn der Compiler optimiert sowieso. Dennoch haben die Operatoren natürlich ihre Berechtigung und die Warnung beinhaltet ausdrücklich die häufige und vor allem verschachtelte Verwendung. Der Inkrement-Operator (++) erhöht den Wert des Operanden um 1. Die Reihenfolge von Operand und Operator ist wichtig. Wenn der Operator vor dem Operanden steht, erfolgt die Erhöhung des Wertes, bevor der Wert dem Operanden zugewiesen wird. Wenn er hinter dem Operanden steht, erfolgt die Erhöhung, nachdem der Wert bereits zugewiesen wurde. Der Dekrementoperator (--) arbeitet analog. Er erniedrigt den Wert des Operanden um 1. Die Reihenfolge von Operand und Operator ist auch hier von Bedeutung. Wenn der Operator vor dem Operanden steht, erfolgt die Erniedrigung des Wertes, bevor der Wert dem Operanden zugewiesen wird. Wenn er hinter dem Operanden steht, erfolgt die Erniedrigung, nachdem der Wert bereits zugewiesen wurde. Testen wir insbesondere die Inkrement-/Dekrement-Operatoren in einem kleinen Beispiel.
204
Java 2 Kompendium
Token
Kapitel 6
Geben Sie den nachfolgenden Quelltext ein.
class OpTest { public static void main(String args[]) { int i=1; int j=3; System.out.println("Startwert von i: " + i); System.out.println("i++:" + i++); System.out.println("++i:" + ++i); System.out.println("i*j:" + i*j); System.out.println("i%j:" + i%j); System.out.println("Startwert von j: " + j); System.out.println("j--:" + j--); System.out.println("--j:" + --j); } }
Listing 6.3: Inkrement-/Dekrement-Operatoren
Abbildung 6.3: Die Wirkung von Operatoren
Speichern und kompilieren Sie die Datei.
Lassen Sie das Programm laufen.
Java 2 Kompendium
205
Kapitel 6
Java – die Hauptbestandteile der Sprache Beachten Sie die Stellen, wo mit ++ der Wert erhöht, aber noch der nicht erhöhte Wert ausgegeben wird. Arithmetische Zuweisungsoperatoren In Java gibt es neben dem direkten Zuweisungsoperators (=) noch die arithmetischen Zuweisungsoperatoren. Diese sind eigentlich nur als Abkürzung für arithmetisch Operationen zu verstehen. Wie auch die arithmetischen Operatoren, können sie sowohl mit ganzen Zahlen als auch mit Fließkommazahlen verwendet werden. Das Ergebnis einer Zuweisung über einen arithmetischen Zuweisungsoperatoren steht immer auf der linken Seite.
Tabelle 6.11: Die arithmetischen Zuweisungsoperatoren
Operator
Bedeutung
Beispiel
Entspricht
+=
Additions- und Zuweisungsoperator
x += 5
x = x + 5
-=
Subtraktions- und Zuweisungsoperator
x -= 3
x = x – 3
*=
Multiplikations- und Zuweisungsoperator
x *= 10
x = x * 10
/=
Divisions- und Zuweisungsoperator
x /= 3
x = x / 3
%=
Modulo- und Zuweisungsoperator
x %= 7
x = x % 7
=
direkter Zuweisungsoperator
x = 3
Bitweise arithmetische Operatoren Bitweise Arithmetik wird im Wesentlichen zum Setzen und Testen einzelner Bits und Kombinationen einzelner Bits innerhalb einer Variablen benutzt. Es gibt eigentlich wenig triftige Gründe, unter Java solch eine bitweise Arithmetik anzuwenden. Die wichtigsten Gründe zur Benutzung bitweiser Operatoren betreffen die direkte Kommunikation mit Hardwarekomponenten oder die Arbeit mit Komprimierungsprozessen (dazu etwas mehr im Anhang unter dem Abschnitt zur Arbeitsweise von Komprimierungsprogrammen) bzw. Verschlüsselungsoperationen. Das wahrscheinlich wichtigste Argument ist, dass bestimmte Vorgänge bei der Arbeit auf Bitebene bezüglich der Performance verbessert werden können. Binäre Arbeit auf Datentypen ist in Java im Vergleich zu anderen Programmiertechniken ziemlich ungefährlich. Da die Datentypen auf allen Plattformen gleich implementiert sind und auch sonst extrem stabil gehandhabt werden, kann die binäre Vorgehensweise unter Java nicht zu den Problemen führen, die bei anderen Sprachen wie C/C++ die Geschichte recht heikel machen können (Zugriff auf falsche Speicherbereiche, Zerstören von Nachbarinformationen usw.).
206
Java 2 Kompendium
Token
Kapitel 6
Bitweise Arithmetik ist nur für die vier Integer-Typen und für Zeichentypen definiert, nicht aber für boolesche Typen und Fließkommatypen.
Operator
Beschreibung
Bedeutung
&
Bitweiser AND-Operator
Die Operation x & y verknüpft alle korrespondierenden Bits von x und y per UND.
|
Bitweiser OR-Operator
Die Operation x | y verknüpft alle korrespondierenden Bits von x und y per ODER.
^
Bitweiser XOR-Operator
Die Operation x ^ y verknüpft alle korrespondierenden Bits von x und y per EXKLUSIVODER.
~
Bitweiser Komplement-Operator Einstelliger, dem Operanden vorangestellter Operator, der alle Bits des Operanden invertiert.
Tabelle 6.12: Die Bit-Operatoren von Java
Wir werden in den folgenden Beispielen Binär- und Hexadezimalrechnung verwenden. Zu beiden Themen finden Sie einen kleinen Exkurs im Anhang. Anhand von Variablen des Typs byte lassen sich Binäroperationen am leichtesten beschreiben, aber der gewählte Beispieldatentyp ist ansonsten willkürlich. Ohne Vorzeichen besteht ein Byte aus 8 Bits, die wiederum nur die Werte 1 oder 0 annehmen können. Jede binäre Darstellung von 0 und 1 steht für eine dezimale Zahl. Die 8-Bit-Binärkkombination 00101010 steht für die Zahl 42. In Hexadezimalzahlen wird dies durch 0x2A ausgedrückt. Für die bitweise Arithmetik gelten nun die folgenden einfachen Regeln: Bitweiser AND-Operator (&) Wenn Sie den AND-Operator (&) mit zwei Bytes benutzen und das Ergebnis in einem dritten Byte ablegen, dann hat das resultierende Byte nur für die Bits den Wert 1, wenn alle beide Operanden an der gleichen Stelle Bits mit dem Wert 1 hatten.
Java 2 Kompendium
207
Kapitel 6
Java – die Hauptbestandteile der Sprache Beispiele:
Tabelle 6.13: Beispiele für &
00101010
&
00101010
=
00101010
00101010
&
00001000
=
00001000
00101010
&
10000000
=
00000000
11110000
&
00001111
=
00000000
10101010
&
01010101
=
00000000
11100011
&
11101100
=
11100000
Die Benutzung von AND hat immer das Resultat, dass maximal die gleichen oder weniger Bits auf 1 gesetzt werden. Das Resultat ist also immer die gleiche oder eine kleinere Dezimal- oder Hexadezimalzahl als das Maximum der beiden Zahlen vorher. Wir machen uns einmal den Spaß und stellen obige bitweise Arithmetik im Dezimal- und im Hexadezimalsystem dar (um deutlich zu machen, dass & nichts mit dem normalen arithmetischen + zu tun hat). Tabelle 6.14: Hexadezimale Beispiele für &
Tabelle 6.15: Dezimale Beispiele für &
208
2A
&
2A
=
2A
2A
&
8
=
8
2A
&
80
=
0
2A
&
F
=
0
AA
&
155
=
0
E3
&
EC
=
E0
42
&
42
=
42
42
&
8
=
8
42
&
128
=
0
42
&
15
=
0
170
&
341
=
0
227
&
236
=
224
Java 2 Kompendium
Token
Kapitel 6
Bitweiser OR-Operator (|) Wenn Sie den OR-Operatoren (|) mit zwei Bytes benutzen und das Ergebnis in einem dritten Byte ablegen, dann hat das resultierende Byte nur für die Bits den Wert 1, wenn mindestens einer der Operanden ein Bit an dieser Position mit dem Wert 1 hatte. Nehmen wir die oben angeführten Beispiele zur Hand: 00101010
|
00101010
=
00101010
00101010
|
00001000
=
00101010
00101010
|
10000000
=
10101010
11110000
|
00001111
=
11111111
10101010
|
01010101
=
11111111
11100011
|
11101100
=
11101111
Tabelle 6.16: Beispiele für |
Die Benutzung von OR-Operator (|) resultiert immer darin, dass die gleichen oder mehr Bits auf 1 gesetzt werden. Bitweiser XOR -Operator (^) Wenn Sie den XOR-Operator (^) (EXKLUSIV-ODER) mit zwei Bytes benutzen und das Ergebnis in einem dritten Byte ablegen, dann hat das resultierende Byte für ein Bit nur dann den Wert 1, wenn das dazugehörige Bit in genau einem der beiden Operanden-Bytes gesetzt wird. Es muss und darf nur einem der Operanden ein Bit an dieser Position auf 1 gesetzt sein. Ein Hinweis zur Eingabe von ^: Vielen Anwendern ist sicher schon aufgefallen, dass die Eingabe von ^ nicht so ganz logisch erfolgt (etwa, wenn sie in Excel potenzieren wollen). Sie müssen nach der Betätigung der ^-Taste eine weitere Taste (etwa die Leertaste) drücken – erst dann wird das ^-Zeichen auf dem Bildschirm erscheinen. Nehmen wir wieder die oben angeführten Beispiele: 00101010
^
00101010
=
00000000
00101010
^
00001000
=
00100010
00101010
^
10000000
=
10101010
11110000
^
00001111
=
11111111
Java 2 Kompendium
Tabelle 6.17: Beispiele für ^
209
Kapitel 6 Tabelle 6.17: Beispiele für ^ (Forts.)
Java – die Hauptbestandteile der Sprache
10101010
^
01010101
=
11111111
11100011
^
11101100
=
00001111
Der Bitweiser Komplement-Operator (~) Der bitweise Komplement-Operator (~) unterscheidet sich massiv von den anderen drei bitweisen Operatoren, denn Komplementieren ist eine einstellige bitweise Operation (d.h. nur ein Operand). Wenn ein Byte komplementiert wird, werden alle seine Bits invertiert. Auch hier wollen wir zur Verdeutlichung ein paar Beispiele heranziehen: Tabelle 6.18: Beispiele für ~
~
00101010
=
11010101
~
00001000
=
11110111
~
10000000
=
01111111
~
00001111
=
11110000
~
01010101
=
10101010
~
11101100
=
00010011
Wir wollen die bitweise Arithmetik in einem Beispiel testen und dabei die verschiedenen Operatoren verwenden. Listing 6.4: Die Arbeit mit binären Operatoren
class BitOp { public static void main(String argv[]) { char meinChar1 = 'a'; char meinChar2 = 'b'; System.out.println((int)meinChar1 + " " + (int)meinChar2); System.out.println((meinChar1 + meinChar2) + " " + (char)(meinChar1 meinChar2)); System.out.println((meinChar1 & meinChar2) + " " + (char)(meinChar1 meinChar2)); System.out.println((meinChar1 | meinChar2) + " " + (char)(meinChar1 meinChar2)); System.out.println((meinChar1 ^ meinChar2) + " " + (char)(meinChar1 meinChar2)); System.out.println(~meinChar1 + " " + (char)(~meinChar1)); } }
+ & | ^
Das Beispiel arbeitet mit zwei char-Variablen, die die Werte a und b zugewiesen bekommen. Das Zeichen a hat den Wert 97 in der Unicode-Darstellung, das Zeichen b den Wert 98 (das wird mit der ersten Ausgabe im Beispiel demonstriert). Das entspricht binär 0110 0001 und 0110 0010. Beachten Sie, dass das Beispiel Casting anwendet.
210
Java 2 Kompendium
Token
Kapitel 6
Wenn nun die beiden char-Variablen einfach addiert werden, werden deren Werte dezimal in der Unicode-Kodierung addiert. Das ergibt 195, was binär 1100 0011 entspricht (die zweite Ausgabe). Bitweise Addition führt dazu, dass binär 0110 0000 entsteht (dann und dann entsteht der Wert 1, wenn alle beide Operanden an der gleichen Stelle Bits mit dem Wert 1 hatten). Das entspricht dem Dezimalwert 96 (die dritte Ausgabe). Analog verhält es sich mit binärem Oder (dezimal entsteht 99 und binär 0110 0011) und den beiden anderen binären Operationen, was die nachfolgenden Ausgaben demonstrieren. Abbildung 6.4: Die Ergebnisse binärer Operationen
Bitweise Verschiebungsoperatoren Bitweise Verschiebungsoperatoren verschieben die Bits in der Darstellung einer ganzen Zahl. Die Bits des ersten Operanden werden um die Anzahl an Positionen verschoben, die im zweiten Operanden angegeben wird. Im Fall der Verschiebung nach links ist es immer eine Null, mit der die rechte Seite aufgefüllt wird. Dieser Vorgang entspricht dem Multiplizieren mit 2 hoch der Zahl, die durch den zweiten Operanden definiert wird. Der normale Verschiebungsoperator nach rechts vervielfacht das Vorzeichenbit. Dieser Vorgang entspricht der Division durch 2 hoch der Zahl, die durch den zweiten Operanden definiert wird. Die Verschiebung nach rechts mit Füllnullen vervielfacht eine Null von der linken Seite.
Java 2 Kompendium
211
Kapitel 6 Tabelle 6.19: Die bitweisen Verschiebungsoperatoren
Tabelle 6.20: Beispiele für bitweise Verschiebung
Listing 6.5: Die Arbeit mit binären Verschiebungen
Java – die Hauptbestandteile der Sprache
Operator
Beschreibung
Bedeutung
<<
Operator für bitweise Verschiebung nach links
Die Operation x << y bedeutet, dass alle Bits von x um y Positionen nach links verschoben werden. Die rechte Seite der Darstellung von x wird mit Nullen aufgefüllt.
>>
Operator für bitweise Verschiebung nach rechts
Die Operation x >> y bedeutet, dass alle Bits von x um y Positionen nach rechts verschoben werden. Dabei wird das Vorzeichenbit vervielfacht.
>>>
Operator für bitweise Verschiebung nach rechts mit Füllnullen
Die Operation x >>> y bedeutet, dass alle Bits von x um y Positionen nach rechts verschoben werden. Die linke Seite der Darstellung von x wird mit Nullen aufgefüllt.
01001111
<<
1
=
10011110
00111100
<<
2
=
11110000
01001111
>>
1
=
00100111
11110000
>>
2
=
11111100
01001111
>>>
1
=
00100111
11110000
>>>
2
=
00111100
class BitVerschieb { public static void main(String argv[]) { int a = 42; int b; b = a >> 1; System.out.println(b); System.out.println(b << 2); System.out.println(b >>> 1); } }
Das Beispiel arbeitet mit zwei int-Variablen. Die Variable a bekommt den Startwert 42 zugewiesen und b den Wert, der aus der Operation a >> 1 entsteht. Da dies der Division durch 2 entspricht, muss das Ergebnis 21 sein (was die erste Ausgabe demonstriert). Binär wird aus der Darstellung 0010 1010 für 42 jedes Bit um den Faktor 1 nach rechts verschoben (0001 0101), was dem Wert 21 in der Unicode-Darstellung entspricht.
212
Java 2 Kompendium
Token
Kapitel 6
Die zweite Operation ist das Verschieben der Bits um zwei Stellen nach links (0101 0100), was dezimal der Multiplikation mit dem Faktor 4 entspricht. Die dritte Operation entspricht verschiebt die Binärdarstellung 0001 0101 (den Wert von b) mit Füllnullen um eine Stelle nach links. Das ergibt 0000 1010, was dezimal dem Wert 10 entspricht. Abbildung 6.5: Die Ergebnisse binärer VerschiebeOperationen
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.
Tabelle 6.21: Die bitweisen Zuweisungsoperatoren
Vergleichsoperatoren Vergleichsoperatoren haben zwei Operanden gleichen Typs und vergleichen diese. Als Rückgabewert der Operation entsteht immer ein boolescher Wert (true oder false).
Java 2 Kompendium
213
Kapitel 6
Java – die Hauptbestandteile der Sprache Der Rückgabewert eines Java-Vergleichs ist auf jeden Fall ein Wahrheitswert. Es ist in Java nicht möglich, einen numerischen Rückgabewert zu erhalten (wie etwa in C/C++, wo man auf Gleichheit mit 0 oder Ungleichheit testen kann). Die Vergleichoperatoren lassen sich in Bezug auf Vergleichslogik wie folgt einteilen: 1.
Relationale Operatoren
2.
Logische Gleichheitsoperatoren
Relationale Operatoren sind zum Ordnen von Grö ß en bestimmt. Die Ordnung erfolgt beispielsweise danach, ob ein Wert grö ß er oder kleiner als ein anderer Wert ist. Die Werte zweier Variablen vom Typ char können ebenfalls verglichen werden und sind in der allgemeinen Beschreibung bereits enthalten. Es werden die Variablen vom Typ char bei der Verwendung eines Vergleichsoperators wie ganze 16-Bit-Zahlen (Werte 0 bis 65535) entsprechend ihrer UnicodeKodierung behandelt. Vergleichsoperatoren werden oft in Schleifen verwendet, die auf einen booleschen Wert abprüfen.
Tabelle 6.22: Die logischen JavaVergleichsoperatoren
Operator
Beschreibung
==
Gleichheitsoperator
!=
Ungleichheitsoperator
<
Kleiner-als-Operator
>
Grö ß er-als-Operator
<=
Kleiner-als-oder-gleich-Operator
>=
Grö ß er-als-oder-gleich-Operator
Und wenn es für Profis jetzt auch als lächerlicher Hinweis erscheint: Verwechseln Sie den Gleiheitsoperator (zwei hintereinander folgenden Gleichheitszeichen) nicht mit dem Zuweisungsoperator (ein einzelnes Gleichheitszeichen). Einige andere Programmiersprachen unterscheiden dahingehend übrigens nicht und verwenden das einfache Gleichzeichen für beide Fälle (Vergleich und Zuweisung).
214
Java 2 Kompendium
Token
Kapitel 6
Wir wollen an dieser Stelle wieder ein weiteres kleines Java-Programm schreiben, um sowohl die Schleife in Verbindung mit boolesche Werten zu erläutern als auch die Unicode-Kodierung von einigen Zeichen ansehen. Damit wir schon so langsam einen etwas anspruchsvolleren Java-Quelltext erstellen, setzen wir dabei ein paar Programmiertechniken voraus, die zwar bisher noch nicht angesprochen wurden (etwa Variablen, Casting und Schleifen), vielen Lesern aber sicher aus anderen Programmiersprachen bekannt sein dürften. Wir werden zudem das Programm Schritt für Schritt durchsprechen. Wenn Ihnen dennoch das Programm zu kompliziert ist – keine Angst, wir werden die einzelnen Techniken im Laufe des Kapitels noch sehr ausführlich beschreiben. class Zeichenspiele { public static void main (String args[]) { int i; char zeichen; for (i=10; i < 20 ;i++) { System.out.print(i); zeichen = (char)i; System.out.print(" " + (char)i + " "); zeichen = (char)(zeichen – 10); System.out.print(zeichen); System.out.print(" "); System.out.println((int)zeichen); System.out.println("------"); } } }
Listing 6.6: Die Verwendung vom Vergleich auf »kleiner«
Was tut dieses Programm? Wenn Sie das Programm wieder eingegeben, gespeichert und kompiliert haben, können Sie es mittels des Interpreters ausführen. Auf dem Standardausgabegerät (meist dem Bildschirm) bekommen Sie eine Blockstruktur angezeigt. Insgesamt 10 Blöcke und eine Trennlinie werden ausgegeben. Abbildung 6.6: Die Ausgabe der Schleife
Java 2 Kompendium
215
Kapitel 6
Java – die Hauptbestandteile der Sprache Das Programm hat eine ähnliche Struktur wie unser HelloJava – Programm und auch das Unicode-Programm. Es besteht im Wesentlichen wieder nur aus der main()-Methode, die jedes eigenständige Java-Programm benötigt. Am Anfang des Programms werden zwei Variablen vereinbart: int i; char zeichen;
Die erste Variable dient später als eine Zählvariable, die zweite später zur Ausgabe. Innerhalb der main()-Methode finden Sie weiterhin eine for-Schleife. Eine solche Schleife dient dazu, die in ihrem Inneren angegebenen Anweisungen so lange zu wiederholen, wie eine bestimmte Bedingung erfüllt ist. Diese Bedingung wird von der for-Schleife über (i=10; i < 20 ;i++) abgeprüft. Solange der Vergleich in dieser Überprüfung den Wert true liefert, wird die Schleife wiederholt. Innerhalb der for-Schleife finden Sie wie bei unseren bisherigen Beispielprogrammen die Methode System.out.println() sowie die verwandte Methode System.out.print(), die ohne Zeilenvorschub arbeitet. Die for-Schleife durchläuft 10 Durchgänge (von 10 bis inklusive 19), was die 10 Blöcke bewirkt. System.out.println("------"); erzeugt den jeweiligen Trennstrich zur optischen Unterscheidung der einzelnen Blocks. In unserem Beispiel geben wir als Erstes die Zählvariable innerhalb der Methode System.out.print() auf dem Standardausgabegerät aus (System.out.print(i);). Damit kontrollieren wir den Schleifenverlauf und den Wert der Zählvariable. Hinter der Zeile zeichen = (char)i;
versteckt sich eine Zuweisung in Verbindung mit einer Technik, die sich Casting nennt. Vorerst reicht es aus, dass Sie sich darunter eine Umwandlung einer Zahl in ein char-Zeichen vorstellen. Per Casting wird der numerische Wert in das zugehörige Zeichen des Unicodes überführt. Zudem werden einige Leerstrings als optische Trennung hinzugefügt. Als Abänderung wird das Ergebnis der Aktion in der am Anfang definierten Zeichen-Variable zeichen gespeichert. Die nachfolgende Ausgabe kennen wir. Es ist die Zeichenvariable. Wir hätten auch hier die Zeichenvariable zeichen direkt nehmen können, was wir im übernächsten Schritt tun werden.
216
Java 2 Kompendium
Token
Kapitel 6
Im Folgeschritt wird es jetzt spannend. Wir machen uns zunutze, dass die Variablen vom Typ char bei der Verwendung eines Vergleichsoperators oder Arithmetik mit Zahlen (wie hier) wie ganze 16-Bit-Zahlen (Werte 0 bis 65535) entsprechend ihrer Unicode-Kodierung behandelt werden. Die Arithmetik zeichen – 10 reduziert den Wert der Unicode-Kodierung von zeichen um den Faktor 10. Um allerdings dieses Ergebnis in einer Zeichenvariable speichern zu können, müssen wir wieder explizit Casting anwenden. System.out.print(zeichen); ist die Kontrollausgabe. Sie sehen, dass das ausgegebene Zeichen sich von der vorherigen Ausgabe unterscheidet (um die Codeverschiebung 10 nach unten).
Der letzte Schritt vor der Trennlinie gibt den jetzigen Unicode von zeichen aus. Auch dazu verwenden wir wieder die Typumwandlung mittels Casting, nur in die Richtung »Zeichen in Ganzzahl vom Typ int«. Die Priorität von relationalen Operatoren liegt unter der arithmetischer Operatoren, jedoch über der von Zuweisungsoperatoren. Interessant ist die Anwendung von Vergleichsoperatoren, wenn die Operanden Objekte sind. In diesem Fall muss man massiv zwischen den Objekten und den darin gespeicherten Werten unterscheiden. Zwar bedeutet eine Objektgleichheit immer die Gleichheit der Werte (in dem Fall liegt die Referenz auf den gleichen Speicherplatz vor), jedoch gilt die Umkehrung nicht. Zwei verschiedene Objekte können selbstverständlich gleiche Werte beinhalten. Der Vergleich mittels der Vergleichsoperatoren liefert dann Ungleichheit, obwohl der Inhalt unter Umständen gleich ist. Um die Gleichheit des Inhalts zu überprüfen, gibt es die in allen Objekten verfügbare Methode equals() (vererbt von Object). Ziehen wir zur Verdeutlichung das folgende Beispiel heran, wo Vergleiche zwischen Strings und einem Random-Objekt (ein Zufallsobjekt) durchgeführt wird. import java.util.*; public class ObjektVergleich { public static void main(String args[]) { String a = new String("hallo"); String b = new String("hallo"); Random c = new Random(); Random d = new Random(); String e = a; System.out.println( "Sind a und b das gleiche Objekt? " + (a==b)); System.out.println( "Haben a und b den gleichen Inhalt? " + a.equals(b)); System.out.println( "Sind a und e das gleiche Objekt? " + (a==e));
Java 2 Kompendium
Listing 6.7: Unterschied von Objektgleichheit und Gleichheit des Inhalts
217
Kapitel 6
Java – die Hauptbestandteile der Sprache System.out.println( "Haben a und e den gleichen Inhalt? " + a.equals(e)); System.out.println( "Sind c und d das gleiche Objekt? " + (c==d)); System.out.println( "Haben c und d den gleichen Inhalt? " + c.equals(d)); } }
Das Programm arbeitet mit fünf Variablen: drei Strings und zwei RandomObjekte. Zwei Strings werden explizit mit dem new-Operator erzeugt, sind also explizit verschiedene Objekte. Die dritte String-Variable bekommt jedoch das erste String-Objekt zugewiesen. Es handelt sich bei a und c um eine Referenz auf den gleichen Speicherbereich, d.h., die Objekte sind identisch. Die beiden Random-Objekte sind auch im Inhalt verschieden. Schauen Sie sich die Ausgabe des Programms zur Verdeutlichung an. Abbildung 6.7: Unterschied von Objektgleichheit und Gleichheit des Inhalts
Zeile 1 und 2 bestätigen, dass die beiden Variablen a und b verschiedene Objekte sind, jedoch den gleichen Wert haben, also die gleichen Zeichen in der identischen Reihenfolge enthalten sind. Zeile 3 bestätigt, dass die beiden Variablen a und e auch im Sinn der Objektgleichheit identisch sind. Daraus folgt trivialerweise die Gleichheit des Inhalts (Zeile 4). Zeile 5 und 6 bestätigen, dass c und d nicht identisch sind; weder bezüglich des Inhalts, noch im Sinne von Objektgleichheit.
218
Java 2 Kompendium
Token
Kapitel 6
Die logischen Vergleichsoperatoren Die logischen Gleichheitsoperatoren sind nicht zum Ordnen gedacht, sondern sie sagen nur aus, ob zwei Werte gleich sind oder nicht. Die Gleichheitsoperatoren haben eine niedrigere Priorität als relationalen Operatoren. Die logischen Vergleichsoperatoren können mit Operanden jeden Typs verwendet werden. Im Fall primitiver Datentypen werden die Werte der Operanden einfach verglichen. Die Vergleiche erzeugen auf jeden Fall nur boolesche Ergebnisse (Letzteres ist jedoch identisch mit gewöhnlichen Vergleichsoperatoren). Dabei stellt Java – im Gegensatz zu vielen anderen Programmiersprachen – die UND- und ODER-Verknüpfung in zwei verschiedenen Varianten zur Verfügung. Es gibt einmal die so genannte ShortCircuit-Evaluation, zum anderen die Bewertung ohne diese Technik. Bei der Short-Circuit-Evaluation eines logischen Ausdrucks wird von links nach rechts ausgewertet und eine Bewertung abgebrochen, wenn bereits ein ausgewerteter Teilausdruck die Erfüllung des gesamten Ausdrucks unmöglich macht. Mit anderen Worten: Eine Bewertung wird abgebrochen, wenn die weitere Auswertung eines Ausdrucks keine Rolle mehr spielt. Damit kann beispielsweise bei umfangreicheren Konstrukten eine Steigerung der Performance erreicht werden. Operator
Beschreibung
Bedeutung
&&
Logischer AND-Operator mit Short-Circuit-Evaluation
Die Operation x && y liefert true, wenn sowohl x als auch y true sind. Ist bereits x false, wird y nicht mehr bewertet.
||
Logischer OR-Operator mit Short-Circuit-Evaluation
Die Operation x || y liefert true, wenn mindestens einer der beiden Operanden true ist. Ist bereits x true, wird y nicht mehr bewertet.
!
Logischer NOT-Operator
Vorangestellter Operator mit einem Operanden. Umkehrung des Wahrheitswerts.
Tabelle 6.23: Logische Operatoren Teil 1
Analog zu den drei genannten logischen Vergleichsoperatoren gibt es auch hier die folgenden drei Operatoren, die schon bei den bitweisen Operatoren aufgetaucht sind.
Java 2 Kompendium
219
Kapitel 6
Java – die Hauptbestandteile der Sprache Im Prinzip beinhaltet die bitweise Betrachtungsweise bereits die logische, wenn man beachtet, dass der boolesche Datentyp nur ein Bit groß ist.
Tabelle 6.24: Logische Operatoren Teil 2
Operator
Beschreibung
Bedeutung
&
Logischer AND-Operator ohne Die Operation x & y liefert true, Short-Circuit-Evaluation wenn sowohl x als auch y true sind. Beide Operanden werden bewertet.
|
Logischer OR-Operator ohne Short-Circuit-Evaluation
Die Operation x | y liefert true, wenn mindestens einer der beiden Operanden true ist. Beide Operanden werden bewertet.
^
EXKLUSIV-ODER
Die Operation x ^ y liefert true, wenn beide Operanden verschiedene Wahrheitswerte haben.
Das nachfolgende Beispiel demonstriert die Verwendung von den logischen Operatoren (auch im Hinblick auf die binäre Anwendung bei der bitweisen Betrachtungsweise). Abbildung 6.8: Die Anwendung von logischen Operatoren
Listing 6.8: Logische Operatoren
220
class ShortCircuit { public static void main (String args[]) { boolean a=true; boolean b=false; int i=4; int j=5; System.out.println(!a);
Java 2 Kompendium
Token
Kapitel 6
System.out.println(a&&b); System.out.println(a||b); System.out.println(a&b); System.out.println(a|b); System.out.println(a^b); System.out.println(i&j); System.out.println(i|j); System.out.println(i^j); } }
Der Komma-Operator wird nur zur Trennung verwendet – etwa im Rahmen von for-Schleifen. Viele Quellen zu Java geben das Komma gar nicht als eigenen Operator an. Wir kommen darauf zurück, wenn er verwendet wird. Weitere Operatoren und einige Zusatzinformationen werden wir im Zusammenhang mit Ausdrücken behandeln, weil diese Operatoren doch einige weitere Grundlagen zu Datentypen voraussetzen und diese nach den Kommentaren abgehandelt werden.
6.1.7
Operatoren-Priorität
Wie jede Programmiersprache muss auch Java Operatoren nach Prioritäten gewichten. In der folgenden Tabelle sollen sämtliche Operatoren von Java aufgelistet werden, wobei der Operator mit höchstem Vorrang ganz oben steht. Operatoren in der gleichen Zeile haben gleiche Priorität. Sämtliche Java-Operatoren bewerten mit Ausnahme der einstelligen Operatoren von links nach rechts. Beschreibung
Operatoren
Hochvorrangig
. [] ()
Einstellig
+ – ~ ! ++ -- instanceof
Multiplikativ
* / %
Additiv
+ –
Relational
< <= >= > >
Gleichheit
== !=
Bitweises AND
&
Bitweises XOR
^
Bitweises OR
|
Short-turn AND
&&
Java 2 Kompendium
Tabelle 6.25: Die Java-Operatoren nach Priorität geordnet
221
Kapitel 6 Tabelle 6.25: Die Java-Operatoren nach Priorität geordnet (Forts.)
Java – die Hauptbestandteile der Sprache
Beschreibung
Operatoren
Short-turn OR
||
Bedingung
?:
Zuweisung
=
6.1.8
und alle Zuweisungoperatoren mit verbundener Operation
Kommentare
Java unterstützt drei verschiedene Kommentararten. Weder Kommentare noch deren Inhalte sind als echte Token zu verstehen. Java besitzt neben den zwei in C/C++ vorhandenen Kommentartypen – den so genannten traditionellen Kommentaren aus der Tradition der C-Sprache – noch einen weiteren Kommentartyp, der vom Dokumentationstool javadoc verwendet wird. Traditionelle Kommentare Einer der traditionellen C-artigen Kommentare beginnt mit einem Slash, gefolgt von einem Stern (/*), schließt beliebigen Text ein und endet mit den gleichen Zeichen in umgekehrter Reihenfolge – einem Stern gefolgt von einem Slash (*/). Diese Form von Kommentar kann überall beginnen und aufhören, mit Ausnahme innerhalb eines Zeichenketten-Literals, eines Zeichenliterals und eines anderen Kommentars. Letzteres bedeutet vor allem, dass Kommentare gleichen Typs nicht verschachtelt werden können. Bei einem verschachteln Konstrukt kann es zu einem Fehler des Compilers kommen. Diese Form eines Kommentars kann sich über mehrere Zeilen erstrecken oder nur in einer einzigen Zeile (außerhalb eines Token) enthalten sein. Man nutzt sie gerne, um ganze Teile eines Programms auszukommentieren und sie bei Bedarf wieder zur Verfügung zu haben, indem man einfach Anfang- und Endzeichen des Kommentars löscht. Der zweite der traditionellen Kommentare beginnt mit einem Doppel-Slash (//) und endet mit dem Zeilenende. Das bedeutet, alle Zeichen in einer Zeile hinter dem Doppel-Slash werden vom Compiler ignoriert. Man nutzt diese Kommentarform gerne, wenn nur eine Zeile eines Programms stört und man sie bei Bedarf wieder zur Verfügung haben möchte, indem man einfach die Kommentarzeichen löscht. javadoc-Kommentare Diese nicht in C/C++ vorhandene Kommentarart in Java ist ein Spezialfall der ersten Form des traditionellen Kommentars. Sie hat die gleichen Eigenschaften, allerdings kann der Inhalt dieses Kommentars noch in einer vom javadoc-Werkzeug automatisch generierten Dokumentation verwendet werden. Er beginnt mit /** und endet mit */. Innerhalb des Containers befindet sich der Kommentar. Das nachfolgende Beispiel zeigt die Anwendung. 222
Java 2 Kompendium
Typen
Kapitel 6
/** * Um was es geht. * @see Querverweis. * @version 3.42 * @author Ralph Steyer */ class HelloWorld { /* traditionelle Kommentarform */ public static void main (String args[]) { System.out.println("Hello Java!"); // Ausgabe } }
Listing 6.9: Beispiel für Kommentare
Beachten Sie, dass der javadoc-Kommentar außerhalb der Klassendefinition steht. Dies ist deshalb wichtig, weil der Generator bei einer Verschachtelung mit einer Klassendefinition mit Ignoranz des Kommentars reagiert. Mehr zu den Parametern innerhalb des javadoc-Kommentars finden Sie bei der Beschreibung von javadoc in dem Kapitel zu den JDK-Tools.
6.2
Typen
Ein Typ bzw. Datentyp gibt in einer Computersprache an, wie ein einfaches Objekt (wie zum Beispiel eine Variable) im Speicher des Computers dargestellt wird. Er enthält normalerweise ebenfalls Hinweise darüber, die Operationen mit und an ihm ausgeführt werden können. Viele Computersprachen lassen es beispielsweise nicht zu, dass mit einer alphanumerischen Zeichenfolge direkte arithmetische Operationen durchgeführt werden, außer es besteht eine explizite Anweisung, diese Zeichenfolge zuerst in eine Zahl umzuwandeln. Java besitzt, wie schon mehrfach erwähnt, acht primitive Datentypen: Vier Ganzzahltypen mit unterschiedlichen Wertebereichen Einen logischen Datentyp Zwei Gleitzahltypen mit unterschiedlichen Wertebereichen nach der internationalen Norm IEEE Standard for Binary Floating-Point Arithmetic, ANSI/IEEE Std. 754-1985 (IEEE, New York) zur Definition von Gleitpunktzahlen und Arithmetik Einen Zeichentyp Der Begriff »primitiv« ist übrigens nicht abwertend zu verstehen und erklärt sich dadurch, dass diese Datentypen im System integriert und nicht als Objekten zu verstehen sind.
Java 2 Kompendium
223
Kapitel 6
Java – die Hauptbestandteile der Sprache An dieser Stelle soll noch einmal explizit darauf hingewiesen werden, dass in Java die primitiven Datentypen plattformunabhängig sind. Eine weitere wichtige Eigenschaft von primitiven Datentypen in Java ist, dass sie immer einen wohldefinierten Default-Anfangswert haben. Im Gegensatz zu einigen anderen Programmiersprachen muss deshalb ein primitiver Datentyp nicht unbedingt mit einem Anfangswert manuell initialisiert werden, sondern ist sozusagen von Natur aus einsatzbereit. Eine Ausnahme bilden mit primitiven Datentypen definierte lokale Variablen, welchen auf jeden Fall vor einer Verwendung ein Anfangswert zugewiesen werden muss.
Tabelle 6.26: Primitive Datentypen der JavaSprache
224
Bezeichnung
Länge
Defaultwert
Kurzbeschreibung
byte
8 Bit
0
Kleinster Wertebereich mit Vorzeichen. Wird zum Darstellen von Ganzzahlwerten (ganzzahliges Zweierkomplement) von (- 2 hoch 7 = -128) bis (+ 2 hoch 7 – 1 = 127) verwendet.
short
16 Bit
0
Kurze Darstellung von Ganzzahlwerten mit Vorzeichen als ganzzahliges Zweierkomplement von (- 2 hoch 15 = -32.768) bis (+ 2 hoch 15 – 1 = 32.767).
int
32 Bit
0
Standardwertebereich mit Vorzeichen zur Darstellung von Ganzzahlwerten (ganzzahliges Zweierkomplement). Bereich von (- 2 hoch 31 = -2.147.483.648) bis (+ 2 hoch 31 – 1 = 2.147.483.647).
long
64 Bit
0
Grö ß ter Wertebereich mit Vorzeichen zur Darstellung von Ganzzahlwerten (ganzzahliges Zweierkomplement). Wertebereich von -9.223.372.036.854.775.808 (- 2 hoch 63) bis 9.223.372.036.854.775.807 (+ 2 hoch 63 – 1).
float
32 Bit
0.0
Kürzester Wertebereich mit Vorzeichen zur Darstellung von Gleitkommazahlwerten. Dies entspricht Fließkommazahlen mit einfacher Genauigkeit, die den IEEE 7541985-Standard benutzen.
Java 2 Kompendium
Typen
Bezeichnung
Kapitel 6
Länge
Defaultwert
Kurzbeschreibung Der Wertebereich liegt ungefähr zwischen +/- 3,4E+38. Es existiert ein Literal zur Darstellung von plus/ minus unendlich, sowie der Wert NaN (Not a Number) zur Darstellung von nicht definierten Ergebnissen.
double
64 Bit
0.0
Grö ß ter Wertebereich mit Vorzeichen zur Darstellung von Gleitkommazahlwerten. Der Wertebereich liegt ungefähr zwischen +/1,8E+308. Auch diese Fließkommazahlen benutzen den IEEE-7541985-Standard. Es existiert ein Literal zur Darstellung von plus/ minus Unendlich, sowie der Wert NaN (Not a Number) zur Darstellung von nicht definierten Ergebnissen.
char
16 Bit
\u0000
Darstellung eines Zeichens des Unicode-Zeichensatzes. Zur Darstellung von alphanumerischen Zeichen wird dieselbe Kodierung wie beim ASCII-Zeichensatz verwendet, aber das höchste Byte ist auf 0 gesetzt. Der Datentyp ist als einziger primitiver Java-Datentyp vorzeichenlos! Der Maximalwert, den char annehmen kann, ist \uFFFF (siehe dazu den Abschnitt zum Unicode).
boolean
1 Bit
false
Diese können die Werte true (wahr) oder false (falsch) annehmen. Alle logischen Vergleiche in Java liefern den Typ boolean.
Tabelle 6.26: Primitive Datentypen der JavaSprache (Forts.)
Einige Bemerkungen zu Datentypen Boolesche Variablen sind dem Java-Typ boolean zugeordnet und können nur die Werte true oder false annehmen. Die Zuordnung eines booleschen Datentyps mit einer Zahl (egal ob Null oder ungleich Null) erzeugt einen Fehler durch den Compiler. Zahlenoperationen können mit diesem Typ explizit nicht durchgeführt werden. Werte vom Typ boolean sind zu allen anderen primitiven Datentypen inkompatibel und lassen sich nicht durch Casting in andere Typen überführen.
Java 2 Kompendium
225
Kapitel 6
Java – die Hauptbestandteile der Sprache Einer char-Variable kann, da sie 2 Byte lang ist, eine Ganzzahl zwischen 0 und 65535 zugewiesen werden. Ohne Konvertierung! Es wird bei einer evtl. Ausgabe dann das zugeordnete Unicode-Zeichen dargestellt. Die Umkehrung – also die Zuweisung von char-Zeichen an andere Datentypen – funktioniert nur für den Typ int ohne Probleme. Für die anderen Datentypen ist keine direkte Zuweisung möglich. Hier müssen Cast-Konstrukte helfen (Ausnahme Datentyp boolean – hier ist keinerlei Konvertierung möglich). Boolesche Literale sind nur die Schlüsselwörter true und false. Diese Schlüsselwörter können in einem Java-Quelltext überall da verwendet werden, wo es Sinn macht. Es gibt einige andere virtuelle Maschinen, die noch weitere Datentypen besitzen.
6.2.1
Operationen mit den Datentypen
Die meisten Operationen, die sich mit den unterschiedlichen Datentypen ausführen lassen, sind für alle Datentypen recht ähnlich. Es gibt jedoch einige wichtige Abweichungen. Lassen Sie uns die Operationen mit den Datentypen nach den Datentypen aufschlüsseln. Operationen mit booleschen Variablen Operationen mit booleschen Variablen sind in Java etwas Besonderes, denn der Datentyp ist nicht numerisch. Dennoch – viele der gleichen Operatorsymbole werden genauso wie bei anderen Ausdrücken verwendet. In den meisten Fällen handelt es sich bei diesen Bedeutungen um eine natürliche Erweiterung der Operationen, die mit ganzzahligen Typen durchgeführt werden. Tabelle 6.27: Operationen mit booleschen Variablen
226
Operation
Name
Bedeutung
=
Zuweisung
Einer booleschen Variable wird der Wert true oder false zugewiesen.
==
Gleichheit
Innerhalb einer Kontrollstruktur wird ein Vergleich durchgeführt. Es wird nur dann true zurückgegeben, wenn beide boolesche Operanden denselben Wert (true oder false) haben. Ansonsten wird false zurückgegeben (entspricht dem nicht-exklusiven Oder NXOR).
Java 2 Kompendium
Typen
Kapitel 6
Operation
Name
Bedeutung
!=
Ungleichheit
Innerhalb einer Kontrollstruktur wird ein Vergleich durchgeführt. Es wird nur dann true zurückgegeben, wenn beide boolesche Operanden unterschiedliche Werte (true oder false) haben. Ansonsten wird false zurückgegeben (entspricht dem exklusiven Oder XOR).
!
Logisches NOT Wenn der Operand false ist, wird true zurückgegeben und umgekehrt.
&
AND
Es wird dann und nur dann true zurückgegeben, wenn beide Operanden true sind.
¦
OR
Es wird dann und nur dann false zurückgegeben, wenn beide Operanden false sind.
^
XOR
Es wird true zurückgegeben, wenn genau ein Operand true ist. Man nennt dies ein exklusives Oder, weil der Wert eines Operanden für das Ergebnis true ausschließt, dass der Wert des anderen Operanden identisch ist.
&&
Logisches AND Für boolesche Variablen ist das logische AND das Gleiche wie AND (&): Es wird dann und nur dann true zurückgegeben, wenn beide Operanden true sind.
¦¦
Logisches OR
Für boolesche Variablen ist das logische OR das Gleiche wie OR (¦): Es wird dann und nur dann false zurückgegeben, wenn beide Operanden false sind.
?:
if-else
Diese Operation benötigt einen booleschen Ausdruck vor dem Fragezeichen. Wenn er true ist, wird der Wert vor dem Doppelpunkt zurückgegeben, ansonsten der Wert hinter dem Doppelpunkt.
Tabelle 6.27: Operationen mit booleschen Variablen (Forts.)
Operationen mit Zeichenvariablen Der in Java verwendete Unicode-Standard ermöglicht den Gebrauch von Alphabeten vieler verschiedener Sprachen. Das lateinische Alphabet, die Zahlen und Satzzeichen haben in der Kodierung die gleichen Werte wie der ASCII-Zeichensatz. Zeichenvariablen können Operanden in jeder ganzzahligen Operation sein und werden dabei wie ganze 16-Bit-Zahlen ohne Vorzeichen behandelt. Das Resultat einer solchen Operation ist entweder vom Datentyp int oder long. Wenn beide Operanden char-Zeichen sind, ist auch der resultierende DatenJava 2 Kompendium
227
Kapitel 6
Java – die Hauptbestandteile der Sprache typ immer ein Zeichen. Falls man ein numerisches Ergebnis als Zeichen ausdrücken will, muss eine explizite Umwandlung in den Datentyp char erfolgen. Wenn Sie Zeichen in einen kleineren Typ umwandeln, werden Sie unter Umständen Informationen verlieren. Dies betrifft beispielsweise den HanZeichensatz (Chinesisch, Japanisch oder Koreanisch), wenn Sie eine Variable vom Datentyp char in eine Variable vom Datentyp short umwandeln. Operationen mit nur einer Zeichenvariable als Operanden (beispielsweise Zuweisungsoperationen, logische Verneinungen oder Inkrement- und Dekrementoperationen) bedürfen keiner besonderen Konvertierung. Die einstelligen Vorzeichenoperatoren (+ und -) haben keinerlei Bedeutung für Operanden vom Typ char, da diese keine Vorzeichen haben.
Tabelle 6.28: Operationen mit Zeichen-Variablen (mindestens einer der Operanden ist vom Typ char)
228
Operation
Name
Bedeutung
=
Zuweisung
Einer Zeichenvariable wird ein Wert zugewiesen.
==
Gleichheit
Innerhalb einer Kontrollstruktur wird ein Vergleich durchgeführt. Es wird nur dann der boolesche Wert true zurückgegeben, wenn beide Operanden denselben Wert (im Sinne von gleichem Unicode-Wert) haben. Ansonsten wird false zurückgegeben.
!=
Ungleichheit
Innerhalb einer Kontrollstruktur wird ein Vergleich durchgeführt. Es wird nur dann der boolesche Wert true zurückgegeben, wenn beide Operanden unterschiedliche Werte (im Sinne von gleichem Unicode-Wert) haben. Ansonsten wird false zurückgegeben.
<, <=, >, >=
Relational
Weitere Operatoren zum Vergleich innerhalb einer Kontrollstruktur. Mit diesen Operatoren wird auf Ungleichheit im Sinn »kleiner«, »kleinergleich«, »grö ß er«, »grö ß ergleich« der Werte (im Sinne von gleichem UnicodeWert) abgeprüft.
+,-
Vorzeichen
Vorzeichenoperatoren bei einem Operanden.
+, –, *, /
binäre Arithmetik Additions-, Subtraktions-, Multiplikations-, und Divisionsoperatoren zur Arithmetik mit Zeichenvariablen. Die Zeichenvariablen gehen in die Berechnung mit ihrem Unicode-Wert ein.
+=, -=, *=, /=
Zuweisung
Additions-, Subtraktions-, Multiplikations-, und Divisionszuweisungen.
Java 2 Kompendium
Typen
Kapitel 6
Operation
Name
++, --
binäre Arithmetik Inkrement- und Dekrementoperatoren für den Unicode-Wert der Zeichenvariablen.
<<, >>, >>>
Verschiebung
Bitweise Verschiebungsoperatoren. Operatoren für bitweise Verschiebung nach links, für bitweise Verschiebung nach rechts und für bitweise Verschiebung nach rechts mit Füllnullen.
<<=, >>=, >>>=
Verschiebung und Zuweisung
Bitweise Verschiebungs- und Zuweisungsoperatoren (nach links, nach rechts und nach rechts mit Füllnullen).
~
Bitweises NOT
Bitweiser logischer Verneinungsoperator (Komplementieren). Eine einstellige bitweise Operation. Wenn ein Zeichen komplementiert wird, werden alle seine Bits invertiert.
&
Bitweises AND
Wenn Sie den AND-Operator (&) mit zwei Zeichen benutzen und das Ergebnis in einem dritten Zeichen ablegen, dann hat nur das resultierende Zeichen nur für die Bits den Eintrag 1, wenn alle beide Operanden an der gleichen Stelle Bits mit dem Wert 1 hatten.
|
Bitweises OR
Wenn Sie den OR-Operator(|) mit zwei Zeichenvariablen benutzen und das Ergebnis in einem dritten Zeichen ablegen, dann hat nur das resultierende Zeichen nur für die Bits den Eintrag 1, wenn einer der Operanden ein Bit an dieser Position = 1 hatte.
^
Bitweises exklusives OR
Wenn Sie den XOR-Operator (^) (exklusives OR) mit zwei Zeichenvariablen benutzen und das Ergebnis in einem dritten Zeichen ablegen, dann hat nur das resultierende Zeichen nur für die Bits den Eintrag 1, wenn das dazugehörige Bit in genau einem der beiden Operanden-Bytes gesetzt wird. Es muss und darf genau einer der Operanden ein Bit an dieser Position auf 1 gesetzt haben.
&=, |=, ^=
Bitweise Zuweisung
Bitweise AND-, OR-, exklusive OR (XOR)und Zuweisungsoperatoren.
Java 2 Kompendium
Bedeutung
Tabelle 6.28: Operationen mit Zeichen-Variablen (mindestens einer der Operanden ist vom Typ char) (Forts.)
229
Kapitel 6 Listing 6.10: Operationen mit char-Variablen
Java – die Hauptbestandteile der Sprache class CharOperationen { public static void main (String args[]) { char meinChar1='a'; char meinChar2='b'; int b=5; System.out.println(meinChar1==meinChar2); System.out.println(meinChar1==b); System.out.println(meinChar1<meinChar2); } }
Abbildung 6.9: Die Anwendung von char-Operationen
Diejenigen, welchen die verkürzte Schreibweise von Operatoren nicht ganz so vertraut ist, können im Anhang eine kleine Erläuterung finden. Operationen mit Gleitkommazahlen Gleitkommazahlen oder Fließkommazahlen bestehen in Java immer (d.h. auf jeder Plattform) aus einer so genannten Zweier-Komplement-Repräsentation der ganzen Zahlen. Der eine Teil der Darstellung wird für den Nachkomma-Anteil und das Vorzeichen verwendet, der zweite Teil ist die Darstellung eines Exponenten zur Basis 2 mit einer Ausgleichszahl. Im Anhang finden Sie einige Hintergrundinformationen zu der Theorie des Zweier-Komplements. Daneben existieren einige spezielle Bitkonfigurationen mit besonderer Bedeutung: negative Unendlichkeit Null positive Unendlichkeit keine Zahl 230
Java 2 Kompendium
Typen
Kapitel 6
Die Fließkommaoperationen selbst sind in Java für alle Plattformen gültig, was in vielen anderen Programmiersprachen nicht zutrifft. Die meisten Fließkommaoperationen und Ganzzahloperationen sind identisch. Wichtigste Ausnahmen sind die Fließkommaoperationen, die den Nachkommateil betreffen und bei Ganzzahloperationen natürlich keinen Sinn machen. Damit sind binäre Verschiebungen gemeint, die insofern unsinnig sind, dass damit eine kaum kontrollierbare Verschiebung von Nachkommateil in den Exponten-Anteil und umgekehrt erfolgen könnte. Operation
Name
Bedeutung
=,+=, -=, *=, /=
Zuweisung
Einer Gleitzahlvariablen wird ein Wert zugewiesen, wobei bei einer arithmetischen Zuweisung die entsprechende arithmetische Operation vorher durchgeführt wird.
==
Gleichheit
Innerhalb einer Kontrollstruktur wird ein Vergleich durchgeführt. Es wird nur dann der boolesche Wert true zurückgegeben, wenn beide Operanden denselben Wert haben. Ansonsten wird false zurückgegeben.
!=
Ungleichheit
Innerhalb einer Kontrollstruktur wird ein Vergleich durchgeführt. Es wird nur dann der boolesche Wert true zurückgegeben, wenn beide Operanden unterschiedliche Werte haben. Ansonsten wird false zurückgegeben.
<, <=, >, >=
Relational
Weitere Operatoren zum Vergleich innerhalb einer Kontrollstruktur. Mit diesen Operatoren wird auf Ungleichheit im Sinn »kleiner«, »kleinergleich«, »grö ß er«, »grö ß ergleich« der Werte geprüft. Auch hier wird, unabhängig vom Typ der Operanden, immer ein boolesches Ergebnis zurückgegeben.
+,-
Vorzeichen
Vorzeichenoperatoren bei einem Operanden.
+, –, *, /
binäre Arithmetik
Additions-, Subtraktions-, Multiplikations-, und Divisionsoperatoren.
+=, -=, *=, /=
Zuweisung
Additions-, Subtraktions-, Multiplikations-, und Divisionszuweisungen.
++, --
binäre Arithmetik
Inkrement- und Dekrementoperatoren für den Wert der Variablen.
Java 2 Kompendium
Tabelle 6.29: Operationen mit den Typen float und double
231
Kapitel 6
Java – die Hauptbestandteile der Sprache Wenn eine Operation mit zwei Operanden des Typs float durchgeführt wird, ist das Ergebnis auch immer eine Variable vom Datentyp float. Analog verhält es sich mit dem Datentyp double. Wenn eine binäre Operation mindestens einen Operanden des Typs double enthält, ist das Ergebnis auch vom Datentyp double. Wenn eine Operation zwischen einem GanzzahlDatentyp und einem Gleitzahl-Datentyp durchgeführt wird, bestimmt der Gleitzahl-Datentyp den Datentyp des Ergebnisses. Gleitzahl-Datentypen können im Prinzip in jeden anderen Datentyp außer boolean konvertiert werden. Die Festlegung auf einen kleineren Typen kann
wie immer zu Informationsverlust führen. Fließkommazahlen werden in Java nach dem IEEE-754-Rundungsmodus »Runden auf den nächsten Wert« gerundet. Wenn ein Gleitzahl-Datentyp in eine ganze Zahl konvertiert wird, werden Nachkommastellen abgeschnitten. Java erzeugt keinerlei Ausnahmen bei Benutzung der Fließkomma-Arithmetik. Ein Overflow oder Überlauf (d.h. ein grö ß eres Ergebnis durch eine Operation, als durch den Wertebereich des jeweiligen Typen ausgedrückt werden kann), oder das Teilen aller möglichen Zahlen außer Null durch Null und ähnliche Operationen, resultieren in der Ausgabe von positiven bzw. negativen unendlichen Werten. Dies ist durchaus ein sinnvoller Wert, denn man kann ihn mit Vergleichsoperatoren auswerten. Ein Unterlauf (d.h. ein kleineres Ergebnis – außer Null – durch eine Operation, als durch den Wertebereich des jeweiligen Typen ausgedrückt werden kann) gibt einen speziellen Wert aus, der positiv oder negativ Null genannt wird. Das Teilen von Null durch Null ergibt den Wert NaN (keine Zahl). Auch dies ist durchaus ein sinnvoller Wert, denn man kann ihn mit Vergleichsoperatoren auswerten. Er wird den Wert false bewirken. Operationen mit ganzzahligen Variablen Ganzzahlige Variablen werden in Java allesamt als ZweierkomplementZahlen mit Vorzeichen verwendet. Tabelle 6.30: Operationen mit ganzzahligen Operanden
Operation
=,+=, -=, *=, / Zuweisung = ==
232
Name
Gleichheit
Bedeutung Einer Ganzzahl-Variablen wird ein Wert zugewiesen. Innerhalb einer Kontrollstruktur wird ein Vergleich durchgeführt. Es wird nur dann der boolesche Wert true zurückgegeben, wenn beide Operanden denselben Wert haben. Ansonsten wird false zurückgegeben.
Java 2 Kompendium
Typen
Kapitel 6
Operation
Name
Bedeutung
!=
Ungleichheit
Innerhalb einer Kontrollstruktur wird ein Vergleich durchgeführt. Es wird nur dann der boolesche Wert true zurückgegeben, wenn beide Operanden unterschiedliche Werte haben. Ansonsten wird false zurückgegeben.
<, <=, >, >=
Relational
Weitere Operatoren zum Vergleich innerhalb einer Kontrollstruktur. Mit diesen Operatoren wird auf Ungleichheit im Sinn »kleiner«, »kleinergleich«, »grö ß er«, »grö ß ergleich« der Werte geprüft. Auch hier wird, unabhängig vom Typ der Operanden, immer ein boolesches Ergebnis zurückgegeben.
+,-
Vorzeichen
Vorzeichenoperatoren bei einem Operanden.
+, –, *, /
binäre Arithmetik Additions-, Subtraktions-, Multiplikationsund Divisionsoperatoren.
+=, -=, *=, /=
Zuweisung
++, --
binäre Arithmetik Inkrement- und Dekrementoperatoren für den Wert der Variablen.
<<, >>, >>>
Verschiebung
Bitweise Verschiebungsoperatoren. Operatoren für bitweise Verschiebung nach links, für bitweise Verschiebung nach rechts und für bitweise Verschiebung nach rechts mit Füllnullen.
<<=, >>=, >>>=
Verschiebung und Zuweisung
Bitweise Verschiebungs- und Zuweisungsoperatoren (nach links, nach rechts und nach rechts mit Füllnullen).
~
Bitweises NOT
Bitweiser logischer Verneinungsoperator (Komplementieren). Eine einstellige bitweise Operation. Wenn ein Zeichen komplementiert wird, werden alle seine Bits invertiert.
&
Bitweises AND
Wenn Sie den AND-Operator (&) mit zwei Ganzzahlen benutzen und das Ergebnis in einer dritten Ganzzahlen ablegen, dann hat nur die resultierende Ganzzahl nur für die Bits den Eintrag 1, wenn alle beide Operanden an der gleichen Stelle Bits mit dem Wert 1 hatten.
Java 2 Kompendium
Tabelle 6.30: Operationen mit ganzzahligen Operanden (Forts.)
Additions-, Subtraktions-, Multiplikationsund Divisionszuweisungen.
233
Kapitel 6 Tabelle 6.30: Operationen mit ganzzahligen Operanden (Forts.)
Java – die Hauptbestandteile der Sprache
Operation
Name
Bedeutung
|
Bitweises OR
Wenn Sie den OR-Operator (|) mit zwei Ganzzahlen benutzen und das Ergebnis in einer dritten Ganzzahlen ablegen, dann hat nur die resultierende Ganzzahlen nur für die Bits den Eintrag 1, wenn einer der Operanden ein Bit an dieser Position mit dem Wert 1 hatte.
^
Bitweises exklusives OR
Wenn Sie den XOR-Operator (^) (exklusives OR) mit zwei Ganzzahlen benutzen und das Ergebnis in einer dritten Ganzzahl ablegen, dann hat nur die resultierende Ganzzahl nur für die Bits den Eintrag 1, wenn das dazugehörige Bit in genau einem der beiden Operanden-Bytes gesetzt wird. Es muss und darf genau einer der Operanden ein Bit an dieser Position auf 1 gesetzt haben.
&=, |=, ^=
Bitweise Zuweisung
Bitweise AND-, OR-, ausschließliche OR (XOR)- und Zuweisungsoperatoren.
Wenn eine Operation mit zwei Ganzzahl-Operanden durchgeführt wird, ist das Ergebnis immer vom Datentyp int oder long. Das Ergebnis wird nur dann als long dargestellt, wenn einer der Operanden vom Datentyp long war oder das Ergebnis nicht ohne Überlauf in einer Variable vom Datentyp int darzustellen ist. Die Datentypen byte oder short können als Ergebnis nur vorkommen, wenn es explizit festgelegt wird. Wenn eine Operation zwischen einem Ganzzahl-Datentyp und einem Gleitzahl-Datentyp durchgeführt wird, bestimmt der Gleitzahl-Datentyp den Datentyp des Ergebnisses. Die vier ganzzahligen Typen können im Prinzip in jeden anderen Datentyp außer boolean konvertiert werden. Die Festlegung auf einen kleineren Typen kann wie immer zu Informationsverlusten führen. Die Konvertierung in eine Fließkommazahl (float oder double) führt normalerweise zu Ungenauigkeiten, außer die ganze Zahl ist eine Zweierpotenz. Wenn irgendeine Operation eine Zahl erzeugt, die den erlaubten Wertebereich eines Datentyps überschreitet, wird die Ausnahme oder der Überlauf nicht angezeigt. Als Ergebnis werden die unteren Bits, die noch hineinpassen, zurückgegeben. Nur wenn der rechte Operand beim Teilen einer ganzen Zahl oder bei einer Modulus-Operation Null ist, wird eine arithmetische Ausnahme erzeugt.
234
Java 2 Kompendium
Datenfelder (Arrays)
6.3
Kapitel 6
Datenfelder (Arrays)
Datenfelder (Arrays) gehören zwar nicht zu den primitiven Datentypen, sondern neben Klassen und Schnittstellen zu den Referenzvariablen. Da sie jedoch von der Logik gut zu den primitiven Datentypen passen, aus ihnen oft zusammengesetzt werden und vergleichsweise einfach sind, werden wie sie hier im Zusammenhang behandeln. Arrays sind in Java gegenüber anderen Programmiersprachen wie C/C++ oder PASCAL anders konzipiert. Ein Datenfeld ist eine Ansammlung von Objekten eines bestimmten Typs (es sind keine verschiedenen Typen innerhalb eines Arrays erlaubt, allerdings kann ein Array selbst wieder Arrays enthalten und damit bewirkt das keinerlei Einschränkung), die über einen laufenden Index adressierbar sind. Arrays sind also auch selbst nichts anderes als (besondere) Objekte. Sie werden wie normale Objekte dynamisch angelegt und am Ende ihrer Verwendung vom Garbage Collector beseitigt. Weiterhin stehen in Arrays als Ableitung von Object alle Methoden dieser obersten Klasse zur Verfügung. Array-Bezeichner haben wie normale Objektvariablen einen Verweistyp. Es kann sich bei Arrays um Datenfelder bestehend aus sämtlichen primitiven Variablentypen (byte, char, short, int, long, float, double, boolean), aber auch anderen Datenfeldern oder Objekten handeln. Letzteres ist besonders deshalb wichtig, da Java keine multidimensionalen Arrays im herkömmlichen Sinn unterstützt, sondern für einen solchen Fall ein Array mit Arrays erwartet (und die können wiederum weitere Datenfelder enthalten – im Prinzip beliebig viele Ebenen). Verschachtelte Arrays wäre also eine korrektere Bezeichnung. Gegenüber normalen Objekten haben Arrays zwei wesentliche Einschränkungen: Arrays haben keine Konstruktoren. Statt dessen wird der new-Operator mit spezieller Syntax aufgerufen. Es können keine Subklassen eines Arrays definiert werden. Um ein Datenfeld in Java zu erstellen, muss man drei Schritte durchführen: 1.
Deklarieren des Datenfelds
2.
Zuweisen von Speicherplatz
3.
Füllen des Datenfelds
Einige Anmerkungen zu Datenfeldern: Es ist möglich, mehrere Schritte zur Erstellung eines Datenfeldes mit einer Anweisung zu erledigen. Die Indizierung von Datenfeldern beginnt mit 0 (wie bei C und C++). Java 2 Kompendium
235
Kapitel 6
Java – die Hauptbestandteile der Sprache Sie können ein Datenfeld nur teilweise bei der Initialisierung füllen. Datenfeldindizes müssen entweder vom Typ int (ganze 32-Bit-Zahl) sein, oder als int festgesetzt werden. Daher ist die grö ß tmögliche Datenfeldgrö ß e 2.147.483.647. Datenfelder können in Java durch die Verwendung des new-Operators dynamisch erstellt werden. Ein Datenfeld ist in Java – wie schon angedeutet – eine Variable, kein Zeiger. Oder genauer, ein Array ist ein so genannter Referenztyp, ein besonderer Typ von Objekt. Die Besonderheit beruht darauf, dass Arrays »klassenlose« Objekte sind. Sie werden vom Compiler erzeugt, besitzen aber keine explizite Klassendefinition. Vom Java-Laufzeitsystem werden Arrays wie gewöhnliche Objekte behandelt. Das hat weitreichende (positive) Konsequenzen. So kann auf kein Datenfeldelement in Java zugegriffen werden, das noch nicht erstellt worden ist; dadurch wird das Programm vor dem Abstürzen und nicht initialisierten Zeigern (Pointer) bewahrt. Des Weiteren besitzen Arrays Methoden und Instanzvariablen.
6.3.1
Deklarieren von Datenfeldern
Im ersten Schritt beim Anlegen eines Datenfeldes muss immer eine Variable erstellt werden, in welcher das Datenfeld gespeichert werden soll. Die Technik ist identisch mit anderen Variablen. Es müssen der Datentyp der Variable (byte, char, short, int, long, float, double, boolean oder ein Objekttyp) und der Name der Variablen festgelegt werden. Im Unterschied zu normalen Variablen muss jedoch mit eckigen Klammern die Variable als Datenfeld gekennzeichnet werden. Es sind im Prinzip zwei Positionen denkbar, wo diese eckigen Klammern die Variable als Datenfeld kennzeichnen können – nach der Variablen oder nach dem Datentyp der Variable. Beispiele: int antwort[]; int[] antwort;
Frage: Welche Variante ist richtig? Antwort: Beide! Java unterstützt beide Syntaxtechniken und Sie können sich die Syntax auswählen, die Ihnen am besten zusagt. Arrays mit anderen Arrays als Inhalt müssen natürlich auch deklariert werden können. Dazu wird pro Dimension einfach ein weiteres Paar an eckigen Klammern angefügt. Ein Datenfeld mit einem Datenfeld als Inhalt der Elemente wird z.B. wie folgt deklariert:
236
Java 2 Kompendium
Datenfelder (Arrays)
Kapitel 6
int antwort [] []; int[] [] [] antwort;
6.3.2
Erstellen von Datenfeldobjekten
Wir haben die Erstellung von Objekten im Allgemeinen noch nicht durchgenommen, jedoch bereits in einigen Abhandlungen (Objektorientierung von Java usw.) dazu einige Bemerkungen gemacht. Deshalb wird es Sie wahrscheinlich nicht überraschen, wenn Sie die Erzeugung von Datenfeldobjekten mittels new-Operator sehen. Dies ist die direkte Erzeugung eines Datenfeldobjekts (und eines anderen Objekts). Daneben gibt es die Möglichkeit, durch direktes Initialisieren des Array-Inhalts ein Datenfeldobjekt zu erzeugen. Die Erzeugung eines Datenfeldes mit dem new-Operator Bei der direkten Erzeugung eines Datenfeldobjekts wird eine neue Instanz eines Arrays erstellt. Das erfolgt schematisch so: new []
Beispiele: new int[5] new double[5] [7]
In der Regel wird das Array direkt einer Variablen zugewiesen. Das sieht dann schematisch so aus: = new []
Beispiele: a = new int[5] b = new double[5] [7]
Diese Variablen, welchen das Array zugewiesen werden soll, müssen vorher entsprechend (also mit der passenden Dimensionsangabe) deklariert worden sein. In der Regel fasst man diese Deklaration und die Zuweisung des Arrays in einem Schritt zusammen. Etwa wie im folgenden Beispiel: int[] antwort = new int[5];
Es entsteht ein neues Array vom Datentyp int mit fünf Elementen. Die Anzahl der Elemente muss bei der direkten Erzeugung eines Datenfeldobjekts angegeben werden.
Java 2 Kompendium
237
Kapitel 6
Java – die Hauptbestandteile der Sprache Beim Erzeugen eines Datenfeldobjekts mit new werden alle Elemente des Arrays automatisch initialisiert. Dabei gelten die Defaultwerte des jeweiligen Datentyps (false für boolesche, \0 für Zeichen-Arrays und 0 bzw. 0.0 für alle numerischen Datenfelder). Erzeugung eines Datenfeldes mit direktem Initialisieren des Array-Inhalts Für diese Technik der Erzeugung eines Datenfeldobjekts müssen Sie nach dem Gleichzeichen die Elemente des Arrays in geschweifte Klammern und mit Komma getrennt angegeben. Etwa wie in dem nachfolgenden Beispiel: int[] antwort = {41,42,43,44};
Ein Array mit der Anzahl der angegebenen Elemente wird automatisch erzeugt. Ein Datenfeld mit einem Datenfeld als Inhalt der Elemente wird z.B. wie folgt erzeugt (2x2-Array): int[][] antwort = {{41,42},{43,44}};
Diese Technik der Erzeugung und Initialisierung von Arrays nennt man auch »Literale Initialisierung«.
6.3.3
Dynamische Arrays
Es ist in Java extrem leicht, dynamische Arrays zu erstellen. Genau genommen ist es quasi »natürlich«, denn Arrays werden unter Java grundsätzlich erst zur Laufzeit erzeugt. Arrays unter Java werden als semidynamisch bezeichnet. Das bedeutet, die Grö ß e eines Arrays kann bei Bedarf erst zur Laufzeit festgelegt werden. Nach der Festlegung kann die Grö ß e des Arrays jedoch nicht mehr geändert werden. Dieses dynamische Verhalten der Java-Arrays beruht darauf, dass zum Zeitpunkt der Deklaration noch nicht festgelegt wird, wie viele Elemente das Array hat. Dies geschieht erst bei der Initialisierung. Nur wenn man Deklaration und Initialisierung in einem Schritt erledigt, hat man das Array quasi statisch erzeugt. Ein wichtiger Fall von der dynamischen Festlegung der Array-Grö ß e kennen Sie bereits. Die Übergabewerte an ein Java-Programm, die in der main()Methode angegeben werden, sind ein Array (String[] args bzw. String args[]), dessen Grö ß e erst zur Laufzeit dynamisch bestimmt wird (einfach auf Grund der Tatsache, wie viele Übergabewerte vom Aufrufer mitgegeben werden). Der Programmcode legt die Grö ß e nicht fest.
238
Java 2 Kompendium
Datenfelder (Arrays)
6.3.4
Kapitel 6
Speichern und Zugreifen auf Datenfeldelemente
Einen Weg, um Elemente von Arrays mit Inhalt zu versehen, haben wir gerade gesehen: die literale Initialisierung. Dies ist aber nur für die anfängliche Bestückung zu verwenden und hilft im Fall der direkten Erzeugung eines Datenfeldobjekts nicht weiter. Wir benötigen eine Technik, um auf die Werte von sämtliche Elementen eines Arrays zugreifen, sie zu testen und manipulieren zu können. Also Zugreifen auf Datenfeldelemente im allgemeinen Sinn. Zugreifen auf Elemente von Datenfeldern mit primitiven Datentypen Wenn Sie schon mit Arrays gearbeitet haben, wird Ihnen bei normalen Arrays mit primitiven Datentypen keine Überraschung bevorstehen – Sie geben einfach den Namen der Datenfeldvariable und in eckigen Klammern den Index an. Um auf das zweite Element des Datenfelds int[] antwort = {41,42,43,44};
zugreifen zu können, werden wir einfach antwort [1];
angeben. Der Index 1 für das zweite (!) Element unseres Datenfeldes ist kein Fehler. Der Index eines Arrays beginnt mit 0 (wie bei C/C++). Wenn Sie bei einem Array versuchen, auf Elemente zuzugreifen, die sich außerhalb der Array-Grenzen befinden, wird entweder ein Kompilierungsfehler oder ein Laufzeitfehler (genauer – eine Ausnahme) erzeugt. Der erste Fehler tritt ein, wenn innerhalb des Quelltexts eine falsche Zuweisung vom Compiler bereits erkennbar ist, der zweite Fall, wenn das Array-Element erst zur Laufzeit berechnet wird. Eine solche Ausnahme ist dann von der Form ArrayIndexOutOfBoundsException. Auf jeden Fall wird der Fehler abgefangen und kann nicht wie bei C einen Pointer ins Nirwana schicken und damit übelste Systemabstürze verursachen. Ändern von Elemente in Datenfeldern mit primitiven Datentypen Die Änderung von Werten in Datenfeldern mit primitiven Datentypen ist identisch mit der Änderung von Werten normaler Variablen. Geben Sie einfach den Namen an und weisen Sie einen Wert zu. Beispiel: antwort [2] = 42;
Java 2 Kompendium
239
Kapitel 6
Java – die Hauptbestandteile der Sprache Wir wollen Arrays in einem Beispiel testen:
Listing 6.11: Verschiedene Arrays
class Array1 { public static void main (String args[]) { int i; // Datenfeld mit 10 int-Werten // – nicht gefüllt int[] datenfelder = new int[10]; // fülle Datenfelder for (i=0;i < 10; i++) datenfelder[i] = i; // Ausgabe for (i=9;i > -1; i--) System.out.print(datenfelder[i] + " "); System.out.println("\n____________________"); // andere Variante für ein Datenfeld mit // 10 char-Werten – nicht gefüllt char datenfelder2[] = new char[10]; // fülle Datenfelder for (i=0;i < 10; i++) datenfelder2[i] = (char)(i*5); // Ausgabe for (i=9;i > -1; i--) System.out.print(datenfelder2[i] + " "); System.out.println("\n____________________"); // Datenfeld mit 10 long-Werten und gleich // bei der Deklaration Speicher zugewiesen // und gefüllt long[] fi = {1,2,3,5,7,11,13,17,23,29}; for (i=9;i > -1; i--) System.out.print(fi[i] + " "); System.out.println("\n____________________"); // andere Variante für ein Datenfeld mit // int-Werten und gleich bei der Deklaration // Speicher zugewiesen und gefüllt int NochEinFeld[] = {1,2,3}; for (i=0;i < 3; i++) System.out.print(NochEinFeld[i] + " "); } }
Dieses Programmbeispiel enthält neben Datenfeldern einige interessante Java-Techniken, die wir an dieser Stelle diskutieren wollen. Die Kommentare innerhalb des Source erklären bereits einige Schritte, aber es gibt noch einige Dinge, auf die gesondert eingegangen werden sollte. Die Zuweisung von Inhalt für die Elemente der Arrays erfolgt in den oben beschriebenen Techniken jeweils über for-Schleifen oder bereits durch direktes Initialisieren des Array-Inhalts. Dabei werden Arrays mit verschiedenen Datentypen verwendet. Zum Teil wird Casting verwendet. Beachten
240
Java 2 Kompendium
Datenfelder (Arrays)
Kapitel 6
Sie die Trennlinie, die mit einem einleitenden \n beginnt. Damit wird ein Zeilenvorschub innerhalb eines Strings ausgelöst. Mittels der for-Schleife for (i=9;i > -1; i--) erfolgt eine Ausgabe, indem der Index der Datenfelder von hinten nach vorne durchgezählt wird. Dies ist genauso zulässig. Dabei wird der Dekrementoperator (--) verwendet, um innerhalb der for-Schleife in den angegebenen Grenzen herabzuzählen. Abbildung 6.10: Eindimensionale Arrays
Multidimensionale Datenfelder Wir hatten bereits festgehalten, dass Java keine multidimensionalen Arrays im herkömmlichen Sinn unterstützt, sondern für diesen Fall ein Array mit Arrays erwartet (und die können wiederum weitere Datenfelder enthalten – im Prinzip beliebig viele Ebenen). Wir wollen der Einfachheit halber dennoch von multidimensionalen Arrays reden (obwohl verschachtelte Arrays eigentlich zutreffender wäre). Die Deklaration und Erzeugung haben wir schon gesehen, aber wie erfolgt der Zugriff? Hier ist tatsächlich eine Umstellung gegenüber Arrays in anderen Programmiersprachen notwendig. Ein Zugriff auf das zweite Element in der zweiten Spalte erfolgt beispielsweise über antwort [2][2];
Eine Zuweisung erfolgt analog über antwort [2][2] = 42;.
Der Unterschied gegenüber Arrays in anderen Programmiersprachen ist, dass die eckigen Klammern in der beschriebenen Weise angegeben werden müssen. Ein Zugriff in der Form antwort[2, 2] oder ähnlich wird einen Fehler erzeugen. Java 2 Kompendium
241
Kapitel 6
Java – die Hauptbestandteile der Sprache Das nachfolgende kleine Beispiel arbeitet mit multidimensionalen Arrays.
Listing 6.12: Multidimensionales Array
public class Array2 { public static void main(String args[]) { int a[][] = new int[2][3]; a[0][0] = 1; a[0][1] = 2; a[0][2] = 3; a[1][0] = 4; a[1][1] = 5; a[1][2] = 6; System.out.println("" + a[0][0] + a[0][1] + a[0][2]); System.out.println("" + a[1][0] + a[1][1] + a[1][2]); System.out.println(a[0][0] + a[0][1] + a[0][2]); System.out.println(a[1][0] + a[1][1] + a[1][2]); } }
Der den ersten beiden Ausgaben vorangestellte Leerstring verhindert, dass die Array-Elemente als numerische Werte zusammengezählt werden. Die Ausgaben drei und vier hingegen lassen es zu. Abbildung 6.11: Multidimensionales Array
Arrays mit allgemeinen Objekten als Inhalt der Elemente Multidimensionale Arrays sind ein spezieller Fall von Arrays mit Objekten als Inhalt der Elemente. Darin kann sich allerdings ebenso jegliche andere Form von Objekten befinden. Ein Array mit Objekten als Inhalt der Elemente enthält Referenzen auf diese Objekte. Wenn einem Datenfeldelement ein Wert zugewiesen wird, erstellen Sie eine Referenz auf das betreffende Objekt. Verschieben Sie die Werte in den Arrays, weisen Sie die Referenz neu zu. Es wird also nicht der Wert von
242
Java 2 Kompendium
Datenfelder (Arrays)
Kapitel 6
einem Element in ein anderes Element kopiert, wie es bei einem Array mit primitiven Datentypen der Fall wäre. Arrays mit Objekten als Inhalt sind eine einfache Möglichkeit, innerhalb eines Arrays verschiedene Arten von Informationen unterzubringen. Es gibt zwar in Java keine direkte Möglichkeit, ein Array mit verschiedenen Datentypen zu deklarieren, aber wenn man einfach Objekte mit unterschiedlichen Datentypen als Inhalt verwendet, ist das kein Problem (obgleich etwas Hintergrundwissen zu Java notwendig ist). Das nachfolgende kleine Beispiel arbeitet mit zwei so genannten Wrapperklassen, um dort Informationen eines bestimmten Datentyps (im Beispiel int und float) unterzubringen. Die damit erzeugten Objekte werden einem Array zugewiesen, das vom Typ eine Instanz der Klasse Number ist (der abstrakten Superklasse der beiden Wrapperklassen). Damit befinden sich in dem Array verschiedenen primitive Datentypen. Zwar in Objekte »eingepackt«, aber wie das Beispiel weiter zeigt, stehen Methoden bereit, die primitiven Werte wieder zu extrahieren. public class Array3 { public static void main(String args[]) { Integer b = new Integer(5); Float c = new Float("4.4f"); Number a[] = {b,c}; System.out.println(a[0].intValue()); System.out.println(a[1].floatValue()); } }
Listing 6.13: Daten verschiedenen Typs in einem Array
Die Ausgabe wird zuerst der Ganzzahlwert 5 und dann der Gleitzahlwert 4.4 sein. Es ist sogar in Java mit dieser Technik möglich, nicht-rechteckige Arrays zu erzeugen. Damit werden Verfahren wie Records oder Typedef-Konstrukte, wie sie in anderen Programmiersprachen vorkommen, überflüssig. Das nachfolgende Beispiel demonstriert eine solche Technik. public class Array4 { public static void main(String args[]) { int a[][] = { {8}, {7, 7}, {6, 6, 6}, {5, 5, 5, 5}, {4, 4, 4, 4, 4}, {1, 2, 3, 4, 5, 6} }; for (int i=0; i < a.length; i++) { for (int j=0;j
Java 2 Kompendium
Listing 6.14: Ein nicht-rechteckiges Array
243
Kapitel 6
Java – die Hauptbestandteile der Sprache
}
} System.out.println(""); } }
Beachten Sie in dem Beispiel, dass wir dort viele interessante Details ausnutzen: Zwei Zählvariablen, die beide erst innerhalb der for-Schleife deklariert werden (das nennt man schleifenlokal). Der Inkrementoperator sowohl voran- als auch nachgestellt. Zwei ineinander verschachtelte for-Schleifen. Das Charakteristikum von Arrays, Objekte zu sein, lässt uns Methoden und Eigenschaften verwenden, die auf jedem Objekt bereitstehen. Etwa die Eigenschaft length, mit der die Grö ß e jeder Dimension bestimmt werden kann. Das macht das Beispiel unempfindlich gegenüber Änderungen der Dimensionen und vor allem kann man für alle Grö ß en die gleiche for-Schleife verwenden (das Abbruchkriterium wird anhand des Arrays dynamisch bestimmt). Abbildung 6.12: Ein nicht-rechteckiges Array
6.3.5
Collections
Ein Spezialfall von Arrays sind so genannte Collections. Auch diese dienen dazu, Mengen von Daten aufzunehmen. Die Daten werden aber gekapselt abgelegt und es ist nur mithilfe vorgegebener Methoden möglich, darauf zuzugreifen. Ein wichtiger Spezialfall einer Collection ist eine Hashtabelle, die die Klasse Hashtable repräsentiert. Dies ist eine Collection, in der die Daten paarweise mit einer Referenzbeziehung gespeichert sind. Collections sind im Wesentlichen im Paket java.util untergebracht. Dort gibt es etwa die Klasse Vector als Java-Repräsentation einer linearen Liste oder das Inter-
244
Java 2 Kompendium
Ausdrücke, Operatoren und Casting
Kapitel 6
face Enumeration, was den sequenziellen Zugriff auf die Elemente eines Vektors mittels eines Iterators erlaubt.
6.4
Ausdrücke, Operatoren und Casting
In diesem Abschnitt sollen die Details zu Operatoren und Ausdrücken ergänzt und besprochen werden, die bisher entweder noch nicht behandelt wurden oder scheinbar »vom Himmel gefallen« sind. Dies betrifft einige besondere Operatoren und auch den Vorgang des Castings.
6.4.1
Ausdrücke
Die Ausdrücke in Java und Ausdrücke in C/C++ sind sehr ähnlich. Sie drücken einen Wert entweder direkt oder durch Berechnung aus. Es kann sich gleichfalls um Kontrollfluss-Ausdrücke handeln, die den Ablauf der Programmausführungen festlegen. Diese Ausdrücke können Konstanten, Variablen, Schlüsselworte, Operatoren und andere Ausdrücke beinhalten. Wir haben in unseren bisherigen Beispielen natürlich schon diverse Java-Ausdrücke verwendet. Ausdruck
Beschreibung
42
arithmetische Konstante
3.141592654
arithmetische Konstante
3*4
multiplikativer Ausdruck
1 + 3 / 4
additiver Ausdruck mit Division
2^32
Exponential-Ausdruck
x=42
Zuweisungs-Ausdruck
ausgabe[42]
Datenfeldindizierung
Tabelle 6.31: Beispiele für gültige Java-Ausdrücke
Java unterstützt, außer in Initialisierungs- und Weiterführungsklauseln für Schleifen-Anweisungen – z.B. for (k=1, j=1; j+k < 65; j++, k++) – keine mit Kommata zusammengesetzten Anweisungen. Man kann Ausdrücke am einfachsten folgendermaßen definieren: Ausdrücke sind das Ergebnis der Verbindung von Operanden und Operatoren über die syntaktischen Regeln der Sprache.
Java 2 Kompendium
245
Kapitel 6
Java – die Hauptbestandteile der Sprache Ausdrücke werden also für die Durchführung von Operationen (Manipulationen) an Variablen oder Werten verwendet. Dabei sind Spezialfälle wie arithmetische Konstanten kein Widerspruch, sondern nur die leere Operation. Bewertung von Ausdrücken Ausdrücke kommen selbstverständlich auch bei komplizierten Kombinationen von Operatoren und Operanden vor. Deshalb muss Java diese Kombinationen bewerten, also eine Reihenfolge festlegen, wie diese komplexeren Ausdrücke auszuwerten sind. Das ist in der menschlichen Logik nicht anders. Sie kennen sicher die Punkt-vor-Strichrechnung in der Mathematik. Überhaupt ist die Bewertung von Ausdrücken in Java meistens durch die Bewertung von Ausdrücken in der Mathematik intuitiv herleitbar. Wir werden uns nun mit drei Begriffen auseinander setzen müssen: 1.
Operatorassoziativität
2.
Operatorvorrang
3.
Bewertungsreihenfolge
Operatorassoziativität klingt zwar am kompliziertesten, der Vorgang ist aber die einfachste der Bewertungsregeln. Vielleicht kennen Sie die Assoziativitätsregel noch aus der Schulmathematik. Alle arithmetischen Operatoren bewerten (assoziieren) Ausdrücke defaultmäß ig von links nach rechts. Das heißt, wenn derselbe Operator in einem Ausdruck mehr als einmal auftaucht – wie beispielsweise der +-Operator bei dem Ausdruck 1 + 2 + 3 – dann wird der am weitesten links erscheinende zuerst bewertet, gefolgt von dem rechts daneben usw. Unterziehen wir folgende arithmetische Zuweisung einer näheren Betrachtung: x=1 + 2 + 3;
In diesem Beispiel wird der Wert des Ausdrucks auf der rechten Seite von dem Gleichheitszeichen zusammengerechnet und der Variablen x auf der linken Seite zugeordnet. Für das Zusammenrechnen des Werts auf der rechten Seite bedeutet die Tatsache, dass der Operator + von links nach rechts assoziiert, dass der Wert von 1 + 2 zuerst berechnet wird. Erst im nächsten Schritt wird zu diesem Ergebnis dann der Wert 3 addiert. Anschließend wird das Resultat dann der Variablen x zugewiesen. Immer, wenn derselbe Operator mehrfach benutzt wird, können Sie die Assoziativitätsregel anwenden. Diese Regel ist nicht ganz so trivial, wie sie im ersten Moment erscheint. Darüber lässt sich bewusst das Prinzip des Castings beinflussen. Beachten Sie das nachfolgende kleine Beispiel.
246
Java 2 Kompendium
Ausdrücke, Operatoren und Casting class OperatorAsso { public static void main (String args[]) { System.out.println("" + 1 + 2 + 3); System.out.println(1 + 2 + 3 + ""); } }
Kapitel 6 Listing 6.15: Operatorassoziativität
Die erste Ausgabe wird 123 sein, die zweite jedoch 6. Warum? In der ersten Ausgabe wird zuerst "" + 1 bewertet. Das erzwingt ein Resultat vom Typ String. Danach wird "1" + 2 bewertet ("1" ist ein String!) und es ergibt den String "12". Dann folgt das Spiel erneut. In der zweiten Version wird zuerst 1 + 2 bewertet und es entsteht die Zahl 3. Erst im letzten Schritt wird mit 6 + "" eine Konvertierung in einen String erzwungen. Operatorvorrang bedeutet die Beachtung der Priorität von Java-Operatoren. Wenn Sie einen Ausdruck mit unterschiedlichen Operatoren haben, muss Java entscheiden, wie Ausdrücke bewertet werden. Hier ist das Beispiel der Punkt-vor-Strich-Rechnung aus der Mathematik wieder sinnvoll. Java hält sich strikt an Regeln der Operatorvorrangigkeit. Je höher ein Operator priorisiert ist, desto eher wird er bewertet. Die multiplikativen Operatoren (*, / und %) haben Vorrang vor den additiven Operatoren (+ und -). In anderen Worten: In einem zusammengesetzten Ausdruck, der sowohl multiplikative als auch additive Operatoren enthält, werden die multiplikativen Operatoren zuerst bewertet. Dies ist übrigens einfach die Punkt-vorStrich-Rechnung. Beispiel: a = b-c*d/e;
Weil die Operatoren * und / Vorrang haben, wird der Unterausdruck zuerst berechnet. Erst danach wird die Subtraktion ausgeführt. Immer wenn Sie die Bewertungsreihenfolge von Operatoren in einem Ausdruck ändern wollen, müssen Sie Klammern benutzen. Klammern haben eine höhere Priorität als arithmetische Operatoren. Jeder Ausdruck in Klammern wird zuerst bewertet. Beispiel: a = (b-c)*d/e;
Hier würde zuerst die Subtraktion von b und c durchgeführt. Auch einstellige Operatoren haben eine sehr hohe Priorität.
Java 2 Kompendium
247
Kapitel 6
Java – die Hauptbestandteile der Sprache Beispiel: a=-b*c;
Der einstellige arithmetische Minusoperator bewirkt, dass -b mit c multipliziert wird. Mehr zu der Operatoren-Priorität finden Sie auf Seite 221 Das letzte zu beachtende Detail bei der Bewertung ist die Bewertungsreihenfolge. Der Unterschied zwischen Bewertungsreihenfolge und Operatorvorrang ist der, dass bei der Bewertungsreihenfolge die Operanden bewertet werden. Die Bewertungsreihenfolge legt fest, welche Operatoren in einem Ausdruck zuerst benutzt werden und welche Operanden zu welchen Operatoren gehören. Außerdem dienen die Regeln für die Bewertungsreihenfolge dazu festzulegen, wann Operanden bewertet werden. Es gibt drei plattformunabhängige Grundregeln in Java, wie ein Ausdruck bewertet wird: 1.
Bei allen binären Operatoren wird der linke Operand vor dem rechten bewertet.
2.
Zuerst werden immer die Operanden, danach erst die Operatoren bewertet.
3.
Wenn mehrere Parameter, die durch Kommata voneinander getrennt sind, durch einen Methodenaufruf zur Verfügung gestellt werden, werden diese Parameter immer von links nach rechts bewertet.
6.4.2
Operatoren
Wir werden uns nun der Operatoren noch einmal unter einem anderen Gesichtspunkt annehmen. Operatoren sind in diesem Zusammenhang spezielle Symbole (eine der Schlüsselkategorien von Token in Java), die verwendet werden, um die Durchführung von Operationen (Manipulationen) an Variablen oder Werten auszuführen. Wir haben Operatoren ja schon behandelt und ein Teil der hier diskutierten Ergebnisse wird Ihnen mittlerweile bekannt sein. Allerdings ist es dennoch sogar bei Überschneidungen keine reine Wiederholung, denn wir werden für die Operatoren aus der veränderten Betrachtungsweise neue Erkenntnisse herausziehen und die noch offenen Operatoren behandeln.
248
Java 2 Kompendium
Ausdrücke, Operatoren und Casting
Kapitel 6
Die Operandenanzahl Java-Operatoren können einen, zwei oder drei Operanden haben. Operatoren, die einen Operanden haben, werden als einstellige oder monadische Operatoren bezeichnet. Einige einstellige Operatoren stehen vor dem Operanden (so genannte Präfixoperatoren), andere stehen hinter dem Operanden (sie werden Postfixoperatoren genannt). Der Dekrementoperator (--) und der Inkrementoperator (++) sind Beispiele für einstellige Operatoren. Operatoren mit zwei Operanden werden als binäre oder dyadische Operatoren bezeichnet. Die arithmetischen Operatoren sind Beispiele dafür. Es gibt in Java ebenfalls einen Operator mit drei Operanden – den Bedingungsoperator zur Abkürzung der if-Struktur. Er wird als tenärer oder triadischer Operator bezeichnet. Der triadische Operator Dieser Operator ist ein unverändert gebliebenes Überbleibsel der C-Sprache. Es handelt sich dabei um eine Abkürzung für die gewöhnlichen if-elseAusdrücke. Er besteht aus folgendem Konstrukt: [ergebnis] = [bed1][Vergloperator][bed2] ? [erg1] : [erg2]
Zur Erläuterung: Der Wert [ergebnis] bekommt den Wert [erg1] zugeordnet, wenn der Vergleich (der boolesche Ausdruck vor dem Fragezeichen) zwischen [bed1] und [bed2] das Ergebnis true gebracht hat, ansonsten bekommt [ergebnis] den Wert [erg2] zugeordnet. Ziehen wir ein praktisches Beispiel zur Verdeutlichung her. class If_oder_Nicht { public static void main (String args[]) { String ergebnis; int a = 42; int b = 21; ergebnis = a > b? "a ist groesser" : "a ist kleiner"; System.out.println(ergebnis); } }
Listing 6.16: Der triadische Operator
Das Programmierbeispiel wird den Text »a ist groesser« (der Wert von a) auf dem Bildschirm ausgeben, denn der boolesche Wert vor dem Fragezeichen hat den Wert true.
Java 2 Kompendium
249
Kapitel 6
Java – die Hauptbestandteile der Sprache Man nennt diesen Vorgang bedingte Bewertung. Trotz der weiten Verbreitung in C-Welten, sollte dieses Konstrukt um der Lesbarkeit des Quelltextes willen gar nicht oder möglichst nur sehr sparsam verwendet werden. Eine saubere if-else-Struktur ist fast genauso schnell getippt und bedeutend besser zu lesen.
6.4.3
Typkonvertierungen und der Casting-Operator
Unter Casting versteht man die Umwandlung von einem Datentypen in einen anderen Datentypen. Java ist eine streng typisierte Sprache, weil sehr intensive Typüberprüfungen stattfinden. Außerdem gelten strikte Beschränkungen für die Umwandlung (Konvertierung) von Werten eines Datentyps zu einem anderen. Java unterstützt zwei unterschiedliche Arten von Konvertierungen: Explizite Konvertierungen, um absichtlich den Datentyp eines Wertes verändern. Ad hoc-Konvertierungen ohne Zutun des Programmierers. Ad-hoc-Typkonvertierungen Java führt bei der Bewertung von Ausdrücken einige Typkonvertierungen ad hoc durch, ja sogar ohne dass Sie es unter Umständen überhaupt wissen. Dies erfolgt dann, wenn die Situation es erfordert. Einen solche Situation tritt ein, wenn bei einer Zuweisung der Typ der Variablen und der Typ des zugewiesenen Ausdruck nicht identisch sind, der Wertebereich der Zuweisung eines Ausdrucks nicht ausreicht, verschiedene Datentypen in einem Ausdruck verknüpft werden oder die an einem Methodenaufruf übergebenen Parameter vom Datentyp nicht mit den geforderten Datentypen übereinstimmen. Die Regeln für eine dann durchgeführte automatische Konvertierung sind für numerische Datentypen untereinander allerdings sehr einfach. Wenn nur Ganzzahltypen miteinander kombiniert werden, legt der grö ß te Datentyp den Ergebnisdatentyp fest. Wenn also einer der beiden Operanden den Datentyp long hat, wird der andere gleichfalls zu long konvertiert und das Ergebnis vom Typ long sein. Aus der Kombination short und int wird int, byte und short wird zu short und byte und int wird zu int. Wenn das Ergebnis einer Verknüpfung vom Wert so groß ist, dass es im so vorgesehenen Wertebereich nicht mehr dargestellt
250
Java 2 Kompendium
Ausdrücke, Operatoren und Casting
Kapitel 6
werden kann, wird der nächstgrö ß ere Datentyp genommen. So kann es vorkommen, dass aus der Verknüpfung von zwei int -Werten der Typ long entsteht. Bei Operationen mit Fließkommazahlen gelten weitgehend die analogen Regeln. Wenn wenigstens einer der Operanden den Datentyp double hat, wird der andere ebenso zu double konvertiert, und das Ergebnis ist dann ebenfalls vom Typ double. Aus zwei float-Datentypen wird allerdings nicht (!) der Typ double, wenn der Wertebereich nicht ausreicht (also keine ad-hoc-Konvertierung). Statt dessen wird der wohldefinierte Wert Infinity zurückgegeben. Es ist also offensichtlich so, dass, wenn der entstehende Datentyp grö ß er als der zu konvertierende Datentyp ist, die Konvertierung ohne Probleme ad hoc funktioniert, da keine Informationen verloren gehen können. class Casting1 { public static void main (String args[]) { byte a = 125; short b = 32000; float c = 45.9f; float d = 4E37f; double e = 4E37; System.out.println(a); System.out.println(a + 3); System.out.println((byte)(a + 3)); System.out.println(b); System.out.println(a * b); System.out.println((short)(a * b)); System.out.println(c); System.out.println(d); System.out.println(c * d); System.out.println(c * e); System.out.println((float)(c * d)); System.out.println((double)c * d); } }
Listing 6.17: Beispiele für ad-hoc-Konvertierungen
Das Beispiel verknüpft Variablen verschiedenen Datentyps, und zwar so, dass dabei das Ergebnis jeweils den Wertebereich des ursprünglichen Datentyps sprengt. Es wird automatisch gecastet, wie die jeweils nachfolgende explizite Rückkonvertierung auf den ursprünglichen Datentyp zeigt (dazu gleich mehr).
Java 2 Kompendium
251
Kapitel 6
Java – die Hauptbestandteile der Sprache
Abbildung 6.13: Beispiele für ad-hoc-Konvertierungen
Wenn in Ausdrücken Verbindungen zwischen verschiedenen Familien von Datentypen (etwa char mit int oder byte mit double) durchgeführt werden, gilt Folgendes: Der Datentyp char wird auf int gecastet. Ganzzahlen werden bei Verbindung mit Fließkommazahlen auf float oder double konvertiert (je nach Grö ß e des beteiligten FließkommaOperanden). Listing 6.18: Beispiele für ad-hoc-Konvertierungen zwischen verschiedenen Familien von Datentypen
class Casting2 { public static void main (String args[]) { byte a = 125; float b = 45.9f; float c = 4E37f; double d = 4E37; char e = 'c'; System.out.println(a + b); System.out.println(a * c); System.out.println(a * d); System.out.println(e); System.out.println(e+1); } }
Die zweite und die dritte Ausgabe zeigen, dass es einen Unterschied ausmacht, ob ein Ganzzahlwert mit einem float- oder einem double-Datentyp verknüpft wird. Ausgabe fünf zeigt die ad-hoc-Konvertierung von einem char-Datentyp in einen int-Datentyp. Grundsätzlich kann man mit dem +-Operator primitive Werte mit einem String verbinden. In diesem Fall wird immer ein String erzeugt.
252
Java 2 Kompendium
Ausdrücke, Operatoren und Casting
Kapitel 6 Abbildung 6.14: Beispiele für ad-hoc-Konvertierungen zwischen verschiedenen Familien von Datentypen
Bei booleschen Werten ist die Situation etwas komplizierter. Diese lassen sich in Java grundsätzlich nicht in andere primitive Datentypen konvertieren (weder ad hoc, noch explizit). Man muss bei Bedarf eine Hilfskonstruktion (etwa mit einer if-Struktur) verwenden. Explizite Konvertierungen Eine explizite Konvertierung ist immer dann notwendig, wenn Sie eine Umwandlung in einen anderen Datentyp wünschen und diese nicht ad hoc auf Grund der oben beschriebenen Situationen eintritt. Um beispielsweise einen in einem großen Datentyp gespeicherten Wert in eine kleineren Datentyp umzuwandeln, müssen Sie explizites Casting anwenden. Dazu müssen Sie in der Regel den Casting-Operator (manchmal Festlegungsoperator genannt) verwenden. Es gibt neben der Verwendung des Casting-Operators noch die Fälle, dass Methoden Typkonvertierungen durchführen. Der Casting-Operator besteht bei Casting auf einen primitven Datentyp nur aus einem Datentypnamen in runden Klammern. Er ist ein einstelliger Operator mit hoher Priorität und steht vor seinem Operanden. Er ist also immer von der folgenden Form: () <Wert>
Der Festlegungsoperator ergibt den Datentyp, wie er in den Klammern bezeichneten Typ festgelegt wird.
Java 2 Kompendium
253
Kapitel 6
Java – die Hauptbestandteile der Sprache Es gibt die folgenden Casting-Operatoren:
Tabelle 6.32: Casting-Operatoren von primitiven Datentypen
Operator
Beispiel
Erläuterung
(byte)
(byte) (x/y) Wandelt das Ergebnis der Division x geteilt durch y in einen Wert vom Datentyp byte um.
(short)
(short) x
Wandelt den Datentyp x in einen Wert vom Datentyp short um.
(int)
(int) (x/y)
Wandelt das Ergebnis der Division x geteilt durch y in einen Wert vom Datentyp int um.
(long)
(long) x
Wandelt den Datentyp x in einen Wert vom Datentyp long um.
(float)
(float) x
Wandelt den Datentyp x in einen Wert vom Datentyp float um.
(double)
(double) x
Wandelt den Datentyp x in einen Wert vom Datentyp double um.
(char)
(char) x
Wandelt den Datentyp x in einen Wert vom Datentyp char um.
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. Variablen eines arithmetischen Typs können auf jeden anderen arithmetischen Typen festgelegt werden. Boolesche Werte können nicht auf irgendeinen anderen Wert festgelegt werden. Konvertieren von Objekten Mit Einschränkungen lassen sich sogar Klasseninstanzen in Instanzen anderer Klassen konvertieren (sowohl ad hoc als auch explizit). Die wesentliche Einschränkung ist, dass die Klassen durch Vererbung miteinander verbunden sein müssen. Allgemein gilt, dass ein Objekt einer Klasse auf seine Superklasse festgelegt werden kann. So kann beispielsweise ein Graphics2DObjekt auf ein Graphics-Objekt konvertiert werden (Graphics ist die Superklasse von Graphics2D). Beispiel: Graphics a = (Graphics) g2d;
Bei der Subklasse (die ja in der Regel mehr Informationen enthält) gibt es im Allgemeinen Probleme. Weil eine Festlegung immer eine unbedingte Typenkonvertierung beinhaltet (sofern überhaupt eine möglich ist), ist sie als Typenzwang bekannt. Dennoch gibt es diverse Situationen, wo eine Kon-
254
Java 2 Kompendium
Ausdrücke, Operatoren und Casting
Kapitel 6
vertierung eines Objekts auf die Subklasse sinnvoll ist. Etwa, wenn man im Rahmen der paint()-Standardmethode Java 2D verwenden will. In diesem Fall castet man das Graphics-Objekt auf ein Graphics2D-Objekt, dessen fehlende Informationen dann default belegt werden. Etwa so: public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; ...// tue etwas sinnvolles }
Beim Konvertieren von einer Instanz auf eine Instanz seiner Superklasse gehen die spezifischen Informationen, welchen nur in der zu konvertierenden Subklasse vorhanden sind, natürlich verloren. Die Technik zum expliziten Konvertieren mit dem Casting-Operator ist analog dem Fall des Castings bei primitiven Datentypen. Er ist also immer von der folgenden Form: () ist der Name der Klasse, in die das Objekt konvertiert werden soll. ist eine Referenz auf das konvertierte Objekt. Casting erstellt eine neue Instanz der neuen Klasse. Das alte Objekt existiert unverändert weiter.
Konvertieren von Objekten in Schnittstellen Obwohl Schnittstellen bisher noch nicht behandelt wurden, gehört eine Diskussion der Konvertierung von Objekten in Schnittstellen der Vollständigkeit halber dazu. Mit Einschränkungen lassen sich Klasseninstanzen in Schnittstellen konvertieren. Dabei ist allerdings zwingend, dass die Klasse selbst oder eine Superklasse des Objekts die Schnittstelle implementiert. Durch Casting eines Objekts in eine Schnittstelle kann dann eine Methode dieser Schnittstelle verwendet werden, obwohl die Klasse des Objekts diese Schnittstelle unter Umständen nicht direkt implementiert hat. Konvertierung von primitiven Datentypen in Objekte und umgekehrt Wenn Sie jetzt nach der Technik fragen, wie primitive Datentypen in Objekte und umgekehrt konvertiert werden können, ist die Antwort ganz einfach: überhaupt nicht! Weder ad hoc noch durch explizites Casting. Das hat sehr weitreichende Konsequenzen, denn ein String ist ja beispielsweise in Java kein primitiver Datentyp. Soll das etwa bedeuten, dass aus einem String, der nur eine Zahl enthält, diese nicht extrahiert werden kann? Nein, natürlich nicht. Nur halt nicht per Casting.
Java 2 Kompendium
255
Kapitel 6
Java – die Hauptbestandteile der Sprache Im Java-Packet java.lang gibt es als Ersatz dafür Sonderklassen, die primitiven Datentypen entsprechen. Man nennt sie Wrapper-Klassen oder kurz Wrapper. Mit den in den Klassen definierten Klassenmethoden können Sie mithilfe des new-Operators jeweils ein Objekt-Gegenstück zu jedem primitiven Datentypen erstellen. Beispiel: Integer Objekt_vomTyp_int = new Integer(42);
Das Objekt Objekt_vomTyp_int ist eine Instanz der Klasse Integer und bekommt direkt den Wert 42 übergeben. Aber auch die Klasse String, auf der sämtliche Stringrepräsentationen von Java basieren, ist hier zu erwähnen. Die Konstruktoren der Wrapper-Klassen erlauben es auch, einen String als Übergabewert anzugeben. Über diesen Weg führt dann am sinnvollsten die Extrahierung einer Zahl aus einem String. Neben der Klasse Integer gibt es für jeden primitiven Datentyp ein Wrapper-Äquivalent. Tabelle 6.33: Wrapper für primitive Datentypen
Wrapper
Beschreibung
Boolean
Wrappt einen primitiven boolean-Wert in ein Objekt.
Byte
Wrappt einen primitiven byte-Wert in ein Objekt.
Character
Wrappt einen primitiven char-Wert in ein Objekt.
Double
Wrappt einen primitiven double-Wert in ein Objekt.
Float
Wrappt einen primitiven float-Wert in ein Objekt.
Integer
Wrappt einen primitiven int-Wert in ein Objekt.
Long
Wrappt einen primitiven long-Wert in ein Objekt.
Short
Wrappt einen primitiven short-Wert in ein Objekt.
In den gleichen Zusammenhang sind die Klassen Number (die abstrakte Superklasse von Byte, Double, Float, Integer, Long und Short) und Void (eine nicht-instanzierbare Platzhalterklasse zum Bereitstellen einer Referenz auf das Klassenobjekt, das den primitive Java-Typ void repräsentiert) zu setzten. Die Extrahierung aus einem per Wrapper erzeugten Objekt funktioniert mit den über die Klasse bereitgestellten Methoden. So stellt die Klasse Integer beispielsweise folgende Methoden bereit (Auswahl):
256
Java 2 Kompendium
Ausdrücke, Operatoren und Casting
Kapitel 6
Methode
Beschreibung
byte byteValue()
Rückgabe des Werts von einem Integer als byte
int compareTo(Integer anotherInteger)
Numerischer Vergleich zweier Integer
int compareTo(Object o)
Vergleich von Integer mit einem anderen Objekt
static Integer decode(String nm)
Dekodierung eines Strings in ein
Tabelle 6.34: Auswahl von Methoden der Interger-Klasse
Integer double doubleValue()
Rückgabe des Werts von einem Integer als double
boolean equals(Object obj)
Objektvergleich
float floatValue()
Rückgabe des Wert von einem Integer als float
static Integer getInteger(String nm)
Rückgabe eines Integer-Objekts auf Grund verschiedener Übergabewerte.
static Integer getInteger(String nm, int val) static Integer getInteger(String nm, Integer val) int hashCode()
Rückgabe eines Hashcodes für diesen Integer
int intValue()
Rückgabe des Werts von einem Integer als int
long longValue()
Rückgabe des Werts von einem Integer als long
static int parseInt(String s)
Parsed das Stringargument und gibt den gefundenen Integerwert dezimal zurück.
short shortValue()
Rückgabe des Werts von einem Integer als short
Die anderen Wrapper-Klassen stellen die gleichen oder verwandte Methoden bereit. Das nachfolgende Beispiel extrahiert den Wert des Objekts als primitiven Datentyp int aus dem Integer-Objekt. Die verwendete Methode ist intValue().
Java 2 Kompendium
257
Kapitel 6 Listing 6.19: Multiplikation von zwei an ein Programm übergebenen Werten
Java – die Hauptbestandteile der Sprache class Multi { public static void main(String args[]) { Integer a = new Integer(args[0]); Integer b = new Integer(args[1]); System.out.println(a.intValue()*b.intValue()); } }
Wenn Sie das Programm mit zwei Integerwerten als Übergabewerte aufrufen (also z.B. java Multi 3 4), werden diese miteinander multipliziert und ausgegeben. Da in Java Übergabewerte an ein Programm immer als Strings übergeben werden (ein dynamisches String-Array), muss man eventuell dort zu übergebende primitive Werte mit geeigneten Wrappern behandeln und dann mit passenden Methoden die primitiven Werte extrahieren. Beachten Sie, dass das Programm nicht gegen Falscheingaben (falsches Format der Übergabewerte oder gar fehlende Parameter) gesichert ist.
6.4.4
Der Instanceof-Operator
Der Instanceof-Operator ist ein binärer Operator mit zwei Operanden: einem Objekt auf der linken und einem Klassennamen auf der rechten Seite. Wenn das Objekt auf der linken Seite des Operators tatsächlich eine Instanz der Klasse auf der rechten Seite des Operators ist (ohne einer ihrer Subklassen), dann gibt dieser Operator den booleschen Wert true zurück. Ansonsten wird false zurückgegeben. Beispiel: neuesObjekt instanceof BestehendeKlasse
Dieser Operator wurde in früheren Java-Versionen oft genutzt, um im Ereignisbehandlungsmodell (Version 1.0) zu überprüfen, von welcher Komponente ein Ereignis ausging, etwa von einem Button oder einem Menü-Eintrag. Wir werden bei der Diskussion der Ereignisbehandlungsmodelle darauf zurückkommen.
6.5
Anweisungen
Wie viele Elemente sind auch Anweisungen in Java denen in C/C++ sehr ähnlich. Anweisungen werden einfach der Reihe nach ausgeführt. Ausnahmen sind Kontrollfluss-Anweisungen oder Ausnahmeanweisungen. Sie werden aufgrund ihres Effektes ausgeführt und haben selbst keine Werte. Java hat viele verschiedene Arten von Anweisungen.
258
Java 2 Kompendium
Anweisungen
Kapitel 6
Anweisungart
Beschreibung
Leere Anweisung
Sie tut nichts und dient als Platzhalter.
Blockanweisungen
Ein Zusammenfassen von grö ß eren Sourceteilen zu Blockstrukturen.
Bezeichnete Anweisung
Jede Anweisung kann mit einer Bezeichnung beginnen. Diese Bezeichnungen dürfen keine Schlüsselworte, bereits festgelegte lokale Variablen oder schon in diesem Modul verwendeten Bezeichnungen sein.
Deklarationen
Einführung eines primitiven Datentyps, eines Datenfelds, einer Klasse, einer Schnittstelle oder eines Objekts.
Ausdrucksanweisungen
Ausdrucksanweisungen werden am häufigsten verwendet. Java verwendet sieben verschiedene Ausdrucksanweisungsarten:
Tabelle 6.35: Anweisungsarten in Java
Zuordnung Pre-Inkrement Pre-Dekrement Post-Inkrement Post-Dekrement Methodenaufruf Zuweisungsausdruck Auswahlanweisung
Sie sucht einen von mehreren möglichen Kontrollflüssen aus. Es gibt in Java drei verschiedene Arten von Auswahlanweisungen: if, if-else switch-case-default.
Dies ist identisch mit C und C++. Iterationsanweisung
Sie gibt an, wann und wie Schleifen gestartet werden. Es gibt in Java drei Arten von Iterationsanweisungen: while do for
Bis auf Sprünge und Bezeichnungen sind sie identisch mit den gleichnamigen Iterationsanweisungen in C und C++.
Java 2 Kompendium
259
Kapitel 6 Tabelle 6.35: Anweisungsarten in Java (Forts.)
Java – die Hauptbestandteile der Sprache
Anweisungart
Beschreibung
Sprunganweisungen
Sprunganweisungen geben die Steuerung entweder an den Anfang oder das Ende des derzeitigen Blocks oder aber an bezeichnete Anweisungen weiter. Beachten Sie, dass es in Java keine goto-Anweisung gibt. Das optionale Bezeichnungsargument für Unterbrechungsund Fortsetzungsanweisungen ist in C oder C++ nicht vorhanden. Diese Bezeichnungen müssen im selben Block stehen, und continue-Bezeichnungen müssen bei den Iterationsanweisungen stehen. Diese Anweisungen beinhalten auch Implikationen für Synchronisierungsanweisungen im selben Block. Bei den vier Arten von Sprunganweisungen handelt es sich um: break continue return throw throw-Anweisungen sind ein wichtiger Bestand-
teil des Ausnahmemechanismus. Synchronisationsanweisung
Sie wird für den Umgang mit Multithreading benutzt. Die Schlüsselworte synchronized und threadsafe werden zum Markieren von Methoden und Blöcken benutzt, die eventuell vor gleichzeitiger Verwendung geschützt werden sollen.
Schutzanweisung
Schutzanweisungen werden zur sicheren Handhabung von Code, der Ausnahmen auslösen könnte (beispielsweise das Teilen durch Null), gebraucht. Diese Anweisungen benutzen die Schlüsselworte try, catch und finally.
Unerreichbare Anweisung
Sie erzeugt einen Fehler zur Kompilierzeit.
Schauen wir uns die Anweisungen im Detail an.
6.5.1
Blöcke und Anweisungen
Methoden und statische Konstruktoren (spezielle Konstruktoren, um Variablen mit einem Startwert zu belegen) werden in Java durch Anweisungsblöcke definiert. Bei einem Anweisungsblock handelt es sich um eine Reihe von Anweisungen, die in geschweiften Klammern ({}) stehen. Diese Struktur ist ohne Veränderung aus C/C++ übernommen. Wenn die Form einer 260
Java 2 Kompendium
Anweisungen
Kapitel 6
Anweisung eine Anweisung oder eine Unteranweisung verlangt, kann jeder sinnvolle Block anstelle der Unteranweisung eingefügt werden. Ein Anweisungsblock hat seinen eigenen Geltungsbereich für die in ihm enthaltenen Anweisungen. Das bedeutet, dass lokale Variablen in diesem Block deklariert werden können, die außerhalb dieses Blocks nicht verfügbar sind, und deren Existenz erlischt, wenn der Block ausgeführt wurde. Blöcke können beliebig ineinander geschachtelt werden. Sie müssen allerdings die Schachtelung wieder sauber schließen. Viele Fehler beruhen auf geöffneten und nicht korrekt geschlossenen Blöcken. Der Java-Compiler überprüft natürlich solche Blockstrukturen. Die im Fehlerfall zurückgegebene Meldung kann jedoch oft in die Irre führen, da der endgültige Fehler sich erst an einer anderen Stelle auswirkt.
6.5.2
Leere Anweisungen
In Java ist es erlaubt, leere Anweisungen zu erstellen. Leere Anweisungen erscheinen als eine eigene Anweisung erst einmal unsinnig, denn es handelt sich dabei um eine Anweisung, die zwar Platz im Code belegt, aber keine Funktion besitzt. Es gibt jedoch ein paar sinnvolle Anwendungen: 1.
Man kann Kennzeichen setzen.
2.
Man kann eine leere Anweisung bei Bedarf mit Debugging-Befehlen füllen. Wenn die Debugging-Befehle nicht gebraucht werden, kommentiert man sie aus und lässt die leere Anweisung stehen.
3.
Man kann eine leere Anweisungen schon einmal prophylaktisch in einen Source einfügen und erst mit Befehlen füllen, wenn man sich richtig an die Programmstelle begibt. Das hilft erheblich gegen Vergesslichkeit.
Für den Compiler sieht eine leere Anweisung nur wie ein zusätzliches Semikolon aus.
6.5.3
Bezeichnete Anweisung
Jede Anweisung kann in Java eine Bezeichnung bekommen. Die eigentliche Benennung hat die gleichen Eigenschaften wie jeder andere Bezeichner; sie darf nicht den gleichen Namen wie ein Schlüsselwort oder ein anderer, bereits deklarierter lokaler Bezeichner haben. Wenn sie aber den gleichen Namen wie eine Variable, eine Methode oder ein Typ hat, die für diesen Block verfügbar sind, dann erhält die neue Benennung innerhalb dieses Blocks Vorrang, und die außenstehende Variable, die Methode oder der Typ wird versteckt. Die Reichweite der Benennung erstreckt sich über den
Java 2 Kompendium
261
Kapitel 6
Java – die Hauptbestandteile der Sprache ganzen Block. Der Benennung folgt immer ein Doppelpunkt. Diese Technik ist uralt. Man kennt sie sogar schon von der Batch-Programmierung unter DOS. Die Benennungen werden nur von den Sprunganweisungen break und continue benutzt. Wir kommen bei der Behandlung der beiden Anweisungen darauf zurück. Wer sich noch an goto erinnert und diese Sprunganweisung lieben gelernt hat, muss enttäuscht werden. Obwohl das (Un-)Wort in Java reserviert ist, hat es keine Funktion.
6.5.4
Deklarationen
Deklarationen definieren eine Variable. Dabei ist es egal, ob diese Variable eine Klasse, eine Schnittstelle, ein Datenfeld, ein Objekt oder ein primitiver Typ ist. Das konkrete Format einer solchen Anweisung jedoch hängt davon ab, welcher der fünf verschiedenen Typen deklariert wird. Klassendeklaration Eine Klassendeklaration besteht immer aus sechs Teilen, die in der nachfolgend in der Tabelle angegebenen Reihenfolge zusammengesetzt werden. Tabelle 6.36: Klassendeklaration
262
Bestandteil
Status
Beschreibung
Klassenmodifier
Optional
Dies sind die Schlüsselworte abstract, final oder public. Eine Klasse kann nicht sowohl final als auch abstract als Modifier haben.
Klasse
Zwingend
Das Schlüsselwort class.
Bezeichner
Zwingend
Ein normaler Bezeichner.
Super
Optional
Das Schlüsselwort extends gefolgt von einem Typnamen. Der Typ muss eine verfügbare Klasse sein, die nicht final ist.
Schnittstellen
Optional
Das Schlüsselwort implements, gefolgt von einer durch Kommata getrennten Liste mit Schnittstellen.
Klassenkörper
Zwingend
Java 2 Kompendium
Anweisungen
Kapitel 6
Schnittstellendeklaration Eine Schnittstellendeklaration besteht immer aus fünf Teilen, die in der nachfolgend in der Tabelle angegebenen Reihenfolge zusammengesetzt werden. Bestandteil
Status
Beschreibung
Schnittstellenmodifier
Optional
Dies sind die Schlüsselworte abstract oder public. Alle Schnittstellen sind abstrakt, der Modifier kann jedoch trotzdem aufgrund der Eindeutigkeit gesetzt werden.
Interface
Zwingend
Das Schlüsselwort interface.
Bezeichner
Zwingend
Ein normaler Bezeichner. Das Schlüsselwort extends, gefolgt von einer durch Kommata getrennten Liste von Schnittstellenbezeichnern.
Schnittstellenerweiterung Optional
Schnittstellenkörper
Tabelle 6.37: Schnittstellendeklaration
Zwingend
Datenfelddeklaration Eine Datenfelddeklaration besteht immer aus fünf Teilen, die in der nachfolgend in der Tabelle angegebenen Reihenfolge zusammengesetzt werden. Bestandteil
Status
Beschreibung
Datenfeldmodifier
Optional
Dies sind die Schlüsselworte public, protected, private oder synchronized.
Typenname
Zwingend
Der Name des Typen oder der Klasse, die aufgestellt werden.
Klammern
Zwingend
[ ].
Initialisierung
Optional
Semikolon
Zwingend
Tabelle 6.38: Datenfelddeklaration
;
Objektdeklaration Ein Objekt zu deklarieren bedeutet, eine Referenz zu erstellen. Diese Referenz verweist nur dann auf ein Objekt, wenn dieses Objekt initialisiert oder dieser Referenz zugeordnet wurde. Eine Objektdeklaration besteht aus den folgenden Teilen:
Java 2 Kompendium
263
Kapitel 6 Tabelle 6.39: Objektdeklaration
Java – die Hauptbestandteile der Sprache
Bestandteil
Status
Beschreibung
Objektmodifier
Optional
Dies sind die Schlüsselworte public, protected, private oder synchronized.
Typenname
Zwingend
Der Name der Klasse, deren Instanz dieses Objekt ist.
Initialisierung
Optional
Semikolon
Zwingend
;
Deklaration primitiver Typen Eine Variable eines primitiven Typen zu deklarieren bedeutet, dass Speicherplatz für diese Variable eingerichtet wird. Wenn ihr kein Wert bei einer expliziten Initialisierung zugewiesen worden ist, nimmt sie ihren spezifischen Grundwert an. Die Deklaration von primitiver Datentypen setzt sich aus den folgenden Teilen zusammen: Tabelle 6.40: Deklaration primitiver Typen
Bestandteil
Status
Beschreibung
Datenfeldmodifier
Optional
Dies sind die Schlüsselworte public, protected, private oder synchronized.
Typenname
Zwingend
Die Bezeichnung des Datentyps: boolean, char, byte, short, int, long, float, double.
Initialisierung
Optional
Semikolon
Zwingend
6.5.5
;
Ausdrucksanweisungen
In Java gibt es wie bereits beschrieben sieben verschiedene Arten von Ausdrucksanweisungen.
264
Java 2 Kompendium
Anweisungen
Kapitel 6
Ausdrucksanweisungen
Beispiel
Zuordnung
a = 42
Pre-Inkrement
++wert
Pre-Dekrement
--wert
Post-Inkrement
wert++
Post-Dekrement
wert--
Methodenaufruf
System.out.println("Hello World!")
Zuweisungsausdruck
Byte wert = new Byte();
1.
Alle Ausdrucksanweisungen müssen mit einem Semikolon beendet werden.
2.
Eine Ausdrucksanweisung wird immer vollständig durchgeführt, bevor die nächste Anweisung ausgeführt wird.
3.
Eine Zuweisungsanweisung kann einen Ausdruck rechts von dem Gleichheitszeichen (dem Zuweisungsoperator =) stehen haben. Dieser Ausdruck kann jede der sieben Ausdrucksanweisungen sein. Es darf in Java immer nur die rechte Seite einer solchen Zuweisung festgelegt werden.
4.
Unter Umständen kann der Rückgabewert einer Methode, die nicht leer ist, ohne Zuweisung aufgerufen werden. Dazu muss der Rückgabewert explizit als void festgelegt werden.
5.
Eine Ausdrucksanweisung kann natürlich aus komplexen Verschachtelungen bestehen. Klammern können zur Festlegung der Reihenfolge dienen, in der die einzelnen Unteranweisungen bewertet werden.
6.5.6
Tabelle 6.41: Ausdrucksanweisungen
Auswahlanweisungen
Bei Auswahlanweisungen bleibt es gleichermaßen spannend, denn auch diese gehören zu den wichtigsten Anweisungen in einer Programmiersprache. Java unterstützt drei verschiedene Arten von Auswahlanweisungen: if if-else switch
Die if- und die if-else-Anweisung Eine if-Anweisung testet eine boolesche Variable oder einen Ausdruck. Wenn die boolesche Variable oder der Ausdruck den Wert true hat, wird
Java 2 Kompendium
265
Kapitel 6
Java – die Hauptbestandteile der Sprache die nachstehende Anweisung oder der nachstehende Anweisungsblock ausgeführt. Wenn die boolesche Variable oder der Ausdruck den Wert false hat, wird die nachstehende Anweisung oder der nachstehende Anweisungsblock ignoriert und mit dem folgenden Block bzw. der folgenden Anweisung fortgefahren. Eng verwandt ist die if-else-Anweisung, die genau genommen nur eine Erweiterung der if-Anweisung ist. Sie hat zusätzlich nur noch einen zusätzlichen else-Teil. Dieser else-Teil – eine Anweisung oder ein Block – wird dann ausgeführt, wenn der boolesche Test im if-Teil der Anweisung den Wert false ergibt. Ein kleines Beispiel zeigt die Verwendung.
Listing 6.20: Die Anwendung von if-else
import java.util.*; public class IfTest { public static void main(String args[]) { Random a = new Random(); if(a.nextFloat()<0.5) System.out.println("Klein"); else System.out.println("Gross"); } }
In dem Beispiel wird mit einem Zufallsobjekt gearbeitet. Die Methode nextFloat() extrahiert daraus den Zufallswert, der zwischen 0.0 und 1.0 liegt. Die nachfolgende if-else-Anweisung wird je nach Wert den ersten oder den zweiten Zweig auswählen. Wenn Sie innerhalb des else-Zweigs eine neue if-Anweisung notieren, haben Sie mit der resultierenden if-else-if-Anweisung einen Spezialfall der if-else-Anweisung. Schreiben wir unser Beispiel entsprechend um. Listing 6.21: Die Anwendung von if-else-if
import java.util.*; public class IfTest2 { public static void main(String args[]) { Random a = new Random(); if(a.nextFloat()<0.2) System.out.println("Ganz klein"); else if(a.nextFloat()<0.4) System.out.println("Klein"); else if(a.nextFloat()<0.6) System.out.println("Mittel"); else if(a.nextFloat()<0.8) System.out.println("Gross"); else System.out.println("Ganz Gross"); } }
Ein solches Konstrukt kann für eine etwas grö ß ere Auswahl von Möglichkeiten durchaus Sinn machen. Es gibt jedoch eine dann meist noch etwas besser geeignete Auswahlanweisung – die switch-Anweisung.
266
Java 2 Kompendium
Anweisungen
Kapitel 6
Die switch-Anweisung Eine switch-Anweisung ermöglicht das Weitergeben des Kontrollflusses an eine von vielen Anweisungen in ihrem Block mit Unteranweisungen. An welche Anweisung innerhalb der switch-Anweisung der Kontrollfluss weitergereicht wird, hängt vom Wert des Ausdrucks in der Anweisung ab. Es wird die erste Anweisung nach einer case-Bezeichnung ausgeführt, die denselben Wert wie der Ausdruck hat. Wenn es keine entsprechenden Werte gibt, wird die erste Anweisung hinter der default-Bezeichnung ausgeführt. Wenn auch die nicht vorhanden ist, wird die erste Anweisung nach dem switch-Block ausgeführt. Die zu testenden switch-Ausdrücke und caseBezeichnungskonstanten müssen alle vom Typ byte, short, char oder int sein. Mit dieser Auswahlanweisung haben Sie eine handlichere Auswahlanweisung, die oft die gleichen Möglichkeiten wie die Erweiterung der ifelse-Anweisung bietet. Einschränkung gegenüber dieser ist jedoch, dass nur diskrete Werte getestet werden können (also Gleichheit) und keine Vergleiche auf »kleiner« oder »grö ß er« möglich sind. Auch dürfen keine zwei caseBezeichnungen im gleichen Block denselben Wert haben. So etwas ist in der if-else-if-Anweisung im Prinzip denkbar (mehrfacher Vergleich in verschiedenen Zweigen auf den gleichen Wert), wenn auch nicht sonderlich sinnvoll, denn nach dem ersten Treffer werden folgende Treffer nicht mehr entdeckt, weil der Kontrollfluss aus der if-else-if-Anweisung herausspringt. Bezeichnungen beeinflussen den Kontrollfluß nicht. Die Kontrolle behandelt diese Bezeichnungen so, als seien sie nicht vorhanden. Daher können beliebig viele Bezeichnungen vor derselben Codezeile stehen. Schreiben wir unser Beispiel für die if-else-if-Anweisung um (beachten Sie, dass wir wegen der Datentypen mit dem Faktor 5 multiplizieren und casten). import java.util.*; public class SwitchTest { public static void main(String args[]) { Random a = new Random(); switch((byte)(a.nextFloat()*5)) { case 1: System.out.println("Ganz klein"); case 2: System.out.println("Klein"); case 3: System.out.println("Mittel"); case 4: System.out.println("Gross"); default: System.out.println("Ganz Gross"); } } }
Listing 6.22: Die Anwendung von switch-case
Was wird die Ausgabe sein? Vielleicht werden Sie überrascht. Im Gegensatz zu der if-else-if-Anweisung springt der Kontrollfluss nach einem Treffer nicht automatisch aus der Struktur, sondern es wird von dem ersten Treffer
Java 2 Kompendium
267
Kapitel 6
Java – die Hauptbestandteile der Sprache an die Struktur bis zum Ende ausgeführt. Das heißt, alle nachfolgend notierten Anweisungen werden explizit ausgeführt. Dies ist in diversen anderen Sprachen anders und damit lauert hier eine gefährliche Fehlerquelle. Wenn dieses Verhalten unterbunden werden soll, setzt man in jedem Block hinter einem Label eine break-Anweisung. Damit vermeiden Sie die Ausführung von mehr als einem Block. Die gesamte switch-case-Struktur wird nach einem Treffer beendet. Unser Beispiel sieht dann so aus:
Listing 6.23: Die Anwendung von switch-case in Verbindung mit break
import java.util.*; public class SwitchTest2 { public static void main(String args[]) { Random a = new Random(); switch((byte)(a.nextFloat()*5)) { case 1: { System.out.println("Ganz klein"); break; } case 2: { System.out.println("Klein"); break; } case 3: { System.out.println("Mittel"); break; } case 4: { System.out.println("Gross"); break; } default: { System.out.println("Ganz Gross"); break; } } } }
Natürlich können Sie Breaks auch gezielt einsetzen, um durch Auslassen von break-Anweisungen den Durchlauf von mehreren Blöcken zu erreichen. Etwa bei einer angeordneten Auswahl, wo ab einer gewissen Grö ß e alle Folgeanweisungen Sinn machen.
268
Java 2 Kompendium
Anweisungen
6.5.7
Kapitel 6
Iterationsanweisungen
Iterationsanweisungen werden auch Wiederholungsanweisungen genannt und dieser Name macht deutlich, um was es geht: die kontrollierte Wiederholung von Anweisungsfolgen zur Laufzeit. Es gibt in Java drei Arten von Iterationsanweisungen: while do for
Diese sind fast identisch zu den analogen Anweisungen in C und C++, mit der Ausnahme, dass in Java die Anweisungen die optionalen Parameter continue und break haben. Die while-Anweisungen Die while-Anweisung testet eine boolesche Variable oder einen Ausdruck. Solange der Test den Wert true hat, wird die Unteranweisung oder der Block ausgeführt. Erst wenn die boolesche Variable oder der Ausdruck den Wert false ausweist, wird die Wiederholung eingestellt und die Kontrolle an die nächste Anweisung nach dem while-Konstrukt weitergegeben. Die Syntax sieht so aus: while()
Erstellen wir wieder ein kleines Testprogramm. class WhileTest { public static void main (String args[]) { int testvariable=0; // Eine numerische Variable while (testvariable < 5) { System.out.println(testvariable); testvariable = testvariable + 1; } System.out.println("Und Schluss"); } }
Listing 6.24: Eine while-Schleife
Die Ausgabe wird die Zahlenkolonne von 0 bis 4 und danach Und Schluss sein. Um die while-Schleife jemals verlassen zu können, wird der Wert der Testvariablen in jedem Schleifendurchgang um den Wert 1 erhöht. Wenn der Ausdruck nicht von Anfang an true ist, wird der Block in der Unteranweisung niemals ausgeführt. Wenn er dahingegen true ist, dann wird dieser Codeblock so lange wiederholt, bis er nicht mehr true ist, oder
Java 2 Kompendium
269
Kapitel 6
Java – die Hauptbestandteile der Sprache eine Sprung-Anweisung ausgeführt wird, und er die Kontrolle an eine Anweisung außerhalb der Schleife weitergibt. Eine while-Schleife ist ein guter Kandidat für so genannte Endlosschleifen. Lassen Sie in unserem Beispiel einfach mal die Zeile testvariable = testvariable + 1; weg. Die Bedingung zum Durchlaufen der Schleife wird immer erfüllt sein und Sie hängen in einer Endlosschleife. Mit ein bisschen Glück lässt sich das Programm noch vom Betriebssystem beenden, mit Pech müssen Sie den Rechner ausschalten. Die do-Anweisung Auch die do-Anweisung (oder auch do-while-Anweisung genannt) testet eine boolesche Variable oder einen Ausdruck. Solange dieser Test den Wert true hat, wird die Unteranweisung oder der Block ausgeführt. Erst wenn die boolesche Variable oder der Ausdruck den Wert false ausweist, wird die Wiederholung eingestellt und die Schleife verlassen. Es gibt aber einen ganz wichtigen Unterschied zur while-Schleife: der Codeblock innerhalb der doAnweisung wird auf jeden Fall mindestens einmal ausgeführt. Dies geschieht immer, ob die Bedingung erfüllt ist oder nicht. Rein von der Syntax her kann man es sich dadurch verdeutlichen, dass die Überprüfung am Ende der Struktur steht. Sie sieht immer so aus: do while()
Programmbeispiel: Listing 6.25: Eine do-Schleife
import java.util.*; class DoTest { public static void main (String args[]) { byte testvariable=0; // Eine numerische Variable Random a = new Random(); do { System.out.println(testvariable); testvariable = (byte)(a.nextFloat()*5); } while (testvariable < 3); System.out.println("Und Schluss"); } }
Das Beispiel wiederholt so lange die Schleife, bis die zufällig generierte Testvariable grö ß er als 3 ist. Die do-Schleife wird aber auf jeden Fall einmal durchlaufen.
270
Java 2 Kompendium
Anweisungen
Kapitel 6
Die for-Anweisung Die for-Anweisungen sind uns ja schon ein paar Mal begegnet. Es sind die komplexesten der drei Iterationsanweisungen. Eine for-Anweisung sieht wie folgt aus: for (; ;)
Einige Erklärungen zu der Syntax: 1.
Hinter for kann optional ein Leerzeichen folgen.
2.
Der Initialisierungsteil kann eine durch Kommata getrennte Reihe von Deklarations- und Zuweisungsanweisungen enthalten. Erst durch ein Semikolon wird der Initialisierungsteil beendet. Diese Deklarationen haben nur Gültigkeit für den Bereich der for-Anweisung und ihrer Unteranweisungen.
3.
Der Testteil enthält eine boolesche Variable oder einen Ausdruck, der einmal pro Schleifendurchlauf neu bewertet wird. Wenn der Vergleich den Wert false ergibt, wird die for-Schleife verlassen. Auch dieser Teil wird wieder durch ein Semikolon beendet.
4.
Auch der In- oder Dekrementteil kann eine durch Kommata getrennte Reihe von Ausdrücken sein, die einmal pro Durchlauf der Schleife bewertet werden. Diese mehrfachen Ausdrücke in diesem Teil der forSchleife machen nur dann Sinn, wenn sie bereits im Initialisierungsteil deklariert wurden. Dieser Teil wird gewöhnlich dazu verwendet, einen Index, der im Testteil überprüft wird, zu inkrementieren (hochzuzählen) oder zu dekrementieren (herabzuzählen).
Nutzen wir zur praktischen Veranschaulichung der for-Schleife ein Beispiel, das die Summe einer Reihe von 1 bis zu einem vorgegebenen Endwert berechnet. /* Berechnet die Summe einer Reihe mit vorgegebener Zahl über eine forSchleife – eine Alternative zu der echten Gauss-Formel */ class Gauss { public static void main(String argv[]) { long ergebnis=0; int ende=70; for (int i=1;i<=ende;i++) { ergebnis += i; } System.out.println("Ergebnis Summe 1 bis " + ende + ": "+ergebnis); } }
Java 2 Kompendium
Listing 6.26: Reihenberechnung – Variante 1
271
Kapitel 6
Java – die Hauptbestandteile der Sprache Das Beispiel arbeitet in der Schleife mit so genannten schleifenlokalen Variablen. Das bedeutet in unserem Fall, dass die Zählvariablen in for-Schleifen nicht unbedingt außerhalb der Schleife vereinbart werden müssen, sondern ebenso direkt im Initialisierungsteil der for-Schleife direkt vereinbart werden können. Sie sind dann auch nur dort bekannt. Diese im Initialisierungsteil der for-Schleife direkt vereinbarten Zählvariablen sind ein Sonderfall von normalen lokalen Variablen und überdecken ggf. gleichnamige Variablen im übergeordneten Programmblock. Die Variablen im übergeordneten Programmblock bleiben hierdurch unverändert. Die Berechnung der Summe einer Reihe geht natürlich viel eleganter, wenn man die echte Gauß-Formel verwendet. Das könnte dann so aussehen:
Listing 6.27: Reihenberechnung – Variante 2
/* Berechnet die Summe einer Reihe mit vorgegebener Zahl nach der GaussFormel */ class Gauss2 { public static void main(String argv[]) { int n; long x; n = 70; x = (long)((n+1) / 2.0 * n ); System.out.println("Ergebnis Summe 1 bis "+n+": "+x); } }
Die Schleife for(;;), d.h. ohne explizite Parameter, wird keinen Compilerfehler erzeugen, denn syntaktisch ist sie völlig korrekt. Analog der Syntax while(true) wird sie eine Endlosschleife erzeugen.
6.5.8
Java-Sprunganweisungen
Ähnlich dem alten goto-Befehl gibt es auch in Java Sprunganweisungen. Diese unterbrechen einen jeweiligen Programmablauf und übergeben den Programmfluss an eine andere Stelle. Java kennt vier Arten von Sprunganweisungen: break continue return throw
Die break-Anweisung Die Unteranweisungsblöcke von Schleifen und switch-Anweisungen können durch die Verwendung der break-Anweisung verlassen werden (wir haben es im Beispiel der switch-Anweisungen ja schon gesehen). Eine unbezeichnete break-Anweisung springt zu der nächsten Zeile nach der derzeitigen (inners-
272
Java 2 Kompendium
Anweisungen
Kapitel 6
ten) Wiederholungs- oder switch-Anweisung. Als Beispiel soll hier auf das Beispiel bei der Behandlung von switch-case verwiesen werden (siehe Seite 267). Mit einer bezeichneten break-Anweisung am Anfang einer Schleife kann an eine Anweisung mit dieser Bezeichnung in der derzeitigen Methode gesprungen werden. Dazu müssen Sie vor dem Anfangsteil der Schleife ein Label (eine Beschriftung) mit einem Doppelpunkt eingeben. Sofern Sie ein falsch gesetztes oder nicht vorhandenes Label in einer Sprunganweisung angeben, wird der Compiler eine Fehlermeldung ausgeben. Wenn es beim Auslösen der break-Anweisung eine umgebende Ausnahmebehandlungsroutine mit finally-Teil gibt, wird immer dieser Teil zuerst ausgeführt, bevor die Kontrolle weitergegeben wird. Richtig interessant werden die benannten Schleifen aber eigentlich erst in Verbindung mit verschachtelten Schleifen. Es wird Ihnen vielleicht auffallen, dass diese bezeichneten Anweisungen ziemlich an das goto-return-Konstrukt aus alten Programmiererzeiten erinnern. Diese Assoziation ist leider nicht ganz von der Hand zu weisen, denn für ein solches Verfahren lässt sie sich missbrauchen. Glücklicherweise sind mit diesen Konstrukten keine beliebigen Sprünge innerhalb des Programms erlaubt, sondern es lassen sich in Java immer nur Sprünge über mehrere Blöcke innerhalb einer Schleife durchführen. Die continue-Anweisung Durch die Anweisung continue wird im Gegensatz zu break nicht die gesamte Schleife abgebrochen, sondern der aktuelle Schleifendurchlauf wird unterbrochen. Es wird zum Anfang der Schleife zurückgekehrt, falls hinter continue kein Bezeichner steht. Ansonsten wird zu einer äußeren Schleife zurückgekehrt, die eine Markierung gleichen Namens enthält. Eine continue-Anweisung darf nur in einem Unteranweisungsblock einer Iterationsanweisung stehen (while, do oder for). Bei einer unbezeichneten continue-Anweisung werden die restlichen Anweisungen im innersten Block der Wiederholungsanweisung übersprungen und die Schleife wieder von vorne durchlaufen, beispielsweise um Fehler abzufangen. Ein Beispiel ist die Verhinderung einer Division durch Null (obwohl das besser mit einer Ausnahmebehandlung abgefangen werden kann). Der nachfolgende Quellcode skizziert die Technik: float zuteilender, ergebnis; int teiler; ... for (teiler = -42; teiler < 42; teiler++) { if (teiler ==0) continue;
Java 2 Kompendium
273
Kapitel 6
Java – die Hauptbestandteile der Sprache ergebnis= zuteilender / teiler; ... }
Auch bei der continue-Anweisung gibt es die Möglichkeit, die Schleife mit einem Bezeichnungsparameter zu versehen. Dies ermöglicht eine Kontrolle darüber, mit welcher Ebene der verschachtelten Iterationsanweisungen fortgefahren werden soll. Auch für die continue-Anweisung gilt, dass ein evtl. vorhandener finally-Teil einer derzeit aktiven try-Anweisung in der angezeigten verschachtelten Ebene immer zuerst ausgeführt wird. Ein vollständiges Beispiel sieht so aus: Listing 6.28: Anwendung von continue
class ContiTest { public static void main (String args[]) { int x=0; while (x<10) { if (++x <10) continue; System.out.println("x ist 10"); } } }
Im obigen Beispiel wird nur ein einziges Mal der String x ist 10 ausgegeben. Nämlich genau dann, wenn x identisch mit 10 ist, da ansonsten vorher immer wieder zum Schleifenursprung zurückgekehrt wird. Beachten Sie das Konstrukt (++x <10). Wir verwenden dabei den Inkrement-Operator, um mit der if-Anweisung keine Endlosschleife zu erzeugen (die Zählvariable muss ja irgendwie weitergezählt werden und der Bereich nach continue wird nicht erreicht). Die return-Anweisung Eine return-Anweisung gibt die Kontrolle an den Aufrufer einer Methode zurück. Wenn sich die return-Anweisung in einer Methode befindet, die nicht als void deklariert wurde und kein Konstruktor ist, kann und muss sie einen Parameter (den so genannten Rückgabewert) des Typs zurückgeben, wie in der Deklaration der Methode angegeben. Diese Rückgabewerte können dann von dem aufrufenden Programm weiter verarbeitet werden. Wenn es einen finally-Teil einer umgebenden Ausnahmebehandlung gibt, wird dieser Teil zuerst ausgeführt, bevor die Kontrolle weitergegeben wird. Für das nachfolgende Beispiel müssen wir selbst geschriebene Methoden einsetzen. Diese werden vor einem Aufruf in der main()-Methode innerhalb der Klasse, aber außerhalb der main()-Methode deklariert.
274
Java 2 Kompendium
Anweisungen
Kapitel 6
Die Anordnung der Methoden vor der main()-Methode ist nicht zwingend erforderlich. Sie kann auch danach erfolgen.
class ReturnTest { static String methode1() { return "ABC"; } static int methode2() { return 42; } static void methode3() { System.out.println("Methode 3"); return; } public static void main(String args[]) { System.out.println(methode1()); System.out.println(methode2()); methode3(); } }
Listing 6.29: Die return-Anweisung
Beachten Sie, dass die Aufrufe der Methoden. methode1() und methode2()direkt als Parameter in der println()-Methode stehen. Ausgegeben wird der Rückgabewert der Methoden: ABC 42 Methode 3
Die throw-Anweisung In Java ist es möglich, durch die throw-Anweisung eine Laufzeitausnahme des Programms zu erzeugen. Dies bedeutet, dass der normale Programmablauf durch eine Ausnahme unterbrochen wird, die zuerst von dem Programm behandelt werden muss, bevor der normale Programmablauf weitergeht. Die Laufzeitausnahme verwendet ein Objekt als Argument. Dieses Objekt, das hinter der Anweisung throw steht, bezeichnet einen Referenzausdruck, der gewöhnlich von der Klasse Exception abgeleitet sein muss. Wenn ein Programminterpreter auf eine throw-Anweisung stö ß t, wird die Ausführung des Programms so lange unterbrochen, bis eine catch-Anweisung mit einem Formalparameter gefunden wird, der dem Klassentyp oder dem Typ einer Superklasse des Argument-Objekts der throw-Anweisung entspricht . Ein gutes Beispiel für eine solche Ausnahme ist der Versuch, eine nicht vorhandene Datei zu öffnen. Eine Klasse, die die Dateibehandlung implemen-
Java 2 Kompendium
275
Kapitel 6
Java – die Hauptbestandteile der Sprache tiert, kann prüfen, ob die Berechtigungen für einen Dateizugriff auf eine spezifizierte Datei ausreichend sind und ob die Datei vorhanden ist. Ist dies nicht der Fall, so wird eine Ausnahme erzeugt (eine »Exception geworfen«). Der finally-Teil einer try-Anweisung wird ausgeführt, sobald auf ihn gestoßen wird. Mehr zur throw-Anweisung folgt bei der Behandlung von Ausnahmen. Synchronisationsanweisung Die Synchronisationsanweisung wird für den Umgang mit Multithreading benutzt. Die Schlüsselworte synchronized und threadsafe werden zum Markieren von Methoden und Blöcken benutzt, die eventuell vor gleichzeitiger Verwendung geschützt werden sollen. Der umfangreiche Themenkomplex »Multithreading« soll an anderer Stelle ausführlich behandelt werden.
6.5.9
Schutzanweisung
Java verfolgt zum Abfangen von Laufzeitfehlern ein Konzept, das mit so genannten Ausnahmen arbeitet. Wir werden diesem Ausnahmekonzept ein eigenes Kapitel (zusammen mit dem Debugging von Sourcecode) widmen. Nur so viel schon vorab: Ausnahmen sind nicht planbare Situationen, die eine unmittelbare Reaktion durch das Programm bzw. den Anwender notwendig machen. Dies ist beispielsweise der Fall, wenn ein Zugriff auf ein Diskettenlaufwerk erfolgt, wo keine Diskette eingelegt ist. Dieser Vorgang wird in Java eine Ausnahme erzeugen, die dann unmittelbar vom Programm bearbeitet werden muss. Schutzanweisungen werden zur sicheren Handhabung von Code, der Ausnahmen auslösen könnte (beispielsweise das Teilen durch Null), gebraucht. Diese Anweisungen benutzen die drei folgenden Schlüsselworte: try catch finally
Diese Anweisungen werden zur Handhabung von Ausnahmen in einer Methode benutzt. Diese Ausnahmenbehandlung ist der switch-casedefault-Anweisung recht ähnlich. Nach dieser Analogie verhält sich die tryAnweisung wie die switch-Anweisung. Allerdings hat die try-Anweisung anstelle eines ganzzahligen Parameters einen Codeblock. Wenn innerhalb eines try-Blockes eine Ausnahme auftaucht, wird die Ausführung dieses Blocks unterbrochen und die Kontrolle mit dem richtigen Objekttyp als 276
Java 2 Kompendium
Klassen und Objekte
Kapitel 6
Parameter an die catch-Anweisung weitergegeben. Die catch-Anweisungen haben Ausnahmeobjekte als Parameter. Die finally-Anweisung erlaubt die Abwicklung wichtiger Abläufe (wie zum Beispiel das Schließen von Dateien) bevor die Ausführung unterbrochen wird.
6.5.10
Unerreichbare Anweisung
Es ist leider leicht möglich, eine Methode zu schreiben, die Codezeilen enthält, die nie erreicht werden können – eine so genannte unerreichbare Anweisung. Sie ist zwar eigentlich nicht schädlich in dem Sinn, dass sie etwas Falsches tut, aber wenn man sich darauf verlässt, dass bestimmte Codezeilen ausgeführt werden und sie werden einfach nicht erreicht, kann der Schaden mindestens genauso groß sein. Der Java-Compiler bemerkt dies glücklicherweise rechtzeitig und erzeugt einen Fehler zur Kompilierzeit.
6.6
Klassen und Objekte
Kommen wir nur zu einem der absolut zentralen Begriffe in Java: Klassen. Wir haben uns mit den allgemeinen Hintergründen von Klassen schon in dem Abschnitt über die Theorie der Objektorientierung beschäftigt (siehe dazu Seite 157) und wollen nun diesen theoretischen Background in JavaPraxis überführen. Ihnen werden sicher viele Dinge sofort einleuchten, falls Sie sich durch diesen Buchabschnitt gearbeitet haben oder Sie sowieso mit der OOP vertraut sind. Da Java absolut objektorientiert ist, können Sie keine prozeduralen Programme schreiben. Man verwendet statt dessen Klassen als Teile der Objektmuster. Klassen definieren den Zustand und das Verhalten von Objekten. In der objektorientierten Sichtweise steht immer das Objekt im Mittelpunkt. Jedwede Operation ist in der Klasse implementiert, zu der ein Objekt gehört. Bei den Elementen einer Klasse, die als Felder bezeichnet werden, handelt es sich im Wesentlichen um Variablen, auf die die ganze Klasse und bei Bedarf auch andere Klassen zugreifen können. Die Ausführung einer Operation übernehmen die Objektmethoden. Sie verhalten sich ähnlich wie Funktionen in anderen Sprachen. Soll nun eine bestehende Operation um eine neue Funktionalität erweitert werden, so werden die Veränderungen in der Klasse vorgenommen und die zugehörigen Methoden dort geschrieben. Die neue Form der Operation wird einfach als Erweiterung innerhalb der Klasse hinzugefügt. Nach außen erscheint das Objekt unverändert (bzgl. der bisherigen Funktionalität) und lässt sich wie gehabt unter einem Namen ansprechen. Weitergehende Änderungen im Programm sind nicht notwendig. Daten und Methoden werden in Klassen gekapselt.
Java 2 Kompendium
277
Kapitel 6
Java – die Hauptbestandteile der Sprache Ein großer Teil der Attraktivität von Klassen und OOP basiert auf der Fähigkeit der Vererbung. Dies gibt einem die Möglichkeit, auf Basis alter Klassen (mit geringerem Aufwand als bei vollständiger Neuentwicklung) neue Klassen zu erstellen. Weil diese neuen Klassen die Eigenschaften einer anderen Klasse erben können, werden sie Subklassen, und die Klasse, aus der sie abgeleitet werden, Superklasse genannt. Gemeinsame Erscheinungsbilder werden in der objektorientierten Philosophie soweit wie irgend möglich in einer Klasse zusammengefaßt werden. Erst wo Unterscheidungen möglich bzw. notwendig sind, die nicht für alle Mitglieder einer Klasse gelten, werden Untergruppierungen – Subklassen – gebildet.
6.6.1
Allgemeines zu Klassen in Java
Jedes Java-Programm besteht aus einer Sammlung von Klassen, aus denen zur Laufzeit die notwendigen Objekte erzeugt werden. Der gesamte Code, der bei Java verwendet wird, wird in Klassen eingeteilt. Jede Klasse, abstrakt oder nicht, definiert das Verhalten eines Objekts durch verschiedene Methoden. Verhalten und Eigenschaft kann von der einen Klasse zur nächsten weitervererbt werden. Alle Klassen in Java haben eine gemeinsame Oberklasse, die Klasse Object. Diese wiederum verfügt nur über eine Metaklasse. Auch Java selbst (als Entwicklungsplattform) ist aus Klassen aufgebaut, die mit dem JDK frei verfügbar sind. Das eigentliche RUNTIMEModul bestand bis zur Version 1.2 aus der Datei »Java Core Classes« (classes.zip), mittlerweile besteht dieses im Wesentlichen aus der Datei rt.jar. Kommen wir nun zu der allgemeinen Struktur einer Java-Klasse. Jede Klasse in Java besteht, was die Syntax angeht, aus zwei Teilen: der Deklaration und dem Body (Körper). Wir kennen ja schon diverse Klassen aus unseren Beispielprogrammen. Nehmen wir nun einmal eines unserer einfachsten Beispiele und sehen es unter dem Gesichtspunkt der Klassenstruktur an. Listing 6.30: Eine Demoklasse
/* Beispielprogramm zur Bildschirmausgabe vom ASCII bzw. Unicode-Zeichensatz */ class Unicode2 { public static void main (String args[]) { int i; for (i=0; i < 256;i++) System.out.print((char)i); } }
Die Deklaration ist class Unicode2, der Rest innerhalb der geschweiften Klammern ist der Klassenkörper. Dies ist jedoch wirklich nur die einfachste Form einer Klasse.
278
Java 2 Kompendium
Klassen und Objekte
6.6.2
Kapitel 6
Allgemeine Klassendeklaration
Generell haben Klassendeklarationen in Java folgendes Format: [<modifiers>] class [extends ] [implements ]
Alle Angaben in eckigen Klammern sind optional. Es kann nur eine Superklasse, aber mehr als eine Schnittstelle implementiert werden. Dann müssen mehrere Schnittstellen, per Komma getrennt, hintereinander geschrieben werden. Es gibt also vier Eigenschaften einer Klasse, die in einer Deklaration definiert werden können: 1.
Modifier
2.
Klassenname
3.
Superklasse
4.
Schnittstellen
Manche Quellen zerlegen den Modifier in zwei Bestandteile (die Sichtbarkeit und den Benutzungsgrad), was in sofern sinnvoll ist, da sich der Modifier in der Tat aus diesen zwei logischen Bestandteilen zusammensetzen kann, die nach gewissen Regeln kombiniert werden können (siehe nächsten Abschnitt). Sie haben ja bisher schon gesehen, dass die Klassendeklaration oft nur das Schlüsselwort class und den Namen der Klasse enthält. Selbst ein Modifier muss nicht immer angegeben werden. Superklassen oder Schnittstellen erst recht nicht. Modifier sind jedoch meist üblich.
6.6.3
Klassen-Modifier
Modifier beginnen eine Klassendeklaration und legen fest, wie die Klasse gehandhabt werden kann, wenn sie fertig ist. Dass wir bisher weitgehend auf Modifier verzichtet haben, liegt daran, dass Klassen einen voreingestellten Defaultstatus haben. Wenn Sie davon abweichen wollen, müssen Sie explizit einen erlaubten Modifier verwenden. Diese stehen Ihnen zur Verfügung: public final abstract
Java 2 Kompendium
279
Kapitel 6
Java – die Hauptbestandteile der Sprache Das Schlüsselwort public regelt die Sichtbarkeit der Klasse (andere Sichtbarkeitsmodifier, wie sie etwa bei Methoden Verwendung finden, sind bei Klassen nicht erlaubt), während die anderen beiden Schlüsselworte den Benutzungsgrad festlegen. Es darf zwar die Sichtbarkeit mit jedem der beiden Schlüsselworte für den Benutzungsgrad kombiniert, die beiden Schlüsselworte für den Benutzungsgrad dürfen jedoch nicht zusammen verwendet werden. Freundliche Klassen – der voreingestellte Defaultstatus Der voreingestellte Defaultstatus einer Klasse ist immer »freundlich« und wird immer dann verwendet, wenn Sie auf public am Anfang einer Klassendeklaration verzichten. Unsere bisherigen Beispiele waren fast immer freundlich. Die freundliche Grundeinstellung aller Klassen bedeutet, dass diese Klassen zwar erweitert und von anderen Klassen benutzt werden können, aber nur von Objekten innerhalb desselben Pakets. Wir haben das Paketkonzept bisher noch nicht weiter erläutert und werden im Anschluss an die Klassen intensiver darauf eingehen (Seite 331). Jedoch eine kleine Erklärung vorab. Das Paket-Konzept von Java dient dazu, mehrere Klassen zu einem Paket über die Anweisung package zusammenzufassen. In C/C++ gibt es eine Notation zum Verbergen eines Namens, sodass nur die Funktionen innerhalb einer bestimmten Quelldatei darauf zugreifen können. Diese Schutzebene ist die besagte »freundliche« Ebene von Java. Für die Bezeichnung friendly wurde früher alternativ package verwendet. Öffentliche Klassen – der Modifier public Eine Klasse wird als öffentlich deklariert, wenn man den Modifier public vor die Klassendeklaration setzt. Dies bedeutet, dass alle Objekte auf public-Klassen zugreifen können. Die Erweiterung gegenüber freundlichen Klassen ist die, dass sie ebenfalls von allen Objekten benutzt und erweitert werden können, die nicht zum eigenen Paket gehören. Die Deklaration des Klassennamens einer öffentlichen Klasse muss immer identisch sein mit dem Namen, unter dem der Source dieser Datei gespeichert wird (natürlich ohne die Erweiterung .java). Aber auch für nichtöffentliche Klassen macht es Sinn, eine Datei, in der ausschließlich diese Klasse definiert ist, mit dem Klassennamen und der Erweiterung .java zu bezeichnen.
280
Java 2 Kompendium
Klassen und Objekte
Kapitel 6
Erstellen wir ein kleines Beispiel, das unter dem Namen PublicTest.java (!) gespeichert werden soll. public class FalscherName { }
Listing 6.31: Die Datei PublicTest.java
Wenn Sie das Beispiel kompilieren wollen, meldet der Compiler folgenden Fehler: PublicTest.java:1: class FalscherName is public, should be declared in a file named FalscherName.java public class FalscherName ^ 1 error
Eine zwingende Folge der Tatsache, dass der Name der Quelltextdatei mit dem Namen der öffentlichen Klasse übereinstimmen muss, ist, dass es nur eine öffentliche Klasse in einer Java-Datei geben darf. Dabei sollte man auch Schnittstellen beachten. Auch diese können als öffentlich deklariert werden und müssen dann in einer Java-Datei des Schnittstellennamens gespeichert werden. Also kann auch keine öffentliche Klasse gemeinsam mit einer öffentlichen Schnittstelle in einer Java-Datei gespeichert (oder genauer – kompiliert) werden. Finale Klassen – der Modifier final Finale Klassen können nicht weiter abgeleitet werden. Mit anderen Worten: Sie dürfen keine Subklassen haben. Der Modifier final muss zu diesem Zweck am Beginn der Klassendeklaration gesetzt werden. Beispiel: final class Unicode
Finale Klassen dienen beispielsweise zum Erstellen eines Standards, der nicht mehr verändert werden soll. Testen wir in einem kleinen Beispiel, was passiert, wenn Sie eine Subklasse von einer finalen Klasse bilden wollen. Dabei können Sie beide Klassen in eine Java-Quelltextdatei ModifierTest.java notieren. Der Compiler macht daraus zwei .class-Dateien. final class MeineFinaleKlasse { int a = 42; } class ModifierTest extends MeineFinaleKlasse { }
Java 2 Kompendium
Listing 6.32: Versuchte Ableitung einer finalen Klasse
281
Kapitel 6
Java – die Hauptbestandteile der Sprache Wenn Sie das Beispiel kompilieren wollen, meldet der Compiler folgenden Fehler: ModifierTest.java:5: cannot inherit from final MeineFinaleKlasse class ModifierTest extends MeineFinaleKlasse ^ 1 error
Abstrakte Klassen – der Modifier abstract Unter einer abstrakten Klasse versteht man eine Klasse, von der nie eine direkte Instanz benötigt wird und von der auch keine Instanz gebildet werden kann (siehe dazu Seite 160). Prinzipiell dienen abstrakte Klassen dazu, unvollständigen Code zu deklarieren. Es handelt sich also in der Regel um eine Klasse, in der mindestens eine Methode nicht vollständig ist. Wenn Sie dies ohne die Kennzeichnung als abstrakt durchführen, wird durch den javac-Compiler ein Fehler erzeugt. Die Deklaration einer Klasse als abstrakt bedeutet jedoch nicht, dass vollständiger Code einen Fehler erzeugt. Auch muss eine abstrakte Klasse keinen unvollständigen Code enthalten. Es ist nur wenig sinnvoll, solchen Code als abstrakt zu kennzeichnen. Java charakterisiert abstrakte Klassen mit dem Schlüsselwort abstract. Abstrakte Klassen werden im Allgemeinen zur Strukturierung eines Klassenbaums verwendet. Eine weitere Aufgabe von ihnen ist die Vereinbarung von Methodenschnittstellen, auch wenn die Methoden selbst erst zu einem späteren Zeitpunkt konkret implementiert werden. Die Namen der Methoden und deren Parameter sind dann trotzdem in den Unterklassen schon bekannt. Noch eine Aufgabe für abstrakte Klassen besteht darin, zwei oder mehrere Klassen zusammenzufassen, die zwar einige Gemeinsamkeiten besitzen, jedoch nicht auseinander herzuleiten sind und wo keine gemeinsame »normale« Superklasse vorhanden ist. Das soll bedeuten, es ist nicht offensichtlich, welche die Superklasse der anderen ist. In einer abstrakten Oberklasse können die Gemeinsamkeiten zusammengefaßt werden. Die Bezeichnung »Unvollständig« legt noch eine Verwendung nahe: es können dort alle Eigenschaften untergebracht werden, die für sich alleine noch keine vollständige Funktionalität gewährleisten, jedoch bereits ein Grundgerüst festlegen, das dann relativ leicht in einer Subklasse vervollständigt werden kann. Testen wir ein Beispiel für eine abstrakte Klasse und deren Vervollständigung in einer Subklasse (AbstractTest.java).
282
Java 2 Kompendium
Klassen und Objekte abstract class MeineAbstrakteKlasse { abstract void test(); int test2() { System.out.println("abc"); return(0); } } class AbstractTest extends MeineAbstrakteKlasse { void test() { System.out.println("Das war mal abstrakt"); test2(); } public static void main(String args[]) { AbstractTest x = new AbstractTest(); x.test(); x.test2(); } }
Kapitel 6 Listing 6.33: Die Anwendung einer abstrakten Klasse
Bei dem Beispiel greifen wir einigen Techniken vor, die erst in der Folge konkret besprochen werden. Sie sind aber für eine Demonstration notwendig. 1.
Die Methode test() in der abstrakten Klasse besteht nur aus der Methodendeklaration, hat also keinen Methoden-Körper. Eine solche Methode muss – wie auch die Klasse – den Modifier abstract vorangestellt bekommen.
2.
Die Methode test2() in der abstrakten Klasse ist vollständig. Das ist wie gesagt kein Widerspruch zu Abstraktheit der Klasse.
3.
In der Subklasse wird die Methode test() überschrieben, d.h. mit einem Methodenkörper versehen.
4.
In der main()-Methode erfolgt der Zugriff auf die Methoden über das aus der Klasse erzeugte Objekt.
Wenn Sie das Programm ausführen, erhalten Sie folgende Ausgabe: Das war mal abstrakt abc abc
Testen Sie die Wirkung von einer abstrakten Klasse als Superklasse, indem Sie die Methode test() in der Subklasse nicht redefinieren und auch nicht anwenden. Dazu können Sie einfach die Zeilen void test() { System.out.println("Das war mal abstrakt"); test2();
Java 2 Kompendium
283
Kapitel 6
Java – die Hauptbestandteile der Sprache } und x.test();
auskommentieren. Benennen Sie außerdem die Klassen am besten um in AbstractTest2 und MeineAbstrakteKlasse2. Speichern Sie das Beispiel unter AbstractTest2.java: abstract class MeineAbstrakteKlasse2 { abstract void test(); int test2() { System.out.println("abc"); return(0); } } class AbstractTest2 extends MeineAbstrakteKlasse2 { /* void test() { System.out.println("Das war mal abstrakt"); test2(); } */ public static void main(String args[]) { AbstractTest2 x = new AbstractTest2(); // x.test(); x.test2(); } }
Wenn Sie dann versuchen, das Beispiel zu kompilieren, erhalten Sie folgende Fehlermeldung: AbstractTest2.java:10: AbstractTest2 should be declared abstract; it does not define test() in MeineAbstrakteKlasse2 class AbstractTest2 extends MeineAbstrakteKlasse2 ^ 1 error
Die Interpretation dieser Meldung ist folgende: Die Subklasse einer abstrakten Klasse muss sämtliche (!) dort abstract definierten Methoden vervollständigen. Andernfalls muss die Subklasse selbst wieder als abstrakt deklariert werden. In der abstrakten Superklasse bereits vollständig definierte Methoden können dagegen in der Subklasse unmittelbar verwendet werden. An dieser Stelle nochmals der Hinweis, dass die Schlüsselwörter final und abstract nicht zusammen in einer Klasse verwendet werden können. Das ist aber offensichtlich, denn eine finale Klasse kann nicht vererbt werden, eine abstrakte hingegen muss, um davon einen Nutzen zu haben.
284
Java 2 Kompendium
Klassen und Objekte
6.6.4
Kapitel 6
Der Klassenname
Jede Klasse benötigt einen Namen. Damit ist fast schon das Wichtigste gesagt, was für den Klassennamen von Bedeutung ist. Es gibt jedoch noch einige wenige Regeln, die die Vergabe von Klassennamen beschränken. Wir kennen die Regeln schon von den Token im Allgemeinen. Zwingende Namenskonventionen für Klassen 1.
Klassennamen dürfen im Prinzip eine unbeschränkte Länge haben (bis auf technische Einschränkungen durch das Computersystem).
2.
Genau wie bei allen anderen Bezeichnern in Java sind die einzigen Bedingungen für Klassennamen, dass diese mit einem Buchstaben oder einem der beiden Zeichen _ oder $ beginnen müssen. Es dürfen weiter nur Unicode-Zeichen oberhalb von dem Hexadezimalwert 00C0 (Grundbuchstaben und Zahlen sowie einige andere Sonderzeichen) verwendet werden.
3.
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 Tokens identisch ist, d.h. den vollkommen identischen Unicode-Wert hat. Java unterscheidet deshalb Groß- und Kleinschreibung. Die beiden Klassennamen HelloJava und helloJava sind nicht identisch.
4.
Klassennamen dürfen keinem Java-Schlüsselwort gleichen und sie sollten nicht mit Namen von Java-Paketen identisch sein.
Es gibt neben den zwingenden Regeln ein paar unverbindliche, aber gängige Konventionen für Klassennamen. Übliche Konventionen für Klassennamen 1.
Man sollte möglichst sprechende Klassennamen verwenden.
2.
Die Identifier von Klassen sollten mit einem Großbuchstaben beginnen und anschließend klein geschrieben werden. Wenn sich ein Bezeichner aus mehreren Worten zusammensetzt, dürfen diese nicht getrennt werden. Die jeweiligen Anfangsbuchstaben werden jedoch innerhalb des Gesamtbezeichners jeweils groß geschrieben.
6.6.5
Superklassen und das Schlüsselwort extends
Wir wollen in Java nicht jedes Mal das Rad neu erfinden, sondern bereits bestehende Funktionalitäten immer wieder nutzen und nur für unsere Zwecke anpassen. Vererbung und Erweiterung einer Superklasse sind die OOZauberwörter.
Java 2 Kompendium
285
Kapitel 6
Java – die Hauptbestandteile der Sprache Und wie geht das? Ganz einfach. Mit dem Schlüsselwort extends. Sie spezifizieren damit die Klasse, auf die Ihre neue Klasse aufbaut (die Superklasse). Durch die Erweiterung einer Superklasse machen Sie aus Ihrer Klasse eine neue Kopie dieser Klasse und ermöglichen gleichzeitig Veränderungen an dieser neuen Kopie. Wenn Sie (was jedoch absolut sinnlos ist) die neue Klasse überhaupt nicht verändern würden und sowohl den Körper leer, als auch den Modifier der Klasse unverändert ließen, dann würden sich die Klassen identisch verhalten. Ziehen wir wieder einmal ein Beispiel heran, wo wir eine Klasse als Ableitung einer Superklasse schon verwendet haben .
Listing 6.34: Ein Applet, das von java.applet.Applet abgeleitet wurde.
import java.awt.Graphics; public class HelloJavaApplet extends java.applet.Applet { public void paint(Graphics g) { g.drawString("Hello Java!", 5, 25); } }
Das war unser erstes Applet. Es ist als Subklasse der Klasse java.applet.Applet definiert. Wir haben sie einfach ein bißchen erweitert. Einige Anmerkungen zur Vererbung in Java Wenn Sie sich erinnern, hatten wir schon angeführt, dass in Java jede Klasse letztendlich eine Ableitung einer einzigen obersten Klasse – der Klasse java.lang.Object – ist. Um das System konsitent zu halten, werden so genannte Metaklassen definiert (siehe dazu Seite 160). Wenn Sie also Ihre Klasse nicht als Erweiterung irgendeiner anderen Klasse deklarieren, so wird grundsätzlich angenommen, dass sie eine Erweiterung der Klasse java.lang.Object darstellt. Die zweite Anmerkung soll noch einmal betonen, dass es in Java keine Mehrfachvererbung gibt. Deshalb können Java-Klassen, anders als in C++, immer nur eine Superklasse haben.
6.6.6
Designregeln für Klassen
Wenn Sie Klassen erstellen, gibt es einige kleine Regeln für das Design. Diese sind zwar als unverbindlich anzusehen, jedoch als Richtschnur ganz hilfreich. Insbesondere dann, wenn die Projekte umfangreicher werden3. 1.
3
286
Daten sollten soweit wie möglich privat deklariert werden (dazu kommen wir gleich). Es entspricht dem objektorientierten Konzept, nur die Informationen nach außen zu geben, die für eine konkrete Funktionalität notwendig und sinnvoll sind. Sie unterbinden durch die GeheimhalFür kleinere Projekte – wie auch unsere Beispiele – kann man meist darauf verzichten.
Java 2 Kompendium
Klassen und Objekte
Kapitel 6
tung von Daten einer Klasse zugleich, dass diese in Anwendungen dieser Klasse verändert werden. Damit lassen sich Veränderungen innerhalb der Klasse leichter realisieren, weil die Effekte auf Anwendungen der Klasse minimiert sind. Ergänzend lassen sich Bugs leichter entdecken. 2.
Es gibt in Java Variablentypen, die nicht zwingend initialisiert werden müssen. Tun Sie es dennoch.
3.
Teilen Sie eine Klasse auf, wenn Sie viele Variablen deklarieren wollen. Erstellen Sie eine Klasse mit den Variablen und binden Sie diese dann in die eigentliche Klasse ein.
4.
Vergeben Sie möglichst sprechende Namen für Ihre Klassen. Dies gilt auch übertragen für Methoden und Felder, aber gerade da hat sich ein ziemlicher Abkürzungswahn durchgesetzt. Für Klassen sollten Sie aber auf jeden Fall bei sprechenden Namen bleiben.
6.6.7
Innere und anonyme Klassen bzw. Schnittstellen
Eine innere Klasse ist innerhalb einer anderen Klasse definiert. Sonst gibt es eigentlich keinen Unterschied zu »normalen« Klassen. Klassen und Schnittstellen können damit innerhalb anderer Klassen eingebettet werden. Solche inneren Klassen können ausschließlich die Klassen unterstützen, in die sie integriert sind. Die Verwendung von inneren Klassen ist seit Java 1.1 möglich. Es gibt dafür im Wesentlichen die folgenden Gründe: 1.
Ein Objekt einer inneren Klasse kann auf die Implementation des Objekts zugreifen, das es kreiert hat. Auch auf Daten, die dort als privat deklariert wurden!
2.
Innere Klassen können vor den anderen Klassen des gleichen Pakets versteckt werden.
3.
Die Verwendung von inneren Klassen ist bei der Erstellung von ereignisbezogenen Anwendungen sehr bequem.
Das Gerüst einer solchen Struktur sieht ungefähr so aus: class SchreibeDatei { public class UeberpruefeLaufwerk { // Rest von UeberpruefeLaufwerk } // Rest von SchreibeDatei }
Listing 6.35: Skizzierte Struktur einer inneren Klasse
Der Name einer inneren Klasse (oder genauer – der Zugriff darauf) wird durch die umschließende Klasse bestimmt. Es gilt die Punktnotation. In unserem skizzierten Beispiel erfolgt der Zugriff auf die innere Klasse über
Java 2 Kompendium
287
Kapitel 6
Java – die Hauptbestandteile der Sprache SchreibeDatei.UeberpruefeLaufwerk .
»Anonymous Classes« steht für eine Abart der inneren Klassen. Es handelt sich um eine Kurzform von inneren Klassen. Sie bekommen keinen Namen, sondern nur eine Implementation mit new. Der Compiler generiert bei anonymen Klassen eine namenlose (anonymous) Klasse, die wie spezifiziert eine bestehende Klasse dann überschreibt. Das Ende einer anonymen Klassen wird durch das Ende des mit new eingeleiteten Ausdrucks festgelegt. Skizziert sieht das so aus: Listing 6.36: Skizzierte anonyme Klasse
private Vector history = new Vector(); public void watch(Ueberwache o) { o.addObserver(new Observer() { public void update(Ueberwache o, Object arg) { history.addElement(arg); } }; }
Wie Anmerkung drei bei inneren Klassen schon andeutet, werden innere bzw. anonyme Klassen gerne im Zusammenhang mit der Behandlung von Ereignissen verwendet. Wir wollen deshalb auf die Erstellung von interaktiven grafischen Oberflächen vorgreifen und eine solche innere (oder genau genommen anonyme) Klasse in Aktion sehen. Listing 6.37: Die Anwendung einer anonymen Klasse
import java.awt.*; import java.awt.event.*; class Anonym extends Frame { public Anonym() { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e){ dispose(); System.exit(0); } });} public static void main(String args[]) { Anonym mainFrame = new Anonym(); mainFrame.setSize(400, 400); mainFrame.setTitle("Test"); mainFrame.setVisible(true); } }
Das Beispiel erzeugt ein Fenster. Die Anwendung der anonymen Klasse besteht darin, dass in der Methode addWindowListener() ein Adapter erzeugt wird, worin die Reaktion auf den Klick auf das Schließsymbol des Fensters realisiert wird.
288
Java 2 Kompendium
Klassen und Objekte
Kapitel 6 Abbildung 6.15: Die Anwendung einer anonymen Klasse – hier wird der Schließbutton mit Funktionalität versehen
Innere Klassen und Interfaces können dieselben Zugriffsmodifizierer verwenden wie die anderen Mitglieder einer Klasse.
6.6.8
Adapterklassen
Eine Adapterklasse ist eine Klasse, die ein Interface mit leeren Methodenrümpfen implementiert, die bei Bedarf (also immer, wenn eine Methode nicht als void deklariert ist) nur einen Standardwert zurückgeben. Adapterklassen werden deshalb verwendet, damit eine Klasse, die eine Schnittstelle nur teilweise nutzen möchte (also etwa nur eine Methode aus einer ganzen Liste von dort deklarierten Methoden), nicht alle Methoden der Schnittstelle überschreiben muss. Das leistet ja wie gesagt die Adapterklasse, die dann als Superklasse der eigentlichen Klasse fungiert. Nachteil: Damit ist der Weg der Vererbung aufgebraucht. Adapterklassen findet man hauptsächlich bei der Eventprogrammierung.
6.6.9
Konstruktoren und der new-Operator
Der Begriff Konstruktoren (Constructors) ist schon einige Male gefallen. Es handelt sich um sehr spezielle Methoden (deshalb wird auch oft von Konstruktormethoden gesprochen), die bei der Erstellung einer Instanz genutzt werden, um ein Objekt zu initialisieren und bestimmte Eigenschaften der Instanz festzulegen, Aufgaben auszuführen und Speicher für die Instanz zu allokieren.
Java 2 Kompendium
289
Kapitel 6
Java – die Hauptbestandteile der Sprache Methoden im Allgemeinen werden wir im Detail gleich im Anschluss behandeln. Konstruktoren müssen immer denselben Namen wie die Klasse selbst haben! Weiterhin dürfen Konstruktoren (obwohl sie Methoden sind) keine Rückgabeparameter haben (nicht einmal die Deklaration als void ist erlaubt), weil sie ausschließlich dazu benutzt werden, die Instanz der Klasse zurückzugeben. Meist sind Konstruktoren public. Sie dürfen nicht als native, abstract, static, synchronized oder final deklariert werden. Aber wie werden nun Konstruktoren konkret eingesetzt? Wenn eine Instanz einer Klasse erstellt wird, ist es nötig, dass ein Speicherbereich für verschiedene Informationen reserviert wird. Wenn Sie nun eine Variable für eine Instanz am Anfang einer Klasse deklarieren, dann sagen Sie dem Compiler damit lediglich, dass eine Variable eines bestimmten Namens in dieser Klasse verwendet wird. Daher ist es notwendig, dass Sie der Variable zusätzlich unter Verwendung des Operators new eine konkretes Objekt zuweisen. Die Deklaration der Variable ist eine gewöhnliche Variablendeklaration, nur ist der Typ der Variablen vom Typ des Objekts, das darin gespeichert werden soll. Die Syntax sieht folgendermaßen aus: ;
Die Zuweisung der konkreten Klasseninstanz mit dem Konstruktor erfolgt dann so: = new ();
Die beiden Schritte werden oft zusammen erledigt. Das sieht dann folgendermaßen aus: = new (); instanzderKlasse ist die Variable, welche die Instanz der Klasse KlassenName
aufnimmt. Der Name der Klasse taucht zweimal auf. Auf der linken Seite ist es in der Tat die Klasse selbst, aber rechts der Name der Konstruktormethode. Achten Sie unbedingt auf die Klammern. Diese müssen auf jeden Fall vorhanden sein (ein Konstruktor ist wie gesagt eine Methode!), während die Parameter darin optional sind.
290
Java 2 Kompendium
Klassen und Objekte
Kapitel 6
Allgemein wird mit einem Konstruktor der Compiler angewiesen, Speicherplatz für eine Instanz dieser Klasse zu reservieren und den Namen dieser Instanz der entsprechenden Speicheradresse zuzuordnen. In jeder Klasse gibt es eine Konstruktormethode ohne Parameter, auch wenn sie nicht explizit deklariert wird. Dann wird sie von einer Superklasse vererbt. D.h., wenn in einer Klasse keine Konstruktormethode definiert wurde, dann ruft Java eine Default-Konstruktormethode ohne Parameter auf. Es kann in einer Klasse mehrere Konstruktormethoden geben. Konstruktoren verhalten sich wie andere Methoden und können überladen werden. Damit ist es möglich, ein Objekt auf vielfältige Art zu erstellen, indem mehrere Konstruktoren mit unterschiedlichen Parametern erstellt werden. Die Konstruktoren müssen sich wie andere Methoden innerhalb einer Klasse irgendwo in der Methodenunterschrift unterscheiden, damit sie überladen werden können. Die Methodenunterschrift bei Konstruktoren lässt dazu nicht viel Freiheiten, denn der Name des Konstruktors (ein Teil der Methodenunterschrift) ist zwingend mit dem Namen der Klasse identisch. Es kann also nur Unterschiede in der Parameterliste geben. Die Parameterliste dient zum Unterscheiden von verschiedenen Konstruktoren einer Klasse. Mehr dazu finden Sie bei den Methoden, Seite 311. Das nachfolgende Programmbeispiel soll die Verwendung des new-Operators mit drei verschiedenen Konstruktoren demonstrieren. Dabei greifen wir auf eine Standardklasse von Java zurück (Date), die wir in der ersten Zeile importieren. Diese beinhaltet mehrere Konstruktormethoden. import java.util.Date; class Konstrukt1 { public static void main (String args[]) { Date datum1, datum2, datum3; datum1 = new Date(); System.out.println("Datum 1 ist " + datum1); datum2 = new Date(97, 8, 8); System.out.println("Datum 2 ist: " + datum2); datum3 = new Date("April, 17, 1963, 3:24, PM"); System.out.println("Datum 3 ist " + datum3); } }
Listing 6.38: Die Anwendung verschiedener Konstruktoren
Den drei Variablen datum1, datum2 und datum3 werden jeweils andere Konstruktoren der Klasse Date() mit dem new-Operator zugewiesen (über die unterschiedlichen Parameter). Sie enthalten also drei verschiedene Instanzen von der Klasse Date, die sich dann auch in unterschiedlichen Formaten in der Ausgabe äußern. Java 2 Kompendium
291
Kapitel 6
Java – die Hauptbestandteile der Sprache Beim Kompilieren des Beispiels werden Sie wahrscheinlich eine Warnung erhalten, dass Elemente als deprecated gelten. Das ist für uns hier kein Problem. Die Warnung bedeutet nur, dass es für diese Elemente neuere Varianten gibt, die statt der veralteten Elemente verwendet werden sollten. Die alten »Knaben« funktionieren aber immer noch. Wir haben jetzt gesehen, wie die Konstruktoren einer Klasse zum Erstellen eines Objekts aufgerufen werden, aber noch nicht, wie sie innerhalb der Klasse erstellt werden. Erstellen wir dazu am besten eine eigene Klasse mit mehreren Konstruktoren zum Überladen und erzeugen dann davon mehrere Objekte. (Hinweis: Speichern Sie das Beispiel unter Konstrukt2.java ab.)
Listing 6.39: Erzeugung von verschiedenen Konstruktoren
292
class MeineKonst { int a = 1; int b; static int c; MeineKonst() { } public MeineKonst(int b) { this.b = b; } public MeineKonst(int c, int d) { b = c * d; } } public class Konstrukt2 { int aussen=5; public static void main(String args[]) { Konstrukt2 u = new Konstrukt2(); System.out.println( "Die Variable aussen in der Klasse Konstrukt2: " + u.aussen); MeineKonst x = new MeineKonst(); System.out.println( "Variable a in Klasse MeineKonst mit Objekt x " + "(leerer Konstruktor): " + x.a); x.b = 4; System.out.println( "Variable b in Klasse MeineKonst mit Objekt x " + "(leerer Konstruktor): " + x.b); MeineKonst y = new MeineKonst(42); System.out.println( "Variable b in Klasse MeineKonst mit Objekt y " + "(Konstruktor mit einem int): " + y.b); y.c = 4; System.out.println( "Klassenvariable c in Klasse MeineKonst " + "(Konstruktor mit einem int): " + y.c); MeineKonst z = new MeineKonst(5,6);
Java 2 Kompendium
Klassen und Objekte
Kapitel 6
System.out.println( "Variable a in Klasse MeineKonst mit Objekt z " + "(Konstruktor mit zwei int): " + z.a); System.out.println( "Variable b in Klasse MeineKonst mit Objekt z " + "(Konstruktor mit zwei int): " + z.b); } }
Die erste Klasse beinhaltet drei Konstruktoren, wo jeweils Variablen deklariert werden. In der Klasse Konstrukt2 werden damit drei Instanzen erzeugt und die verschiedenen Variablen über die erzeugten Objekte ausgegeben. Die Ausgabe sieht folgendermaßen aus: Die Variable aussen in der Klasse Konstrukt2: 5 Variable a in Klasse MeineKonst mit Objekt x (leerer Konstruktor): 1 Variable b in Klasse MeineKonst mit Objekt x (leerer Konstruktor): 4 Variable b in Klasse MeineKonst mit Objekt y (Konstruktor mit einem int): 42 Klassenvariable c in Klasse MeineKonst (Konstruktor mit einem int): 4 Variable a in Klasse MeineKonst mit Objekt z (Konstruktor mit zwei int): 1 Variable b in Klasse MeineKonst mit Objekt z (Konstruktor mit zwei int): 30 Abbildung 6.16: Verschiedene Konstruktoren im Einsatz
In diesem Beispiel sind einige interessante Details zu beachten. Sie finden hier den Einsatz des Schlüsselworts this. Dieses erlaubt es, aus einer Methode auf das Objekt zuzugreifen, in dem die Methode enthalten ist.
Java 2 Kompendium
293
Kapitel 6
Java – die Hauptbestandteile der Sprache Auf this gehen wir auf Seite 299 näher ein. Die verschiedenen Konstruktoren in der Klasse MeineKonst werden überladen. Die Identifikation des richtigen Konstruktors erfolgt über den Typ und die Anzahl der Parameter, die beim Aufruf verwendet werden. Wenn ein Konstruktor nicht in der aktuellen Klasse gefunden wird, wird die Suche in der Superklasse fortgesetzt. Wenn Sie selbst einen (beliebigen) Konstruktor in einer Klasse erzeugen, haben Sie keinen Zugriff mehr auf den Default-Konstruktor (der sonst aus einer Superklasse vererbt wird). Diesen müssen Sie entweder reimplementieren (ein leerer Methodenkörper genügt bereits) oder Sie verwenden den Zugriff über das Schlüsselwort super, um den Konstruktor der Superklasse anzusprechen. Das wollen wir anhand von Beispielen demonstrieren.
Listing 6.40: Aufruf des DefaultKonstruktors zur Erzeugung eines Objekts und Zugriff darüber auf die Instanzvariable
public class Konstrukt3 { int a = 5; public static void main(String args[]) { Konstrukt3 u = new Konstrukt3(); System.out.println( "Die Instanzvariable a ueber das Objekt u: " + u.a); } }
Das Beispiel Konstrukt3 verwendet den – in der Klasse nicht explizit implementierten – Default-Konstruktor. Er wird über die Vererbung aus der Superklasse (in diesem Beispiel die Superklasse aller Java-Objekte – Object) bereitgestellt. Wenn wir das Beispiel geringfügig verändern, hat man keinen Zugriff mehr. Listing 6.41: Ein selbst definierter Konstruktor, aber Erzeugung eines Objekts mit dem parameterlosen DefaultKonstruktor
public class Konstrukt4 { Konstrukt4(int a) { } int a = 5; public static void main(String args[]) { Konstrukt4 u = new Konstrukt4(); System.out.println( "Die Instanzvariable a ueber das Objekt u: " + u.a); } }
Das Beispiel Konstrukt4 führt einen neuen Konstruktor ein, der einen Parameter verwendet. Er wird aber in dem Beispiel nicht verwendet, sondern es wird versucht, in der main()-Methode den parameterlosen Default-Konstruktor zu verwenden. Wenn Sie das Beispiel aber kompilieren, erhalten Sie folgende Fehlermeldung: 294
Java 2 Kompendium
Klassen und Objekte
Kapitel 6
Konstrukt4.java:9: cannot resolve symbol symbol : constructor Konstrukt4 () location: class Konstrukt4 Konstrukt4 u = new Konstrukt4(); ^ 1 error
Obwohl der Default-Konstruktor nicht explizit überschrieben wurde, steht er nicht mehr zur Verfügung. Zum Stichwort » Ü berschreiben« siehe Seite 311 Das Problem bleibt auch bestehen, wenn in einer Superklasse ein expliziter Konstruktor definiert wurde. Auch dann kann man in der Subklasse nicht mehr auf den Default-Konstruktor zugreifen. /* Nicht lauffähig – zur Demonstration eines Fehlers */ class SKlasse { SKlasse(int a, int b) { } } public class Konstrukt5 extends SKlasse { int a = 5; public static void main(String args[]) { Konstrukt5 u = new Konstrukt5(); System.out.println( "Die Instanzvariable a ueber das Objekt u: " + u.a); }}
Listing 6.42: Auch das geht nicht
Konstruktoren kann man zwar – wie wir gesehen haben – überladen, aber Überschreiben geht rein technisch nicht so, wie es bei gewöhnlichen Methoden in Java möglich ist. Das liegt daran, dass Konstruktoren immer denselben Namen wie die Klasse haben müssen und sich die Namen von Superklasse und Subklasse unterscheiden müssen. Aber es gilt, dass auch ein Konstruktor mit veränderter Methodenunterschrift den Konstruktor der Superklasse verdeckt. Den verdeckten Konstruktor müssen Sie entweder redefinieren oder darauf über super zugreifen, wie im nachfolgenden Beispiel: public class Konstrukt6 { int a = 5; float b; Konstrukt6() { } Konstrukt6(int b) { this.a=b; }
Java 2 Kompendium
Listing 6.43: Zugriff auf verdeckte Konstruktoren
295
Kapitel 6
Java – die Hauptbestandteile der Sprache Konstrukt6(float c) { super(); this.b=c; } public static void main(String args[]) { Konstrukt6 u = new Konstrukt6(); System.out.println( "Die Instanzvariable a ueber das Objekt u: + u.a); Konstrukt6 v = new Konstrukt6(6); System.out.println( "Die Instanzvariable a ueber das Objekt v: + v.a); Konstrukt6 w = new Konstrukt6(6); System.out.println( "Die Instanzvariable a ueber das Objekt w: + w.a); Konstrukt6 x = new Konstrukt6(1.2f); System.out.println( "Die Instanzvariable b ueber das Objekt x: + x.b); }
"
"
"
"
}
Die Ausgabe sieht dann wie folgt aus: Die Instanzvariable a ueber das Objekt u: 5 Die Instanzvariable a ueber das Objekt v: 6 Die Instanzvariable a ueber das Objekt w: 6 Die Instanzvariable b ueber das Objekt x: 1.2
6.6.10
Das Speichermanagement
Java verfügt über ein ausgefeiltes Speichermanagement. Immer wenn mit dem new-Operator und einem Konstruktor eine neue Instanz einer Klasse erstellt wird, belegt das Laufzeitsystem von Java einen Teil des Speichers, in dem die zu der Klasse bzw. ihrer Instanz gehörenden Informationen abgelegt werden. Sie müssen zwar damit für jedes neue Objekt eine Zuweisung von Speicherplatz vornehmen, jedoch nicht mehr die benötigte Grö ß e des Speicherplatzes spezifizieren, sondern nur noch den Namen des benötigten Objekts. Das bedeutet ebenfalls, dass der new-Anweisung bereits der richtige Datentyp zugewiesen wurde, wenn sie eine Referenz auf einen zugewiesenen Speicherbereich zurückgibt, weshalb sie keine Datentypkonvertierung wie in C benötigt. Wie soll aber dieser Speicher wieder freigegeben werden, wenn das Objekt nicht länger benötigt wird? Bei Speichermanagement fällt vielen C-Program296
Java 2 Kompendium
Klassen und Objekte
Kapitel 6
mierern garantiert der Begriff Destruktor als potenzielles Gegenstück zum Konstruktor ein. Wir haben ja gesehen, dass Java beim Erzeugen einer Instanz einen Konstruktor aufruft und da ist es naheliegend, dass zum Beseitigen des Objekts aus dem Speicher ein Destruktor aufgerufen werden muss. Wir haben es uns jedoch an verschiedenen Stellen schon klar gemacht, dass dies nicht der Fall ist. Java gibt Speicherplatz immer automatisch mit einem Papierkorb frei. Es gibt in diesem Sinn keinen Destruktor in Java. Mit der Benutzung des Operators new wird von Ihnen zum einen die explizite Zuweisung von Speicherplatz für jedes neue Objekt verlangt, zum anderen wird aber auch sichergestellt, dass es automatisch zerstört werden kann und der Speicher dann wieder vollkommen freigegeben wird. Dafür ist die Garbage Collection zuständig, mit der Java automatisch Objekte freigibt, wenn es keine Referenzen mehr auf diese Objekte gibt. Die explizite Speicherfreigabe (wie sie etwa in C/C++ mit der Methode free() durchgeführt wird) ist schlicht und einfach überflüssig. Der Speicher wird also immer automatisch freigegeben, der Papierkorb kann aber auch mit der Methode System.gc() direkt manuell aufgerufen werden. Dies ist jedoch in der Regel weder notwendig, noch zu empfehlen. Falls Sie sich um die Speicherbereinigung und vor allem den ordnungsgemäßen Abschluss von Prozessen selbst kümmern wollen, steht die Methode finalize() zur Verfügung. Der Finalisierer – die Methode finalize() Es gibt Situationen, in denen es erforderlich ist, dass ein Objekt vor seiner Beseitigung noch gezielte Abschlussaktionen ausführen sollte, beispielsweise das Beenden von Leseoperationen aus Dateien oder das Schließen von offenen Netzwerkverbindungen. Man nennt diesen Beendigungsvorgang Finalisierung. Wenn man unter Java überhaupt von einem Gegenstück zu den Konstruktoren reden kann, dann ist dies die finalize()-Methode: protected void finalize() throws Throwable
Die finalize()-Methode gehört zur Klasse java.lang.Object und ist somit in allen Klassen vorhanden. Sie ist der Grundeinstellung nach leer und wird normalerweise während der automatischen Speicherbereinigung vom Runtime-System in Java aufgerufen. Sie hat die Aufgabe, laufende Prozesse ordnungsgemäß zu beenden, bevor ein Objekt zerstört wird. Eine recht ähnliche Struktur (die sogar ähnlich geschrieben wird), werden wir noch bei den Ausnahmen sehen – den finally-Block. Wenn man sicherstellen möchte, dass laufende Prozesse ordnungsgemäß beendet werden, sollte die finalize()-Methode zusätzlich direkt im Code untergebracht werden. Dies sieht dann ungefähr so aus: Java 2 Kompendium
297
Kapitel 6
Java – die Hauptbestandteile der Sprache void finalize() { ... irgendwelche Abschlussarbeiten ... }
Wenn dann die Instanz dieser Klasse nicht mehr benutzt und zerstört werden soll, wird die Methode automatisch aufgerufen, um alle Prozesse wie benötigt zu schließen. Man kann diese Methode gleichfalls mit finalize() direkt aufrufen. Beachten Sie, dass die Methode finalize() in java. lang.Object als protected deklariert ist und daher auch geschützt bleiben muss und maximal weiter eingeschränkt werden kann. Überdies ist der genaue Zeitpunkt, wann die Methode finalize() aufgerufen wird, nicht genau zu bestimmen, weil der Prozess der Garbage Collection nicht genau vorhersagbar ist. Der Garbage Collector läuft zwar als permanenter Hintergrundthread, jedoch nur mit niedriger Priorität. Daher ist die finalize()Methode eigentlich nur zum Abfangen von potenziellen Fehlersituationen zu gebrauchen. Standardabschlussarbeiten sollten besser an anderen Stellen im Code durchgeführt werden. Bei der Zerstörung von Applets sollten Sie nicht die finalize()-Methode, sondern die destroy()-Methode verwenden. Die allgemeinere finalize()Methode ist hier nicht zu verwenden, da diese im Gegensatz zur destroy()Methode nicht immer beim Beenden von einem Browser oder dem Neuladen eines Applets automatisch ausgeführt wird. Ein chronologischer Ablauf einer Speicherbereinigung in Java sieht etwa so aus: 1.
Zunächst prüft der Garbage Collector, ob es noch Verweise auf ein zu löschendes Objekt gibt. Wenn es keine Verweise mehr gibt, hängt das weitere Vorgehen davon ab, ob die zugehörige Klasse einen Finalisierer besitzt. Wenn kein Finalisierer vorhanden ist, wird das Objekt direkt entfernt und der Speicher sofort freigegeben.
2.
Falls das Objekt dagegen über einen Finalisierer verfügt, wird er von dem Garbage Collector aufgerufen.
3.
Nachdem die Methode finalize() aufgerufen wurde, wird erneut geprüft, ob es keine Verweise auf die Instanz mehr gibt. Diese erneute Überprüfung ist notwendig, da prinzipiell im Finalisierer wieder ein Verweis auf die Instanz erzeugt werden kann. Es wird der Speicher erst dann freigegeben, wenn die Prüfung keinen neuen Verweis gefunden hat.
Ein paar Anmerkungen zur finalize()-Methode. 1.
298
Es besteht keine Garantie dafür, dass finalize() ausgeführt wird. Es kann nämlich passieren, dass der Garbage Collector während der Laufzeit des Interpreters gar nicht aufgerufen wird. Java 2 Kompendium
Klassen und Objekte 2.
Der Finalisierer einer Instanz wird höchstens einmal aufgerufen.
3.
Falls bei der Ausführung von finalize() eine Exception erzeugt wird, die nicht in der Methode selbst abgefangen wird, dann wird die Ausführung wie sonst auch abgebrochen. Im Gegensatz zu anderen Methoden werden Exceptions im Finalisierer ignoriert und nicht an das Programm weitergeleitet.
4.
Der Aufrufzeitpunkt von finalize() ist nicht vorhersehbar.
5.
Es können keine Aussagen darüber gemacht werden, in welcher Reihenfolge Objekte entfernt werden.
6.6.11
Kapitel 6
Das Schlüsselwort this
Bei der Arbeit mit Konstruktoren hatten wir gerade das Schlüsselwort this verwendet. Um was geht es dabei? Über die Punktnotation kann man auf andere Klassen bzw. Objekte zugreifen. Wie aber kann eine Klasse bzw. ein Objekt auf sich selbst zugreifen? Dafür stellt Java dieses Schlüsselwort this zur Verfügung, das es ja ebenso schon in C/C++ gab. Es gibt im Allgemeinen zwei Situationen, die den Gebrauch dieser Variablen rechtfertigen: 1.
Es gibt in einer Klasse zwei Variablen mit gleichem Namen – eine gehört zu der Klasse, die andere zu einer spezifischen Methode in der Klasse. Die Benutzung der Syntax this. ermöglicht es, auf diejenige Variable zuzugreifen, die zu der Klasse gehört. Das genau haben wir in den Konstruktor-Beispielen gemacht.
2.
Eine Klasse muss sich selbst als Parameter für eine Methode weiterreichen bzw. der Rückgabetyp einer Methode ist eine Objektinstanz der Klasse. Ein Beispiel sind Applets, die auf Methoden wie beispielsweise showStatus() zugreifen können.
Das Schlüsselwort this kann aber allgemeiner verstanden werden. Es kann an jeder Stelle verwendet werden, an der das Objekt erscheinen kann, dessen Methode gerade aktiv ist. Etwa als Argument einer Methode, als Ausgabewert oder in Form der Punktnotation zum Zugriff auf eine Instanzvariable. In vielen Anweisungen steckt das Schlüsselwort implizit drin, denn man kann oft darauf verzichten, wenn die Situation eindeutig ist. Die nachfolgenden Beispiele zeigen die Verwendung von this in verschiedenen Varianten. Das Beispiel This1 zeigt, wie Sie innerhalb einer Methode auf eine Variable der Klasse zugreifen können. Beachten Sie, dass die Instanzvariable in der Klasse denselben Namen hat wie die als Parameter übergebene Variable. Ohne das vorangestellte this wäre die Zuweisung in der Methode meinemethode(int x) eine Selbstzuweisung. Die Zeile this.x = x;
Java 2 Kompendium
299
Kapitel 6
Java – die Hauptbestandteile der Sprache bedeutet, dass der Instanzvariable x der Wert zugewiesen wird, der als Parameter der Methode übergeben wird. Dass der Name des Parameters identisch ist, stört nicht. Java stellt dafür die Namensräume bereit, die eine Eindeutigkeit gewährleisten.
Listing 6.44: Einsatz von this, um auf die Variable der Klasse zuzugreifen
class This1 { int x = 0; void meinemethode(int x) { // Zuweisung der Variable in der Klasse mit dem // Wert des Parameters this.x = x; } void printwas() { System.out.println( "Wert der Variable in der Klasse: " + x ); } public static void main (String args[]) { // Erzeugen einer Instanz This1 a = new This1(); a.meinemethode(42); // Ausgabe der Variable der Klasse a.printwas(); } }
In der Methode meinemethode(int x) setzen Sie den Wert der Variable in der Klasse auf 42. Diese geben Sie dann in der Methode printwas() aus. Die Ausgabe wird Wert der Variable in der Klasse: 42
sein. Erstellen wir noch ein (relativ) einfaches Beispiel, das this verwendet. Allerdings setzen wir dort nicht den Wert der Variablen in der Klasse – wir fragen ihn nur ab. Dabei werden wir zwei Variablen gleichen Namens benutzen. Eine ist in der Klasse als Instanzvariable definiert, die andere als lokale Variable in der Methode, wo wir beide Werte ausgeben wollen. Das Schlüsselwort this kann wie das Schlüsselwort super nur im Körper einer nicht-statischen Methode verwendet werden.
Listing 6.45: Zugriff per this auf die Instanzvariable
300
class This2 { int x = 42; void meinemethode() { int x = 24; System.out.println( "Wert der Variable in der Klasse: " + this.x ); System.out.println( Java 2 Kompendium
Klassen und Objekte
Kapitel 6
"Wert der Variable in der Methode: " + x ); } public static void main (String args[]) { // Erzeugen einer Instanz This2 a = new This2(); a.meinemethode(); } }
Die Ausgabe wird Wert der Variable in der Klasse: 42 Wert der Variable in der Methode: 24
sein. Da das Schlüsselwort this so wichtig ist und gerade Einsteigern oft Schwierigkeiten bereitet, behandeln wir noch ein weiteres Beispiel. Hier nutzen wir this wieder, um aus einem Konstruktor auf das erst zur Laufzeit erzeugte Objekt (ein Frame) zugreifen zu können. Auf »diesem« Frame-Objekt fügen wir Schaltflächen hinzu, legen von »diesem« Frame-Objekt das Layout und die Grö ß e fest und zeigen »dieses« Frame-Objekt dann an4. import java.awt.*; import javax.swing.*; public class This3 extends JFrame { JButton jButton1 = new JButton(); FlowLayout flowLayout1 = new FlowLayout(); JButton jButton2 = new JButton(); public This3() { this.getContentPane().setLayout(flowLayout1); this.jButton2.setText("jButton2"); this.jButton1.setText("jButton1"); this.getContentPane().add(jButton1, null); this.getContentPane().add(jButton2, null); this.setSize(300,200); this.show(); } public static void main(String[] args) { This3 meinFrame1 = new This3(); } }
Listing 6.46: Ein Java-Programm mit grafischer Oberfläche
Beachten Sie, dass Sie das Programm nicht mit dem Schließbutton beenden können (es ist kein Eventhandling integriert). In der DOS-Box können Sie es mit STRG+C beenden.
4
Die Markierung des Wortes »diesem« soll deutlich machen, was this eigentlich bedeutet.
Java 2 Kompendium
301
Kapitel 6
Java – die Hauptbestandteile der Sprache
Abbildung 6.17: Ein Java-Programm mit grafischer Oberfläche, die mit this anzusprechen ist
Da der Frame im Konstruktor eindeutig identifizierbar ist, könnte auch durchgängig auf this verzichtet werden. So funktioniert das Beispiel auch: Listing 6.47: Das ginge auch, ist aber vom Quelltext nicht so eindeutig zu lesen
import java.awt.*; import javax.swing.*; public class This4 extends JFrame { JButton jButton1 = new JButton(); FlowLayout flowLayout1 = new FlowLayout(); JButton jButton2 = new JButton(); public This4() { getContentPane().setLayout(flowLayout1); jButton2.setText("jButton2"); jButton1.setText("jButton1"); getContentPane().add(jButton1, null); getContentPane().add(jButton2, null); setSize(300,200); show(); } public static void main(String[] args) { This4 meinFrame1 = new This4(); } }
6.7
Methoden
Methoden sind das Java-Gegenstück zu Funktionen in Programmiersprachen wie C/C++ oder PASCAL. Sie bilden das Kernstück jeder Klasse und sind für die Handhabung aller Aufgaben zuständig, die von dieser Klasse durchgeführt werden sollen. Zwar ist die eigentliche Implementierung der Methode im Body (Körper) der Methode enthalten. Zusätzlich gibt es wie bei den Klassen die Deklaration, die einen großen Teil von Informationen über die Methode enthält. Java ist in der Lage, rekursive Aufrufe von Methoden zu verwalten.
302
Java 2 Kompendium
Methoden
6.7.1
Kapitel 6
Deklaration einer Methode
Die Deklarationen von Methoden sehen generell folgendermaßen aus: [<modifiers>] ([<Parameter>]) [throws] [<ExceptionListe>]
Dabei ist alles in eckigen Klammern optional. Die konkret verwendete Kombination aus diesen Teilen der Deklaration nennt man die Methodenunterschrift. Diese besteht aus mindestens dem Namen der Methode, dem Rückgabetyp und den Klammern sowie dort eventuell enthaltenen Parametern. Oft wird für denselben Zusammenhang der Begriff Methodensignatur verwendet.
6.7.2
Die Zugriffsspezifizierung
Man kann bei Methoden wie bei Klassen den Zugriff beschränken. Es können zwar immer die Methoden innerhalb einer gegebenen Klasse auf alle anderen Methoden dieser Klasse zugreifen, jedoch für Methoden anderer Objekte können Beschränkungen aufgebaut werden. Beachten Sie dabei bitte, dass jede Klasse nur einen Zugriffsmodifier haben kann. Es macht wenig Sinn, wenn eine Methode als weniger streng deklariert ist als die Klasse, zu der sie gehört. Eine Klassendeklaration sollte also nie mehr einschränken, als die darin enthaltenen Methoden an maximalen Rechten haben sollen. Wir wollen bei Methoden bzgl. des Zugriffs von Zugriffsspezifier reden und den Begriff Modifier erst bei der Sichtbarkeit von Methoden verwenden. Damit sind wir nicht ganz konsistent zu der Terminologie bei den Klassen, aber es wurde ja bereits dort auf die mögliche Zerlegung des Modifiers in zwei Bestandteile (die Sichtbarkeit und den Benutzungsgrad) hingewiesen. Bei Klassen ist jedoch nicht die Vielfalt wie bei Methoden gegeben und deshalb ist eine solche Zerlegung der Übersichtlichkeit halber nur bei Methoden sinnvoll. Freundliche Methoden Der voreingestellte Defaultstatus einer Methode ist immer freundlich und wird immer dann verwendet, wenn Sie keine explizite Zugriffsspezifizierung am Anfang einer Deklaration vornehmen. Dies ist identisch mit Klassen. Die freundliche Grundeinstellung aller Methoden bedeutet, dass diese Methoden sowohl innerhalb der Klasse, als auch innerhalb des zugehörigen Paketes benutzt werden können.
Java 2 Kompendium
303
Kapitel 6
Java – die Hauptbestandteile der Sprache Beispiel: void speichereNichtgeheimeZahl()
Öffentliche Methoden Öffentliche Methoden werden mit dem Zugriffsspezifier public deklariert. Dies ist analog dem Verfahren bei Klassen. Dies bedeutet, dass alle Methoden auf public-Methoden zugreifen können, egal in welchen Klassen oder Paketen sie definiert sind. Die Erweiterung gegenüber freundlichen Methoden ist wieder die, dass sie auch von allen Methoden benutzt werden können, die nicht zum eigenen Paket gehören. Beispiel: public void speichereNichtgeheimeZahl()
Geschützte Methoden So genannte geschützte Methoden werden mit dem Zugriffsspezifier protected realisiert. Geschützte Methoden sind im Wesentlichen identisch mit öffentlichen Methoden, mit Ausnahme der Tatsache, dass sie nicht für Objekte außerhalb der Pakets der Klasse verfügbar sind. Allerdings haben Subklassen der Klasse immer noch den vollen Zugriff auf diese Methoden der Superklasse. Beispiel: protected void speichereGeheimzahl()
Ein Kompilierfehler kann unter Umständen darauf zurückzuführen sein, dass Sie versuchen, auf eine Methode außerhalb des sichtbaren Anwendungsbereichs einer geschützten Methode zuzugreifen. Die dann entstehende Fehlermeldung ist leider missverständlich, denn Sie weist nicht darauf hin, dass Sie versuchen, auf eine geschützte Methode zuzugreifen. Statt dessen sieht Sie etwa so aus: No method matching speichereGeheimzahl() found in class java.xyz.jhg.
Der Grund ist der, dass die geschützten Methoden vor nicht-privilegierten Klassen versteckt werden. Wenn deshalb eine Klasse kompiliert wird, die die Sicherheitsbestimmungen nicht erfüllt, werden Methoden dieser Art vor dem Compiler versteckt. Ähnliche Fehlermeldungen begegnen Ihnen, wenn Sie versuchen, außerhalb Ihrer Zugriffsrechte auf eine private oder freundliche Methode zuzugreifen, oder wenn Sie versuchen, von einer nicht-priviligierten Klasse auf ein Feld zuzugreifen.
304
Java 2 Kompendium
Methoden
Kapitel 6
Private Methoden Private Methoden stellen die höchste auf eine Methode anwendbare Sicherheitsstufe dar. Sie werden durch die Benutzung von dem Zugriffsspezifier private eingerichtet. Eine private Methode ist nur für die anderen Methoden in derselben Klasse verfügbar. Sogar eine Subklasse dieser Klasse kann auf eine private Methode nicht zugreifen. Beispiel: private void speichereGeheimzahl()
In früheren Java-Versionen war es erlaubt, so genannte privat geschützte Methoden zu deklarieren, indem die Zugriffs-Spezifier private und protected in Kombination verwendet wurden. Methoden mit einer solchen Kennzeichnung sollten sowohl für die Klasse als auch für deren Subklassen verfügbar sein, aber nicht für den Rest des Pakets oder für Klassen außerhalb des Pakets. Die Kombination ist seit dem JDK 1.2 verboten.
6.7.3
Die Methodenmodifier
Methodenmodifier ermöglichen es Ihnen, ähnlich wie Klassenmodifier, bestimmte Eigenschaften einer Methode festzulegen. So lässt sich eine Methode etwa in Instanz- und Klassenmethoden unterscheiden. Aber es gibt noch weitere Modifier. Klassenmethoden versus Instanzmethoden Klassenmethoden werden mit dem Modifier static realisiert. Beispiel: static void toggleStatus()
Diese Klassenmethoden oder statischen Methoden sind solche Methoden, die in der Klasse agieren und auf die verwandten Klassenvariablen (auf die wir noch bei der Besprechung der Variablen intensiver eingehen) direkt zugreifen können. Ein wichtiger Vertreter von Klassenmethoden ist die main()-Methode, auf der jedes eigenständige Java-Programm aufbaut. Für die Erklärung muss ein wenig ausgeholt werden. Es muss in Java zwischen einer spezifischen Instanz einer Klasse und der Klasse selbst unbedingt unterschieden werden. Klassenmethoden liegen außerhalb der konkreten Objekte, aber innerhalb der Klassen. Sie werden über das Metaklassenkonzept in die streng objektorientierte Theorie von Java eingebunden. Methoden einer Klasse sind global, betreffen also immer die gesamte Klasse samt
Java 2 Kompendium
305
Kapitel 6
Java – die Hauptbestandteile der Sprache sämtlicher Instanzen der Klasse. Klassenmethoden können deshalb an beliebigen Stellen genutzt werden, unabhängig davon, ob eine konkrete Instanz der Klasse existiert oder nicht. Eine Instanzmethode ist dagegen nur über ein Objekt verwendbar. Zwar können andere Instanzmethoden einer Klasse eine Instanzmethode aufrufen, aber das verlagert das Problem nur weiter. Die konkrete Anwendung erfolgt über ein Objekt. Die Deklaration von Instanz- und Klassenmethoden unterscheidet sich nicht sonderlich. Es gibt nur einen Faktor, der eine Instanzmethode und eine Klassenmethode unterscheidet. Durch das Platzieren des Modifiers static vor eine Methodendeklaration machen Sie diese Methode zu einer statischen Methode. Einige Aussagen zu Instanz- und Klassenmethoden sollen die Unterschiede verdeutlichen: Nicht-statische Methoden können zwar mit statischen Variablen (auf die wir noch zu sprechen kommen) arbeiten, statische Methoden demgegenüber nur mit statischen Variablen und anderen statischen Methoden. Es ist ein klassischer Fehler, wenn Sie versuchen, aus einer statischen Methode eine nicht-statische Variable oder eine nicht-statische Methode zu referenzieren. Sie erhalten dann eine Fehlermeldung der folgenden Art durch den Compiler: "Can't make a static reference to nonstatic..."
bzw. ...non-static variable x cannot be referenced from a static context
Versuchen Sie einmal, das nachfolgende Beispiel zu kompilieren – Sie werden die Fehlermeldung erhalten. Listing 6.48: So geht es nicht
/* Dieses Beispiel soll einen Fehler produzieren – nicht lauffähig */ class StaticTest { int x=42; // falsch // So ist es richtig // static int x=42; public static void main (String args[]) { System.out.println("x ist " + x); } }
Im auskommentierten Teil sehen Sie, wie die Variable richtig deklariert ist. Wenn Sie also eine Methode erstellen, die ausschließlich mit statischen Variablen operiert, dann sollten Sie diese Methode am besten auch als statisch deklarieren. Auch wenn sie eine Methode erstellen wollen, die während der 306
Java 2 Kompendium
Methoden
Kapitel 6
»Lebenszeit« der zugehörigen Klasse in einer Schleife läuft – wie zum Beispiel ein Thread, der aus einem Socket liest – und Sie wollen diese Informationen mit allen Instanzen dieser Klasse teilen, dann können Sie das erreichen, indem Sie diese Methode statisch machen. Auch wenn Sie mehrere Instanzen der Klasse erstellen müssen, ist das Resultat, dass Sie nur eine Kopie der Methode zu erstellen brauchen, was eine Menge Speicherplatz spart. Wir kommen bei der Behandlung von statischen Variablen nochmals auf Klassen- und Instanzmethoden zurück (Seite 325). Abstrakte Methoden Eine weitere Kennzeichnung von Methoden ist, dass sie als abstrakte Methoden festgelegt werden. Dies erledigt der Modifier abstract. Beispiel: abstract void toggleStatus(); Wir kennen den Modifier abstract ja schon von den Klassen. Im Falle von abstrakten Methoden handelt es sich um Methoden, die zwar deklariert sind, aber nicht in die aktuelle Klasse implementiert wurden. Das bedeutet, abstrakte Methoden besitzen keinen Körper. Daher muss eine abstrakte Methode in der Subklasse der aktuellen Klasse überschrieben und implementiert werden, bevor mit der Methode etwas anzufangen ist. Sie deklarieren eine abstrakte Methode, indem Sie das Schlüsselwort abstract vor den Methodennamen setzen, und den Body (Körper) der Methode durch ein einfaches Semikolon (;) ersetzen. Eine komplette abstrakte Methode könnte demnach folgendermaßen aussehen: abstract boolean sonstnix();
Ein paar Bemerkungen zu abstrakten Methoden: 1.
Weder statische Methoden noch Klassen-Konstruktoren dürfen als abstrakt deklariert werden.
2.
Abstrakte Methoden dürfen nie als final deklariert werden, weil dadurch verhindert wird, dass diese Methoden überschrieben werden können. Sie wären nutzlos.
3.
Eine Klasse, die eine abstrakte Methode enthält, muss selbst als abstrakt deklariert werden. Sofern eine Subklasse dieser Klasse nicht alle abstrakten Methoden überschreibt, muss auch sie als abstrakt deklariert werden.
Java 2 Kompendium
307
Kapitel 6
Java – die Hauptbestandteile der Sprache Finale Methoden Eine weitere Spezifizierung von Methoden erfolgt über den Modifier final. Dieser spezifiziert finale Methoden. Beispiel: final void toggleStatus(); Das Schlüsselwort final vor eine Methodendeklaration verhindert, dass irgendwelche Subklassen der derzeitigen Klasse diese Methode überschreiben. Methoden, die auf keinen Fall geändert werden sollen, sollten deshalb immer als final deklariert werden. Native Methoden Native Methoden werden mit dem Modifier native gekennzeichnet. Beispiel: native void toggleStatus(); Native Methoden sind Methoden, die nicht in Java geschrieben sind, aber dennoch innerhalb von Java verwendet werden sollen. Meist handelt es sich um Methoden in C/C++. Die Syntax zum Verwenden von nativen Methoden ist ähnlich der abstrakter Methoden. Der Modifier native wird vor der Methode deklariert, und der Body (Körper) der Methode wird durch ein Semikolon ersetzt. Es darf nicht vergessen werden, dass die Deklaration den Compiler über die Eigenschaften der Methode informiert. Deshalb ist es von höchster Wichtigkeit, dass Sie denselben Rückgabewert und dieselbe Parameterliste verwenden, wie sie auch im ursprünglichen Code vorhanden waren. Synchronisierte Methoden Im Rahmen von Multithreading ist es möglich, Methoden zu synchronisieren. Dies erfolgt mit dem vorangestellten Modifier synchronized. Beispiel: synchronized void toggleStatus(); Mehr dazu finden Sie Seite 413.
6.7.4
Die Rückgabewerte von Methoden
Die Rückgabewerte von Methoden sind wichtige Informationen einer Methode, ob sie korrekt abgearbeitet wurde oder was sie genau ausgeführt hat. Rückgabewerte von Java-Methoden können von jedem erlaubten Datentyp sein, nicht nur primitive Datentypen, sondern auch komplexe Objekte (beispielsweise Zeichenketten oder Arrays).
308
Java 2 Kompendium
Methoden
Kapitel 6
Eine Methode muss immer einen Wert zurückgeben (und zwar genau den Datentyp, der in der Deklaration angegeben wurde), es sei denn, sie ist mit dem Schlüsselwort void deklariert worden. Das Schlüsselwort void bedeutet gerade, dass sie explizit keinen Rückgabewert hat. Rückgabewerte werden mit der Anweisung return() zurückgegeben. Innerhalb der Klammer steht der gewünschte Rückgabewert. Bei als void deklarierten Methoden kann die return-Anweisung auch ohne Klammern notiert werden. Das ist etwa dann sinnvoll, wenn ein Rücksprung aus einer solchen Methode mit bestimmten Bedingungen gekoppelt wird. /* Beispielprogramm zur Demonstration der Rückgabewerte mit return*/ import java.util.*; class Rueckgabe { public static int zurueck1() { return(42); } public static void zurueck2() { do { Random a = new Random(); System.out.println(a.nextFloat()); if(a.nextFloat()>0.5) return; } while(true); } public static String zurueck3() { return("Und tschuess"); } public static void main (String args[]) { int i=zurueck1(); System.out.println(i); zurueck2(); System.out.println(zurueck3()); } }
Listing 6.49: Verwendung von Rückgabewerten bzw. return
Die Ausgabe ist der Wert 42, der Rückgabewert der Methode zurueck1(). Der Rückgabewert (der Datentyp int) wird einer lokalen Variablen zugewiesen und diese dann ausgegeben. Dann wird so lange die Schleife in der Methode zurueck2() wiederholt, bis die Bedingung für die Auslösung von return (ohne Parameter) erfüllt ist. Abschließend wird der von der Methode zurueck3() zurückgegebene Wert (ein String) direkt in der Ausgabemethode verwendet.
6.7.5
Der Methodenname
Bezüglich der Methodennamen gelten die gleichen Regeln wie bei allen Token. Java 2 Kompendium
309
Kapitel 6
Java – die Hauptbestandteile der Sprache Zwingende Namenskonventionen für Methoden 1.
Methodennamen dürfen im Prinzip eine unbeschränkte Länge haben (bis auf technische Einschränkungen durch das Computersystem).
2.
Genau wie bei allen anderen Bezeichnern in Java sind die einzigen Bedingungen für Methodennamen, dass diese mit einem Buchstaben oder einem der beiden Zeichen _ oder $ beginnen müssen. Es dürfen weiter nur Unicode-Zeichen über dem Hexadezimalwert 00C0 (Grundbuchstaben und Zahlen sowie einige andere Sonderzeichen) verwendet werden.
3.
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 Tokens identisch ist, d.h. den vollkommen identischen Unicode-Wert hat. Java unterscheidet deshalb Groß- und Kleinschreibung. Die beiden Methodennamen main() und Main() sind nicht identisch.
4.
Methodennamen dürfen keinem Java-Schlüsselwort gleichen und sie sollten nicht mit Namen von Java-Paketen identisch sein.
Es gibt neben den zwingenden Regeln für Methodennamen ein paar unverbindliche, allerdings gängige Konventionen. Übliche Konventionen für Methodennamen 1.
Man sollte möglichst sprechende Methodennamen verwenden.
2.
Die Methodennamen sollten mit einem Kleinbuchstaben beginnen und anschließend klein geschrieben werden. Wenn sich ein Bezeichner aus mehreren Worten zusammensetzt, dürfen diese nicht getrennt werden. Die jeweiligen Anfangsbuchstaben werden jedoch innerhalb des Gesamtbezeichners jeweils groß geschrieben.
6.7.6
Die Parameterliste
Eine Parameterliste ist eine Reihe von Informationen, die als Parameter in Klammern eingeschlossen an die Methode weitergegeben werden. Jeder Parameter besteht aus der Angabe des Datentyps und des Variablennamens, wie er innerhalb der Methode verwendet werden soll. Die Anzahl der Parameter ist beliebig und kann Null sein. Im letzteren Fall lassen Sie die Klammern leer. Wenn mehr als ein Parameter angegeben wird, werden die Parameter durch Kommata getrennt. Beispiele: public static int zurueck() public static void vierParameter( int para1, int para2, float para3, boolean para4)
310
Java 2 Kompendium
Methoden
6.7.7
Kapitel 6
Der Methodenkörper
Im Methodenkörper kann man lokale Variablen deklarieren, andere Methoden aufrufen oder jegliche Form von Anweisungen aufrufen, solange sie keinen syntaktischen Regeln widersprechen. Wenn ein Rückgabewert gefordert wird, muss die return-Anweisung die letzte Anweisung im Quelltext sein. Andernfalls meldet der Compiler eine unerreichbare Anweisung und wird die Methode nicht übersetzen.
6.7.8
Methoden überladen und überschreiben
Hier kommen wir nun zu zwei grundlegenden Java-Techniken – dem Überladen und dem Überschreiben von Methoden. Überladen (Overloading) Java erlaubt, wie wir wissen, ein Überladen von Methoden, jedoch kein Überladen von Operatoren. Das so genannte Methoden-Overloading ist ein Teil der Realisierung des polymorphen Verhaltens von Java. Wir hatten bei den allgemeinen Betrachtungen zu OOP die Technik des Überladens von Methoden schon besprochen, doch noch einmal zur Erinnerung: Überladen kann bedeuten, dass bei Namensgleichheit von Methoden in Superklasse und abgeleiteter Klasse die Methoden der abgeleiteten Klasse die der Superklasse überdecken. Dabei wird aber zwingend vorausgesetzt, dass sich die Parameter signifikant unterscheiden, sonst handelt es sich bei der Konstellation Superklasse-Subklasse um den Vorgang des Überschreibens. Signifikant für die Unterscheidung kann die Anzahl der Parameter sein, aber auch bei gleicher Anzahl von Parametern alleine der Parametertyp. In der gleichen Klasse kann man sowieso nur Methoden mit gleichem Bezeichner deklarieren, wenn diese sich signifikant irgendwo sonst unterscheiden. Das Überladen einer Methode ist hauptsächlich ein allgemeiner Prozess zum Erstellen mehrerer Methoden (selbst in der gleichen Klasse) mit demselben Methodennamen, jedoch mit unterschiedlichen Parametersignaturen und wahrscheinlich auch unterschiedlichen Funktionalitäten im jeweiligen Methodenkörper. Zur Laufzeit wird das Programm dann automatisch bestimmen, welche Version der Methode aufzurufen ist. Die unterschiedlichen Parametersignaturen sind das Merkmal, nach dem die Auswahl erfolgt. Gerade bei den Konstruktoren (auf die wir gleich noch zu sprechen kommen) als speziellen Methoden lässt sich die Technik des Überladens natürlich einsetzen. class SuperKlasse { public static void gebeaus(int i) { System.out.println("Das ist die Superklasse: " + i); } }
Java 2 Kompendium
Listing 6.50: Überladen
311
Kapitel 6
Java – die Hauptbestandteile der Sprache class Ueberlade extends SuperKlasse { public static void gebeaus(int i, int j) { System.out.println( "Das ist die Subklasse mit zwei Parametern: " + (i + j)); } public static void gebeaus(float i) { System.out.println( "Die Subklasse mit einem float-Parameter: " + i); } public static void main (String args[]) { /* Aufruf der Methode mit zwei Parametern in der Subklasse */ gebeaus(2,2); /* Aufruf der Methode in der Superklasse auf Grund des int-Parameters */ gebeaus(1); /* Aufruf der Methode mit einem float-Parameter in der Subklasse */ gebeaus(1.2f); } }
Die Kommentare im Source dokumentieren, wann und warum welche Methode aufgerufen wird. Zuerst wird auf Grund der zwei Parameter die passende Methode der Subklasse aufgerufen, anschließend auf Grund des einen int-Parameters die Methode in der Superklasse. Der dritte Aufruf nimmt die Methode mit einem float-Parameter in der Subklasse. Abbildung 6.18: Die verschiedenen Methodenaufrufe
Die Parametersignatur und die Parameterliste sind zu unterscheiden. Die Parametersignatur einer Methode ist das Merkmal, nach dem die Auswahl beim Überladen erfolgt. Sie gibt nur an, welche Art Datentyp von Parameter an eine Methode weitergereicht wird, sowie die Anzahl der Parameter, jedoch nicht den Namen des Parameters. Die Parameterliste einer Methode dagegen enthält zusätzlich den Namen des Parameters.
312
Java 2 Kompendium
Methoden
Kapitel 6
Der Unterschied zwischen Parameterliste und Parametersignatur ist entscheidend, denn das Deklarieren einer Methode desselben Namens (in der gleichen Klasse oder in Super- und Subklasse), aber mit einer unterschiedlichen Parametersignatur, führt wie gesagt zum Überladen einer Methode, das Deklarieren einer neuen Methode mit der gleichen Parametersignatur, und nur maximal einer unterschiedlichen Parameterliste (das bedeutet nur maximal unterschiedliche Namen der Parameter bei sonst unveränderten Angaben) jedoch nicht. Im Gegenteil, es wird sogar ein Compilerfehler erzeugt, wenn zwei oder mehr Methoden desselben Namens und derselben Parametersignatur in der gleichen Klasse deklariert werden. In unterschiedlichen Klassen können jedoch Methoden mit gleichem Namen und identischer Parameterliste deklariert werden. Auch wenn diese über Vererbung in Verbindung stehen. Dann heißt der Vorgang Überschreiben (Overriding). Overriding Auch die Technik des Überschreibens hatten wir bei den allgemeinen Betrachtungen zu OOP schon besprochen. Der Mechanismus des Überschreibens von Methoden ist dem Überladen ähnlich, sollte jedoch nicht mit ihm verwechselt werden. Überschreiben ermöglicht ebenfalls eine Spezialisierung von Objekten. Wir hatten schon angesprochen, dass das Erstellen zweier Methoden des gleichen Namens und der identischen Parametersignatur innerhalb derselben Klasse nicht gestattet ist, aber um eine Klasse zu erweitern, kann man sie in einer Subklasse überschreiben. Es bedeutet, dass Methoden einer Subklasse die Methoden der Elternklasse verdecken. Der Unterschied zwischen Überschreiben und Überladen einer Methode in einer Superklasse ist mehr formeller als inhaltlicher Natur. Eine Methode zu überschreiben bedeutet, dass die Methode in der Subklasse den identischen Deklarationskopf (also neben der Namensgleichheit auch vollkommen identische Argumente) wie die Methoden der Superklasse erhält, während bei dem Überladen der Methoden wie gesagt zwar die Namen der Methoden, aber nicht die Argumente identisch sind. class SuperKlasse2 { //Methode der Superklasse public static void gebeaus(int i) { System.out.println(i + 10); } } class Ueberschreibe extends SuperKlasse2 { /* gleicher Methodenname in der Subklasse */ //Identische Methodendeklaration public static void gebeaus(int i) { System.out.println(i – 10); } public static void main (String args[]) {
Java 2 Kompendium
Listing 6.51: Überschreiben
313
Kapitel 6
Java – die Hauptbestandteile der Sprache // Methode der Superklasse wird überschrieben gebeaus(42); } }
Die Ausgabe wird 32 sein, d.h., die Methode der Superklasse wird überschrieben. Das Schlüsselwort super Es gibt diverse Gründe, warum man eine Methode, die bereits in einer Superklasse implementiert war, sowohl in der überschriebenen Variante, als auch gleichzeitig in der Originalversion nutzen möchte. Dazu dient das Schlüsselwort super5. Ein Methodenaufruf wird bei deren Verwendung in der Hierarchie nach oben zur Superklasse weitergereicht. Schauen wir ein Beispiel mit dem Schlüsselwort super an. Listing 6.52: Einsatz von super
class MeineSuperklasse { //Methode der Superklasse void gebeaus(int i) { System.out.println(i + 10); } } class Ueberschreibe2 extends MeineSuperklasse { /* gleicher Methodenname, aber anderer Parameter in der Subklasse */ //Identische Methodendeklaration void gebeaus(int i) { System.out.println(i – 10); //Ausgabe //Zugriff auf die Methode der Superklasse super.gebeaus(42); } public static void main (String args[]) { Ueberschreibe2 u = new Ueberschreibe2(); // Die Methode der Subklasse wird aufgerufen u.gebeaus(42); } }
Die Ausgabe wird 32 und in der nächsten Zeile 52 (das Ergebnis der Superklassen-Methode) sein. Um eine Methode in einer Superklasse aufzurufen, wird also die folgende Syntax verwendet: super.<methodenname>(<Parameter>);
Beachten Sie in dem Beispiel, dass wir erst ein Objekt der Klasse Ueberschreibe2 erzeugen, um auf die Methode gebeaus() in der Klasse Ueberschreibe2 zugreifen zu können. In dieser Methode rufen wir erst die Methode der Superklasse auf. Wenn Sie direkt in der main()-Methode auf 5
314
Wir haben den Einsatz schon bei Konstruktoren gesehen.
Java 2 Kompendium
Methoden
Kapitel 6
die Superklasse referenzieren wollen, erhalten Sie die Meldung "Undefined variable ...". Wie wir beim this-Schlüsselwort noch sehen werden haben, können Sie nur in dem Körper einer nicht-statischen Methode das Schlüsselwort super verwenden. Schauen wir uns super noch in einem anderen Zusammenhang an. Besondere Methoden sind die Konstruktoren, die man gleichfalls in der Superklasse aufrufen kann. Jedoch ist bei Konstruktoren der Methodenname überflüssig, denn der muss sowieso identisch zum Klassenname sein. Deshalb wird bei Konstruktoren die folgende Syntax verwendet: super(<Parameter>);
Wenn der Konstruktor keine Parameter hat, kann man auch einfach super();
notieren. Der Einsatz von super in Konstruktor-Methoden bewirkt den Aufruf der unmittelbaren Superklasse. // die Superklasse class ErsteEbene { // Konstruktor 1 der Superklasse ohne Parameter ErsteEbene() { System.out.println( "Der erste Konstruktor der Superklasse wird aufgerufen."); } /* Konstruktor 2 der Superklasse mit einem intParameter. Zusätzlich erledigt dieser Konstruktor noch einige Aufgaben. In unserem Fall 2 Bildschirmausgaben */ ErsteEbene(int i) { System.out.println( "Der zweite Konstruktor der Superklasse wird aufgerufen."); System.out.println( "Der Wert der Variablen ist "+ i); } } // Die Subklasse der Klasse ErsteEbene class ZweiteEbene extends ErsteEbene { ZweiteEbene() // Der Konstruktor 1 der Subklasse { // Aufruf des Konstruktors der Superklasse ohne // Parameter
Java 2 Kompendium
Listing 6.53: Der Einsatz von super bei Konstruktoren
315
Kapitel 6
Java – die Hauptbestandteile der Sprache super(); System.out.println("Der Konstruktor 1 der Subklasse wird aufgerufen."); } // Der Konstruktor 2 der Subklasse ZweiteEbene(String a) { // Aufruf des Konstruktors der Superklasse mit // Parameter super(5); System.out.println( "Konstruktor 2 der Subklasse wird aufgerufen. Uebergabewert war: " + a); } } public class SuperTest { public static void main (String args[]) { /* Die Anwendung des new-Operators erstellt eine Instanz der Klasse zweiteEbene. Es wird Speicher zugewiesen und – für uns hier wichtig – der Konstruktor der Klasse zweiteEbene aufgerufen. */ new ZweiteEbene(); new ZweiteEbene("Ueber und ueber"); } }
Die Ausgabe sieht wie folgt aus: Der erste Konstruktor der Superklasse wird aufgerufen. Der Konstruktor 1 der Subklasse wird aufgerufen. Der zweite Konstruktor der Superklasse wird aufgerufen. Der Wert der Variablen ist 5. Konstruktor 2 der Subklasse wird aufgerufen. Uebergabewert war: Ueber und ueber Abbildung 6.19: Die Ausgabe beweist den Zugriff auf die Konstruktoren der Superklasse.
316
Java 2 Kompendium
Schnittstellen und das Schlüsselwort implements
Kapitel 6
Der Aufruf des Konstruktors der Superklasse muss die erste Anweisung in Konstruktor der Subklasse sein.
6.8
Schnittstellen und das Schlüsselwort implements
Der Verzicht von Java auf Mehrfachvererbung wird über ein so genanntes Schnittstellenkonzept kompensiert (siehe dazu auch Seite 169). Objekte können eine beliebige Anzahl an Schnittstellen (Interfaces) implementieren. Schnittstellen sind im Prinzip Klassen sehr ähnlich6, jedoch im Gegensatz dazu unterstützen sie Mehrfachvererbung und sind auch sonst flexibler in Bezug auf Vererbung, als dies bei Klassen der Fall ist. Zumindest eine Schnittstelle wird in Ihrer Java-Karriere eine große Rolle spielen: die Schnittstelle Runnable, die für die Multithreading-Technik zentrale Bedeutung hat. Eine Schnittstelle ist in der Java-Sprache eine Sammlung von MethodenNamen ohne konkrete Definition sowie einer Reihe von Konstanten. Obwohl eine einzelne Java-Klasse nur genau eine Superklasse haben kann (und auch genau eine haben muss!), können in einer Klasse mehrere Schnittstellen implementiert werden. Schnittstellen ermöglichen, ähnlich wie abstrakte Klassen, das Erstellen von Pseudoklassen, die ganz aus abstrakten Methoden zusammengesetzt sind. Neben der Eigenschaft als Alternative zu der Mehrfachvererbung werden Schnittstellen (gerade durch die Eigenschaft, keine konkrete Definition zu besitzen) dazu benutzt, eine bestimmte Funktionalität zu definieren, die in mehreren Klassen benutzt werden kann oder wenn die genaue Umsetzung einer Funktionalität noch nicht sicher ist (sie wird erst später in der auf die Schnittstelle aufbauenden Klasse realisiert). Sofern Sie derartige Methoden in einer Schnittstelle unterbringen, können Sie gemeinsame Verhaltensweisen definieren und die spezifische Implementierung dann den Klassen selbst überlassen. Daher liegt die Verantwortung für die Spezifizierung von Methoden dieser Implementierungen, ähnlich wie bei abstrakten Klassen, immer bei den Klassen, die eine Schnittstelle implementieren. Durch die Implementierung von Schnittstellen muss jede nicht-abstrakte Klasse alle in der Schnittstelle deklarierten Methoden überschreiben! Das ist analog der Verwendung von abstrakten Klassen. Auch sonst sind sich abstrakte Klassen und Schnittstellen sehr ähnlich. Wesentliche Unterschiede sind wie gesagt die Implementation in Klassen und die Tatsache, dass eine Schnittstelle keine Methoden mit Körper enthalten kann (was ja in einer abstrakten Klasse nicht verboten ist). 6
Sie werden manchmal sogar als – besondere – Klassen gesehen.
Java 2 Kompendium
317
Kapitel 6
Java – die Hauptbestandteile der Sprache
6.8.1
Erstellung einer Schnittstelle
Die Syntax zur Erstellung einer Schnittstelle und der konkrete Erstellungsprozess ist der/dem von Klassen sehr ähnlich. Es gibt vor allem den wichtigen Unterschied, dass keine Methode in der Schnittstelle einen Körper haben darf und keine Variablen deklariert werden dürfen, die nicht als Konstanten dienen. Die Deklaration einer Schnittstelle erfolgt mit folgender Syntax: [public] interface [extends <SchnittstellenListe>]
Alles in eckigen Klammern Geschriebene ist optional. Öffentliche Schnittstellen Schnittstellen können als Voreinstellung von allen Klassen im selben Paket implementiert werden (freundliche Einstellung). Damit verhalten sie sich wie freundliche Klassen und Methoden. Indem Sie Ihre Schnittstelle explizit als public deklarieren, ermöglichen Sie es – wie auch bei Klassen und Methoden – den Klassen und Objekten außerhalb eines gegebenen Pakets, diese Schnittstelle zu implementieren. Analog public-Klassen müssen öffentlich deklarierte Schnittstellen zwingend in einer Datei namens < NamederSchnittstelle >.java definiert werden. Andere Zugriffsmodifier als public sind bei Schnittstellen nicht erlaubt!
6.8.2
Namensregeln für Schnittstellen
Die Regeln für die Benennung von Schnittstellen sind dieselben wie für Klassen. Es gibt wenige Regeln, die die Vergabe von Namen beschränken. Zwingende Namenskonventionen für Schnittstellen
318
1.
Schnittstellennamen dürfen im Prinzip eine unbeschränkte Länge haben (bis auf technische Einschränkungen durch das Computersystem).
2.
Genau wie bei allen anderen Bezeichnern in Java sind die einzigen Bedingungen für Schnittstellennamen, dass diese mit einem Buchstaben oder einem der beiden Zeichen _ oder $ beginnen müssen. Es dürfen weiter nur Unicode-Zeichen über dem Hexadezimalwert 00C0 (Grundbuchstaben und Zahlen sowie einige andere Sonderzeichen) verwendet werden.
Java 2 Kompendium
Schnittstellen und das Schlüsselwort implements 3.
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 Tokens identisch ist, d.h., den vollkommen identischen Unicode-Wert hat.
4.
Schnittstellennamen dürfen keinem Java-Schlüsselwort gleichen und sie sollten nicht mit Namen von Java-Paketen identisch sein.
Kapitel 6
Wie auch bei Klassen ist es üblich, den ersten Buchstaben jedes Schnittstellennamens groß zu schreiben.
6.8.3
Die Erweiterung anderer Schnittstellen
Java-Schnittstellen können gemä ß dem OOP-Konzept der Vererbung auch andere Schnittstellen erweitern, um somit zuvor geschriebene Schnittstellen weiterentwickeln zu können. Die neue Subschnittstelle erbt dabei auf die gleiche Art und Weise wie bei Klassen die Eigenschaften von der Superschnittstelle. Vererbt werden alle Methoden und statischen Konstanten der Superschnittstelle. Zwar können Schnittstellen andere Schnittstellen erweitern, aber weil diese keine konkreten Methoden definieren dürfen, dürfen auch die Subschnittstellen keine Methoden der Superschnittstellen definieren. Statt dessen ist dies die Aufgabe jeder Klasse, die die abgeleitete Schnittstelle verwendet. Die Klasse muss sowohl die in der Subschnittstelle deklarierten Methoden als auch alle Methoden der Superschnittstellen definieren! Wenn Sie also eine erweiterte Schnittstelle in einer Klasse implementieren, müssen Sie sowohl die Methoden der neuen als auch die der alten Schnittstelle überschreiben. Schnittstellen können Klassen nicht erweitern. Wenn Sie mehr als eine Schnittstelle erweitern wollen, müssen Sie die einzelnen Schnittstellennamen in der Schnittstellenliste der Schnittstellendeklaration durch ein Komma von dem anderen trennen.
6.8.4
Der Körper einer Schnittstelle
Da eine Schnittstelle nur abstrakte Methoden und Konstanten (als final deklarierte Variablen) enthalten darf, können im Körper einer Schnittstelle zwar keine bestimmten Implementierungen spezifiziert werden, aber ihre Eigenschaften können dennoch festgelegt werden. Ein großer Teil der Vorzüge von Schnittstellen resultiert aus der Fähigkeit, Methoden deklarieren zu können.
Java 2 Kompendium
319
Kapitel 6
Java – die Hauptbestandteile der Sprache Methoden in Schnittstellen Die Hauptaufgabe von Schnittstellen ist das Deklarieren von abstrakten Methoden, die in anderen Klassen definiert werden. Bei diesem Prozess müssen ein paar wichtige Dinge beachtet werden. Der einzig signifikante Unterschied in der Syntax der Deklaration einer Methode in einer Schnittstelle und der Syntax für die Deklaration einer Methode in einer Klasse ist der, dass im Gegensatz zu Methoden, die in Klassen deklariert werden, die Methoden in Schnittstellen keinen Körper haben können. Eine Schnittstellen-Methode besteht nur aus einer Deklaration. Eine Deklaration einer Methode legt zwar nicht fest, wie diese sich verhalten wird. Nichtsdestotrotz wird definiert, welche Informationen sie als Übergabeparameter benötigt und welche (falls überhaupt) Informationen als Rückgabewert zurückgegeben werden. Weil die folgenden Implementierungen der Methoden in den Klassen von den Deklarationen der Methoden in den Schnittstellen abhängig sind, ist eine genaue Planung der nötigen Eigenschaften der Methoden – ihrer Rückgabewerte, Parameter und Ausnahmelisten – extrem wichtig. Die konkrete Syntax von Methodendeklarationen in Schnittstellen hat folgende Syntax: [public] ([<Parameter>]) [throws <ExceptionListe>];
Alles in eckigen Klammern Geschriebene ist optional. Die Deklaration einer Methode in Schnittstellen endet direkt mit einem Semikolon (anders als bei normalen Methodendeklarationen in Klassen). Die Verwendung des Schlüsselworts public bei Methodendeklarationen in Schnittstellen ist zwar bei der Deklaration einer Methode möglich, aber da alle Methoden in Schnittstellen defaultmäß ig public sind, kann man diesen Modifier weglassen. Die anderen potenziellen Methodenmodifier (native, static, synchronized, final, private oder protected) dürfen bei der Deklaration einer Methode in einer Schnittstelle nicht verwendet werden. Variablen in Schnittstellen Auch wenn Schnittstellen im Allgemeinen zur abstrakten Implementierung von Methoden verwendet werden, so können sie dennoch zusätzlich einen bestimmten Typ von Variablen enthalten. Da Schnittstellenmethoden keinen Code im Körper enthalten können, müssen alle Variablen, die in einer Schnittstelle deklariert werden, außerhalb der Methodenkörper stehen. Es werden also globale Felder für die Klasse sein. Weiterhin sind alle Felder,
320
Java 2 Kompendium
Schnittstellen und das Schlüsselwort implements
Kapitel 6
die in einer Schnittstelle deklariert werden, unabhängig vom Modifier, der bei der Deklaration des Feldes benutzt wurde, immer public, final und static. Sie müssen das nicht explizit in der Felddeklaration angeben, obwohl es der besseren Lesbarkeit halber sinnvoll ist. Felder in Schnittstellen werden wie finale statische Felder in Klassen dazu verwendet, Konstanten zu definieren, die allen Klassen zur Verfügung stehen, die diese Schnittstellen implementieren. Weil alle Felder final sind, müssen sie in der Schnittstelle unbedingt initialisiert werden, wenn sie von der Schnittstelle selbst deklariert werden. Wenn Sie das unterlassen, meldet der Compiler einen Fehler. Ausnahmen in Schnittstellen Auch bei Schnittstellenmethoden gibt es die Möglichkeit, Ausnahmen anzugeben. Im Allgemeinen ist eine Ausnahme eine unerwünschte Erscheinung wie ein Fehler. Deshalb ist es sinnvoll, in jedes Programm Code zur Fehlerbehandlung einzubauen, um solche Fehler abzufangen. Solche unerwünschten und undefinierten Vorkommnisse müssen behandelt werden. Java besitzt besondere Konstrukte, um mit solchen Problemen umgehen zu können. Ausnahmen in Java sind Objekte, die erzeugt werden, wenn eine Ausnahmebedingung vorliegt. Diese Objekte werden dann von der Methode an das aufrufende Objekt zurückgegeben und müssen von diesem geeignet behandelt werden. Diese Erzeugung von Ausnahmen wird mit der Anweisung throw ausgeführt. Das Auffangen von Ausnahmen (catch) wird im Allgemeinen dadurch erreicht, dass man Anweisungen, die Ausnahmen erzeugen könnten, im Inneren eines try-catch-Block schreibt. Um nun eine Ausnahme erzeugen zu können, muss der Ausnahmetyp (oder einer seiner Superklassen) in der Ausnahmenliste für die Methode enthalten sein. Bei Schnittstellenmethoden sind Ausnahmen allerdings von geringer Bedeutung, denn es handelt sich nur um abstrakte Methoden. Alleine für das Überschreiben von Schnittstellenmethoden sind die Ausnahmen von Bedeutung. Dies sind die Regeln für das Überschreiben von Schnittstellenmethoden, die Ausnahmen erzeugen: Die neue Ausnahmenliste in der Implementierung darf nur Ausnahmen enthalten, die auch in der ursprünglichen Ausnahmenliste oder in deren Subklassen vorhanden sind. Die neue Ausnahmenliste muss nicht unbedingt Ausnahmen enthalten, egal wie viele in der ursprünglichen Liste vorhanden sind. Der Grund ist, dass die alte Liste der neuen Methode durch Vererbung zugewiesen wird.
Java 2 Kompendium
321
Kapitel 6
Java – die Hauptbestandteile der Sprache Die überschriebene Methode kann jede in der ursprünglichen Ausnahmenliste enthaltenen oder aus dieser Liste abgeleiteten Ausnahmen, unabhängig von der eigenen Ausnahmenliste auswerfen. Im Allgemeinen bestimmt die Ausnahmenliste der in der Schnittstelle deklarierten Methode, und nicht die der redeklarierten Methode, welche Ausnahmen ausgeworfen werden können und welche nicht.
6.8.5
Überschreiben und Verwenden von Schnittstellenmethoden
Eine Schnittstellenmethoden kann nicht verwendet werden, bis sie nicht in der Klasse überschrieben worden ist, die diese Methode implementiert. Das ist zwangsläufig, da eine Schnittstellenmethode ja keinerlei Körper haben darf. Eine Funktion gibt es aber selbstverständlich dennoch. Eine Methodendeklaration in einer Schnittstelle legt das Verhalten einer Methode insoweit fest, als dass der Methodenname, der Rückgabetyp und die Parametersignatur definiert werden. Die Implementierung von Schnittstellen in Klassen erfolgt über die implements-Anweisung, wie wir bei der Behandlung der Klassendeklaration gesehen haben. Wenn eine solche Schnittstellenmethode in einer Klasse überschrieben wird, dann gibt es mehrere Aspekte, die geändert werden können. Genau genommen ist das Einzige, was sich an der Methode nie verändern darf, der Methodenname. Aber auch andere Faktoren unterliegen bezüglich einer Veränderung strengen Regeln:
322
1.
Wie wir schon gesehen haben, sind alle in einer Schnittstelle deklarierten Methoden als Grundeinstellung mit dem Zugriffslevel public ausgestattet. Eine solche Methode kann nicht so überschrieben werden, dass der Zugriff auf sie noch weiter beschränkt wird. Deshalb müssen alle in einer Schnittstelle deklarierten und in einer Klasse überschriebenen Methoden mit dem Zugriffsmodifier public versehen werden. Von den übrigen Modifiern, die auf Methoden angewendet werden können, dürfen nur native und abstract auf solche Methoden angewendet werden, die ursprünglich in einer Schnittstelle deklariert wurden.
2.
Schnittstellenmethoden können eine Parameterliste definieren, die an die Methode weitergegeben werden müssen. Wenn in der Klasse eine neue Methode mit dem gleichen Namen, jedoch einer anderen Parameterliste deklarieren, wird wie allgemein üblich die in der Schnittstelle deklarierte Methode überladen und nicht überschrieben. Dies ist zwar nicht falsch, aber dann muss noch zusätzlich die Methode überschrieben werden, denn wir hatten ja schon festgehalten, dass jede Methode in einer Schnittstelle überschrieben werden muss, außer Sie erklären Sie für abstrakt, indem Sie dieselbe Parametersignatur wie in Ihrer SchnittJava 2 Kompendium
Schnittstellen und das Schlüsselwort implements
Kapitel 6
stelle verwenden. Das bedeutet, dass sich für eine Redefinition zwar die Namen von Variablen ändern dürfen, nicht aber deren Anordnung und Typ. Da Körper von Schnittstellenmethoden leer sind, muss als wesentliche Aufgabe bei der Implementierung einer solchen Methode in eine Klasse ein Körper für die ursprünglich in der Schnittstelle deklarierten Methoden erstellt werden. Und zwar wie bereits mehrfach erwähnt für jede ursprünglich in Ihrer Schnittstelle deklarierte Methode, außer die Methode soll native oder die neue Klasse abstrakt sein. Wie Sie konkret eine Schnittstelle nur überschreiben, hängt von der Aufgabe ab, die die jeweilige Methode erfüllen soll. Eine Schnittstelle stellt zwar sicher, dass Methoden in einer nicht-abstrakten Klasse definiert und entsprechende Datentypen zurückgegeben werden, ansonsten werden von ihr aber keine weiteren Restriktionen oder Begrenzungen für die Körper der Methoden in den Klassen festgelegt. Allerdings gibt es dennoch einige potenzielle Fallen. Zwar ist immer sichergestellt, dass eine nicht-abstrakte Klasse, die eine Schnittstelle implementiert, jede in dieser Schnittstelle deklarierte Methode enthält. Allerdings ist damit noch lange nicht gewährleistet, dass diese Methoden von ihrer Funktionalität her auch richtig implementiert werden. Die Erstellung eines Methodenkörpers, der nur aus geöffneten und geschlossenen geschweiften Klammern besteht, reicht beispielsweise aus, um die Bedingung einer Methode zu erfüllen, deren Rückgabetyp void ist. Ansonsten langt eine return-Anweisung, die eine geforderten Datentyp zurückliefert. Damit tricksen Sie den Compiler insofern aus, als dass Sie die Bedingungen für die Implementierung einer Schnittstelle erfüllt haben. Das kann indes zu vielerlei Problemen führen. Mit solchen Methoden ohne sinnvolle Funktion lassen sich Operationen mit Klassen durchführen, die dann jedoch unbefriedigende Resultate erzielen. Es gibt mehrere Verfahren, mit denen Klassen, die Schnittstellen implementieren, von anderen Klassen benutzt werden können. Alle Anwendungen hängen von einer ganz bestimmten Bedingung ab: Es muss sichergestellt werden, dass die in der Schnittstelle deklarierten Felder und Methoden in der gegebenen Klasse definiert werden.
6.8.6
Verwenden von Feldern einer Schnittstelle
Die Felder einer Schnittstelle müssen sowohl statisch als auch final sein. Der Zugriff auf ein Feld einer Schnittstelle erfolgt entweder direkt oder über die Benutzung der standardisierten Punktschreibweise: .
Java 2 Kompendium
323
Kapitel 6
Java – die Hauptbestandteile der Sprache
6.8.7
Beispiele mit Schnittstellen
Schnittstellen sind zwar nicht schwer zu verstehen, aber das Thema war bisher ziemlich abstrakt. Wir werden also ein paar Beispiele erstellen, die Schnittstellen erzeugen und diese dann in einer Klasse verwenden. Gehen wir die Geschichte erst einmal ganz einfach an – mit einer Schnittstelle, wo nur zwei Konstanten deklariert werden, die dann in einer Klasse ausgegeben werden. Listing 6.54: Eine Schnittstelle mit Konstanten
interface MeineSchnittstelle { int zaehler = 42; String str = "Test in der Schnittstelle."; } public class InterfaceTest implements MeineSchnittstelle { public static void main (String args[]) { System.out.println(zaehler); //Ausgabe System.out.println(str); //Ausgabe } }
Die Ausgabe wird der Wert der beiden in der Schnittstelle definierten Konstanten sein. Beachten Sie, dass wir die optionalen Angaben bei der Schnittstellendeklaration weggelassen haben. Die Eigenschaften der Schnittstellen legen automatisch (wie oben beschrieben) zahlreiche Randbedingungen fest. Testen Sie das Beispiel einmal, indem Sie die Wertzuweisung der Variablen in der Schnittstelle weglassen. Sie werden folgende Fehlermeldung erhalten: InterfaceTest.java:3: = expected int zaehler; ^ InterfaceTest.java:4: = expected String str; ^
Jedes Feld einer Schnittstelle muss zwingend eine Wertzuweisung dort bekommen. Es ist ja als Konstante zu verstehen. Erweitern wir nun das Beispiel. Dazu erzeugen wir eine weitere Schnittstelle und implementieren auch diese in einer Klasse. Dieses Mal jedoch nicht in der public-Klasse, sondern in einer neuen Klasse. Aus dieser erzeugen wir dann in der main()-Methode der öffentlichen Klasse ein Objekt und greifen über dieses auf die Methode zu, die die zweite Schnittstelle als Methodendeklaration enthält und zwingend in der implementierenden Klasse überschrieben werden muss.
324
Java 2 Kompendium
Variablen
Kapitel 6
interface MeineSchnittstelle { int zaehler = 42; String str = "Test in der Schnittstelle."; } interface MeineZweiteSchnittstelle { public void meineMethode(); } class MethodenAufruf implements MeineSchnittstelle, MeineZweiteSchnittstelle { public void meineMethode() { System.out.println(zaehler); //Ausgabe System.out.println(str); //Ausgabe } } public class InterfaceTest2 { public static void main (String args[]) { MethodenAufruf u = new MethodenAufruf(); u.meineMethode(); } }
Die Ausgabe wird identisch mit dem ersten Beispiel sein. Testen Sie den Fall, dass Sie die Methode einer Schnittstelle in der implementierenden Klasse nicht überschreiben. Sie erhalten eine Fehlermeldung der folgenden Art: InterfaceTest2.java:11: class InterfaceTest2 must be declared abstract. It does not define void meineMethode() from interface MeineZweiteSchnittstelle. public class InterfaceTest2 implements MeineSchnittstelle, MeineZweiteSchnittstelle
6.9
Variablen
Wir haben den Begriff Variable bisher schon sehr intensiv benutzt. Jeder, der mit irgendwelchen Programmiersprachen Erfahrung hat, kann sich darunter etwas vorstellen. Variablen sind Stellen im Hauptspeicher, in denen irgendwelche Werte gespeichert werden können. Dazu haben sie einen Namen, einen Datentyp und eben einen Wert. Wir kennen schon alle JavaDatentypen, die Namensregeln für Variablen und wissen bereits, dass vor einer Verwendung einer Variablen sie deklariert werden muss. Außerdem ist uns bekannt, dass Java keine Konstanten im üblichen Sinn kennt und diese über das Schlüsselwort final bei Variablen realisiert. Soweit wahrscheinlich nichts Neues. Die nachfolgende Aussage ist jedoch schon etwas interessanter.
Java 2 Kompendium
325
Kapitel 6
Java – die Hauptbestandteile der Sprache Java kennt nur drei unterschiedliche Arten von Variablen: 1.
Instanzvariablen
2.
Klassenvariablen
3.
Lokale Variablen
Doch wie unterscheiden sich diese drei Arten von Variablen im Detail?
6.9.1
Instanzvariablen
Instanzvariablen werden zum Definieren von Attributen oder eines Zustandes eines bestimmten Objekts benutzt. Dazu werden Instanzvariablen innerhalb der Klasse definiert. Man findet auch gelegentlich den Begriff Objektvariable statt Instanzvariable, der dasselbe bezeichnet. Grundsätzlich stehen Instanzvariablen nur über ein Objekt zur Verfügung (analog der Situation von Instanzmethoden). Um auf eine Instanzvariable im allgemeinen Fall zuzugreifen, verwenden Sie die Punktnotation. Dabei steht auf der linken Seite des Punktes das Objekt (die Instanz), zu der die Variable gehört, und auf der rechten Seite der Name der Variablen. Beispiele: MeinObjekt.meineVariable; DeinObjekt.dortigeInstanzVariable;
Auf beiden Seiten des Punktes steht ein Ausdruck (im Java-Sinn). Damit können Instanzvariablen auch verschachtelt werden. Wenn die Instanzvariable selbst ein Objekt beinhaltet und dieses Objekt wiederum eine eigene Instanzvariable, so kann auf darauf über die Punktnotation Bezug genommen werden. Die Punktnotation wird dabei von links nach rechts bewertet. Beispiel: MeinObj.meineVar.enthalteneInstanzVariable; Zuerst wird im dem Beispiel die Variable meineVar von dem Objekt MeinObj bewertet. Diese zeigt auf ein anderes Objekt mit der Variable enthalteneInstanzVariable. Deren Wert wird dann zurückgegeben.
6.9.2
Klassenvariablen
Klassenvariablen sind mit Instanzvariablen vergleichbar, außer der extrem wichtigen Tatsache, dass ihre Werte auf sämtliche Instanzen der jeweiligen Klasse und die Klasse selbst zutreffen. Sie stehen also in der Klasse selbst zur Verfügung, aber auch jede Instanz einer Klasse bekommt eine Referenz auf die Klassenvariable (ein Zeiger auf den gleichen Speicherbereich) vererbt. 326
Java 2 Kompendium
Variablen
Kapitel 6
Egal wie die Variable angesprochen wird (in der Klasse, über die Klasse oder über eine beliebige Instanz) – es ist immer der gleiche Speicherbereich. Klassenvariablen fungieren als eine Art globale Variablen, die der Klasse selbst und all ihren Instanzen zur Verfügung stehen. Klassenvariablen werden wie eine Instanzvariable innerhalb der Klasse definiert. Zusätzlich muss nur das Schlüsselwort static vorangestellt werden. Dies ist identisch zu dem Vorgang, mit dem Instanzmethoden zu Klassenmethoden geadelt werden. Der Zugriff auf Klassenvariablen erfolgt wieder mit der Punktnotation. Dabei steht bei einem Aufruf von außen vor dem Punkt der Name der Klasse und nicht der einer Instanz. Bei einem Zugriff aus der Klasse heraus kann man die Klassenvariable direkt oder eine beliebige Instanz der Klasse ansprechen.
6.9.3
Klassenvariablen versus Instanzvariablen
Versuchen wir noch einmal, die Unterschiede zwischen Instanz- und Klassenvariablen exakt herauszukristallisieren. Wie bei Klassenmethoden gilt, dass Klassenvariablen außerhalb der konkreten Objekte liegen, aber innerhalb der Klassen. Sie werden über das Metaklassenkonzept in die streng objektorientierte Theorie von Java eingebunden. Klassenvariablen können deshalb an beliebigen Stellen genutzt werden, unabhängig davon, ob eine konkrete Instanz der Klasse existiert oder nicht. Eine Instanzvariable ist dagegen nur über ein Objekt verwendbar. Zwar können Instanzmethoden einer Klasse eine Instanzvariable aufrufen, aber das verlagert das Problem wie bei der Situation mit den Instanzmethoden nur weiter. Die konkrete Anwendung erfolgt über ein Objekt. Das nachfolgenden Beispiel demonstriert die Unterschiede nochmals detailiert. class Variable1 { int a = 5; static int b = 6; public static void main(String args[]) { // Erzeugung von zwei Instanzen von Variable1 Variable1 u = new Variable1(); Variable1 v = new Variable1(); System.out.println( "Die Instanzvariable a ueber das Objekt u: " + u.a); System.out.println( "Die Instanzvariable a ueber das Objekt v: " + v.a); // Über u wird dortige Instanzvariable geändert u.a = 111;
Java 2 Kompendium
Listing 6.55: Unterschiede zwischen Instanzund Klassenvariablen
327
Kapitel 6
Java – die Hauptbestandteile der Sprache System.out.println( "Instanzvariable a ueber Objekt u nach Aenderung von u.a: " + u.a); System.out.println( "Instanzvariable a ueber Objekt v nach Aenderung von u.a: " + v.a); // Zugriff auf Klassenvariable System.out.println( "Direkter Zugriff auf Klassenvariable b: " + b); System.out.println( "Zugriff auf Klassenvariable b ueber Klassenname: " + Variable1.b); System.out.println( "Zugriff auf Klassenvariable b ueber Objekt u: " + u.b); System.out.println( "Zugriff auf Klassenvariable b ueber Objekt v: " + v.b); // Direkte Aenderung der Klassenvariable b = 765; System.out.println( "Direkter Zugriff auf Klassenvariable b: " + b); System.out.println( "Zugriff auf Klassenvariable b ueber Klassenname: " + Variable1.b); System.out.println( "Zugriff auf Klassenvariable b ueber Objekt u: " + u.b); System.out.println( "Zugriff auf Klassenvariable b ueber Objekt v: " + v.b); // Aenderung der Klassenvariable ueber Objekt v b = 1; System.out.println( "Direkter Zugriff auf Klassenvariable b: " + b); System.out.println( "Zugriff auf Klassenvariable b ueber Klassenname: " + Variable1.b); System.out.println( "Zugriff auf Klassenvariable b ueber Objekt u: " + u.b); System.out.println( "Zugriff auf Klassenvariable b ueber Objekt v: " + v.b); } }
Wie schon bei den Methoden betont – Klassenmethoden können nicht direkt auf Instanzvariablen zugreifen. Wie auch? Diese sind ja an die Existenz eines zugeordneten Objekts gebunden.
328
Java 2 Kompendium
Variablen
Kapitel 6
Die Ausgabe wird folgende sein: Die Instanzvariable a ueber das Objekt u: 5 Die Instanzvariable a ueber das Objekt v: 5 Instanzvariable a ueber Objekt u nach Aenderung von u.a: 111 Instanzvariable a ueber Objekt v nach Aenderung von u.a: 5 Direkter Zugriff auf Klassenvariable b: 6 Zugriff auf Klassenvariable b ueber Klassenname: 6 Zugriff auf Klassenvariable b ueber Objekt u: 6 Zugriff auf Klassenvariable b ueber Objekt v: 6 Direkter Zugriff auf Klassenvariable b: 765 Zugriff auf Klassenvariable b ueber Klassenname: 765 Zugriff auf Klassenvariable b ueber Objekt u: 765 Zugriff auf Klassenvariable b ueber Objekt v: 765 Direkter Zugriff auf Klassenvariable b: 1 Zugriff auf Klassenvariable b ueber Klassenname: 1 Zugriff auf Klassenvariable b ueber Objekt u: 1 Zugriff auf Klassenvariable b ueber Objekt v: 1
Das Beispiel demonstriert (und dokumentiert im Source die entscheidenden Stellen), dass eine Änderung einer Klassenvariablen (egal, wie und wo sie erfolgt), sich in der Klasse und allen Instanzen auswirkt, während die Änderung einer Instanzvariablen für die Schwesterinstanzen der gleichen Klasse verborgen bleibt.
6.9.4
Lokale Variablen
Java kennt selbstverständlich lokale Variablen (wir haben sie schon vielfach verwendet). Lokale Variablen werden innerhalb von Methodendefinitionen deklariert und können auch nur dort benutzt werden. Dies können Indexvariablen von Schleifen (so genannte schleifenlokale Variablen) sein oder temporäre Variablen zur Aufnahme von Werten, die nur innerhalb der Methodendefinitionen Sinn machen. Es gibt gleichfalls auf Blöcke beschränkte lokale Variablen. Lokale Variablen existieren nur solange im Speicher, wie die Methode, die Schleife oder der Block existiert. Wesentlicher Unterschied von lokalen Variablen zu Instanz- und Klassenvariablen ist, dass sie unbedingt einen Wert zugewiesen bekommen müssen, bevor sie benutzt werden können. Instanz- und Klassenvariablen haben einen typspezifischen Defaultwert. Es gibt einen Compilerfehler, wenn lokale Variablen nicht vor der ersten Verwendung initialisiert wurden. Ein weiterer Unterschied von lokalen Variablen zu Instanz- und Klassenvariablen ist, dass man auf sie immer direkt und nicht über die Punktnotation zugreifen kann. Der Bereich Konstanten ist in Java sowieso eine Beson-
Java 2 Kompendium
329
Kapitel 6
Java – die Hauptbestandteile der Sprache derheit. Im Fall von lokalen Variablen beinhaltet dies, dass sie nicht als Konstanten gesetzt werden können. Das bedeutet, das Schlüsselwort final ist bei lokalen Variablen nicht erlaubt.
6.9.5
Die Java-Konstanten
Konstanten sind Speicherbereiche mit Werten, die sich nie ändern. Wie gerade angedeutet, können in Java solche Konstanten ausschließlich für Instanz- und Klassenvariablen, aber nie für lokale Variablen erstellt werden. Um eine Konstante in Java zu deklarieren, benutzen Sie nur das Schlüsselwort final und stellen es einer Variablendeklaration voran. Zusätzlich müssen Sie dieser Variablen einen Anfangswert zuweisen (der sich dann auch nie mehr ändert – sonst hätten wir ja keine Konstante). In der Java-API wird ein große Anzahl von Konstanten zur Verfügung gestellt, mit deren Hilfe Sie einfacher und übersichtlicher programmieren können. Variablendeklarationen in Schnittstellen bedeuten immer die Erstellung von Konstanten, unabhängig davon, ob final vorangestellt wird oder nicht.
6.10
Der Zugriff auf Java-Elemente
Wir kommen nun zu einem Abschnitt, in dem geklärt werden soll, in welchen Situationen wie auf Java-Elemente (Variablen, Methoden usw.) zugegriffen werden kann. Sie also irgendwie anzusprechen. Dabei muss man verschiedene Situationen unterscheiden. Klassenelemente innerhalb der jeweiligen eigenen Klasse kann man direkt ansprechen, indem man einfach den Bezeichner dafür angibt. Der Aufruf einer Methode oder einer Variablen wird immer soweit unten in einer Hierarchie wie möglich erfolgen. Wird ein Aufruf in einer tiefen Ebene nicht gefunden, wird die nächsthöhere Ebene durchsucht und so fort. Noch nicht in Detail geklärt (obwohl schon oft verwendet) ist die Frage, wie Klassenelemente aus anderen Klassen benutzt werden können. Das erfolgt einfach, indem per Punktnotation der fremde Klassenname vorangestellt wird (gegebenenfalls über ihren vollqualifizierten Namen in einer Paketstruktur -dazu gleich mehr) und dann das darin enthaltene Klassenelement, das man ansprechen möchte, etwa das Element out in der Klasse System. Kommt Ihnen das bekannt vor? Wir nutzen das Verfahren bei System.out.println();.
330
Java 2 Kompendium
Pakete und die import-Anweisung
Kapitel 6
Bezüglich der Zugänglichkeit der Klassen muss allerdings noch beachtet werden, dass das Paket, in dem sie enthalten sind, verfügbar ist. Was das genau bedeutet, besprechen wir im nächsten Abschnitt. Auch bei fremden Klassen müssen Sie die Regel für den Zugriff auf Objektelemente natürlich einhalten. Sie erzeugen aus der anderen Klasse ein Objekt und sprechen gewünschte Instanzvariablen und Instanzmethoden über die Punktnotation an. Dabei steht auf der linken Seite des Punktes das Objekt (die Instanz), zu dem die Felder oder Methoden gehören, und auf der rechten Seite der Name der Felder oder Methoden. Beispiele: MeinObjekt.meineVariable; DeinObjekt.methode(arg1,arg2);
Auf beiden Seiten des Punktes steht ein Ausdruck (im Java-Sinn). Damit kann die Punktnotation ebenfalls verschachtelt werden. Wenn etwa die aufgerufene Methode selbst ein Objekt beinhaltet und diese Objekt wiederum eine eigene Instanzvariable, so kann auf darauf über die Punktnotation Bezug genommen werden. Die Punktnotation wird dabei von links nach rechts bewertet.
6.11
Pakete und die import-Anweisung
Pakete (Packages) sind in Java Gruppierungen von Klassen und Schnittstellen, eine spezielle Art des Designs und der Organisation von Java im Großen. Sie sind das Java-Analogon zu Bibliotheken vieler anderer Computersprachen. Die Java-Laufzeitbibliothek, das Java-API, wird in Form von Paketen zur Verfügung stellt. Ein Paket von Java-Klassen enthält normalerweise logisch zusammenhängende Klassen und Schnittstellen.
6.11.1
Die Verwendung von Paketen
Jede Klasse in Java ist Bestandteil eines Pakets. Der vollqualifizierte Name einer Klasse besteht immer aus dem Namen des Pakets, gefolgt von einem Punkt, eventuellen Unterpaketen, die wieder durch Punkte getrennt werden, bis hin zum eigentlichen Klassennamen. Um nun eine Klasse verwenden zu können, muss dem Compiler gesagt werden, in welchem Paket er sie suchen soll. Das kann einmal mit dem vollqualifiziertn Namen erfolgen, zum Beispiel: java.awt.Frame; Das bedeutet, dass der Compiler in dem Paket java nach dem Unterpaket awt und dort die Klasse Frame sucht.
Java 2 Kompendium
331
Kapitel 6
Java – die Hauptbestandteile der Sprache Ein Paket stellt eine Abbildung eines Unterverzeichnisses dar, wo nach der gewünschten Klasse vom Compiler gesucht werden soll. Wird eine Klasse also in der genannten Paketstruktur gesucht, sucht der Compiler in der Verzeichnisstruktur java/awt/Frame. Dabei stellt sich natürlich die Frage, in welchem Verzeichnis der Compiler die Suche beginnt. Zuerst einmal wird das aktuelle Verzeichnis nach solchen Unterverzeichnissen durchsucht. Findet der Compiler dort nichts, sucht er entsprechend dem voreingestellten Suchpfad des Systems. Früher war dafür die Umgebungsvariable CLASSPATH verantwortlich, aber seit dem JDK 1.2 wird diese nicht mehr herangezogen7. Nach der Installation des JDK »wissen« alle Tools implizit, von welchem Verzeichnis aus sie nach Klassen suchen sollen. Das ergibt sich aus dem JDK-Unterverzeichnis bin. Von da aus werden die Systemklassen von Java (also Packages des Java-API) gesucht. Wenn Sie eigene Packages erstellen und in einem anderen Programm verwenden wollen, müssen die dabei verwendeten Verzeichnisse dem Suchpfad hinzugefügt werden (etwa mit der Option -classpath beim Aufruf des Compilers). Mehr dazu finden Sie bei der Behandlung des Compilers im Kapitel über das JDK. Es gibt nun einige Namenskonventionen für Pakete. Die Standardklassen von Java sind in eine Paketstruktur eingeteilt, die an das Domain-System von Internet-Namen angelehnt ist. Dabei sollte jeder Anbieter von eigenen Java-Paketen diese nach seinem DNS-Namen strukturieren, nur in umgekehrter Reihenfolge. Wenn etwa eine Firma RJS mit der Internetadresse www.rjs.de Pakete bereitstellt, sollte die Paketstruktur de.rjs lauten. Eventuelle Unterpakete sollten darunter angeordnet werden. Einzige (offizielle) Ausnahmen sind die Klassen, die im Standardumfang einer Java-Installation enthalten sind. Diese beginnen mit java oder javax. Das System ist aber nicht zwingend. Nur kann es bei Nichtbeachtung dazu führen, dass es in grö ß eren Projekten zu Namenskonflikten kommt. Es gelten ansonsten nur wieder die üblichen Regeln für Token. Sie werden auf Ihrem Rechner keine explizite Verzeichnisstruktur im JDKVerzeichnis finden, die mit java oder javax beginnt. Das liegt daran, dass die Java-Systemklassen in gepackter Form ausgeliefert werden. Die jarDatei rt.jar beinhaltet aber die gesamte Verzeichnisstruktur.
7
332
Falls sie gesetzt ist, kann das höchstens zu Fehlern führen. Statt dessen gibt es aber die Compiler-Option -classpath, die bei Bedarf gesetzt werden kann.
Java 2 Kompendium
Pakete und die import-Anweisung
Kapitel 6
Wenn Sie nun aus der angegebenen Klasse eine Methode oder Variable benötigen, müssen Sie diese Pfadangabe um den Methoden- bzw. Variablennamen erweitern. Und zwar für jedes Element, das Sie aus der Klasse benötigen. Damit wird die Lesbarkeit des Quelltexts beeinträchtigt und vor allem der Tippaufwand sehr hoch. Dies macht eine einfachere und schnellere Technik notwendig. Um Elemente einer fremden Klasse innerhalb von Klassen wie eine Bibliothek nutzen zu können, kann man sie vorher importieren. Das geschieht durch eine import-Zeile, die vor der Definition irgendeiner Klasse in der Java-Datei stehen muss. Wenn Sie in einem Paket selbst eine andere Klasse importieren wollen, muss die import-Anweisung hinter der package-Anweisung stehen (darauf kommen wir gleich zurück). Wir können also die obige Klasse importieren, indem wir die gesamte Punktnotation (das Paket, zu dem sie gehört, und die Klasse selbst) am Anfang einer Datei mit dem Schlüsselwort import angeben. Danach können wir Komponenten aus der Klasse direkt ansprechen. In unserem Beispiel würde es wie folgt aussehen: import java.awt.Frame; In diesen Fall können Sie später im Quelltext auf die Klasse Frame einfach über Ihren Namen zugreifen. Etwa so: Frame a = new Frame(); Es gibt ein Java-Paket, das Sie nie explizit importieren müssen. Das ist java.lang. Dieses Paket wird immer automatisch importiert. Darin finden Sie so wichtige Klassen wie Object oder String. Die import-Anweisung dient nur dazu, Java-Klassen über einen verkürzten Namen innerhalb der aktuellen Bezugsklasse zugänglich zu machen und damit den Code zu vereinfachen. Sie hat nicht den Sinn (wie die includeAnweisung in C), die Klassen zugänglich zu machen oder Sie einzulesen. Es kann durchaus mehrfache import-Anweisung innerhalb einer Klassendefinition geben (wenn Sie mehrere Klassen importieren wollen). Sie müssen hinter der optionalen Anweisung package (im Fall von Paketen selbst, sonst fehlt die package-Anweisung) stehen. Wenn Sie nun mehrere Klassen aus dem gleichen Paket verwenden wollen, können Sie diese nacheinander importieren. Wenn Sie allerdings mehrere Klassen aus einem Paket benötigen, arbeitet man sinnvollerweise mit Platzhaltern. Mittels einer solchen Wildcard – dem Stern * – kann das ganze Paket auf einmal importiert werden. Das
Java 2 Kompendium
333
Kapitel 6
Java – die Hauptbestandteile der Sprache geschieht wieder durch die import-Zeile, wobei nur der Klassenname durch den Stern ersetzt wird. Danach können Sie alle Klassen aus dem Paket direkt über ihren Namen ansprechen. Das Sternchen importiert keine (!) untergeordneten Pakete. Um also alle Klassen einer komplexen Pakethierarchie zu importieren, müssen Sie explizit auf jeder Hierarchieebene eine import-Anweisung erstellen. Der import-Befehl kennt im Prinzip drei Varianten: import <package>.; import <package>.*; import <package>;
Wir kennen die beiden ersten Formen ja schon. Die erste Form wird immer dann eingesetzt, wenn man gezielt auf eine Klasse zugreifen möchte und nur diese Klasse aus einem Paket benötigt. Der Vorteil ist, dass eine Klasse innerhalb des Source-Codes direkt über ihren Namen angesprochen werden kann. Bei Fall 2 kann eine Klasse innerhalb des Source-Codes ebenfalls direkt über ihren Namen angesprochen werden. Der Vorteil ist das Einbinden aller Klassen aus einem Paket mit einer Anweisung. Die dritte Form dient dazu, mit möglichst wenigen Anweisungen ein Paket oder sogar mehrere Pakete zu importieren. Allerdings kommt jetzt wieder zum tragen, dass keine untergeordneten Pakete importiert werden. Deshalb kann in dem Fall eine Klasse innerhalb des Source-Codes nicht direkt über ihren Namen angesprochen werden. Statt dessen muss vor dem Klassennamen das letzte Element aus dem Package-Namen per Punktnotation gesetzt werden. Dies ist sukzessive fortzusetzen, wenn es mehrere Paketebenen gibt, die nicht im import-Befehl angegeben wurden. In diesem Fall muss unter Umständen eine längere Pfadangabe die Klasse referenzieren. Zwar darf ein Paket viele unterschiedliche Klassen enthalten (egal, ob logisch zusammenhängend oder nicht), jedoch darf jede Datei, die etwas mit dem Aufbau von Paketen zu tun hat, maximal eine öffentliche Klassendefinition enthalten. Da das Importieren von Elementen in Java kein echter Import in dem Sinne ist, dass das resultierende Programm alle angegebenen Klassen irgendwie verwalten muss, sondern nur eine Pfadangabe, kann man beliebig viele Pakete und Klassen importieren, ohne dass das resultierende Programm grö ß er oder sonst ineffektiver wird. Nicht explizit benötigte Klassen werden vom Compiler wegoptimiert. Das nennt man »type import on demand.«
334
Java 2 Kompendium
Pakete und die import-Anweisung
6.11.2
Kapitel 6
Erstellung eines Paketes
Jedem Entwickler bleibt es selbst überlassen, wie er seine Klassen und Schnittstellen zu Paketen zusammenfasst und gruppiert. Eine Java-Datei wird ganz einfach zu einem Paket bzw. einem bestehenden Paket zugeordnet. Sie müssen nur ganz am Anfang der Datei als erste gültige Anweisung (auch vor der ersten Klassendefinition, aber abgesehen von Kommentaren) das Schlüsselwort package und den Namen des Pakets, gefolgt von einem Semikolon, setzen. Anschließend können Sie wie gewohnt Ihre Klassen definieren. Beispiel: package meinErstesPaket; public class meineErsteKlasse { ... }
Wenn sich innerhalb einer Quelldatei mehrere Klassendefinitionen befinden, dann werden alle Klassen dem durch das Schlüsselwort package angegebenen Paket zugeordnet. Grundsätzlich spiegelt die Paketstruktur eine Verzeichnisstruktur wider. Wenn Sie also ein Paket mit dem Namen de schaffen wollen, benötigen Sie ein Unterverzeichnis dieses Namens, worin die zu dem Paket gehörenden Dateien gespeichert werden. Beim Kompilieren werden dann auch die .class-Dateien dort erstellt. Das Verfahren setzt sich mit eventuellen Unterverzeichnissen fort, wenn entsprechende Unterpakete erstellt werden sollen. Spielen wir das Verfahren in einem effektiven Beispiel durch. Erstellen Sie in ihrem Java-Stammverzeichnis ein Verzeichnis de.
Erstellen Sie dort die nachfolgenden Dateien A.java und B.java.
package de; public class A { public int a=42; } Erste Datei in Paket de package de; public class B { public String b="Die Antwort ist "; }
Java 2 Kompendium
Listing 6.56: Zweite Datei in Paket de
335
Kapitel 6
Java – die Hauptbestandteile der Sprache Erstellen Sie im Verzeichnis de das Unterverzeichnis rjs.
Erstellen Sie dort die nachfolgende Datei C.java.
Listing 6.57: Die Datei in Paket de.rjs
package de.rjs; public class C { public void ausgabe(String a, int b) { System.out.println(a + b); } }
Gehen Sie zurück ins Stammverzeichnis.
Erstellen Sie dort die Datei PaketTest.java.
Listing 6.58: Import der selbst erstellten Pakete
import de.*; import de.rjs.*; public class PaketTest { public static void main(String args[]) { A k1 = new A(); B k2 = new B(); C k3 = new C(); k3.ausgabe(k2.b, k1.a); } }
Wenn die Datei PaketTest.java kompiliert wird, wird der Compiler die importierten Dateien automatisch mit übersetzen, wenn diese noch nicht kompiliert sind. Unter Umständen ist dabei notwendig, die individuelle Aufrufoption -classpath mit den Pfadangaben beim Compiler zu verwenden. Die früher notwendige Angabe CLASSPATH ist im JDK 1.2 bzw. 1.3 nicht mehr notwendig und kann höchstens den Compiler abschießen. In den Kapiteln über das JDK und die Java-Neuerungen finden Sie mehr dazu.
6.11.3
Das anonyme Default-Paket
Wenn die package-Anweisung in einer Java-Datei fehlt, wird die Klasse einem voreingestellten Paket ohne Namen (einem so genannten anonymen 336
Java 2 Kompendium
Zusammenfassung
Kapitel 6
Paket) zugeordnet. Die Klassen dieses Pakets können dann direkt von allen Klassen importiert werden, die im gleichen Verzeichnis stehen (und nur von diesen). Hierbei wird dann die import-Anweisung ohne weitere Qualifizierung angegeben oder ganz darauf verzichtet.
6.11.4
Zugriffslevel
Eine Klasse, die von anderen Klassen verwendet werden soll, muss eine der beiden nachfolgenden Bedingungen erfüllen: 1.
Beide Klassen gehören zum selben Paket. Das ist beim anonymen Paket trivialerweise der Fall.
2.
Eine Klasse aus einem fremden Paket, auf die zugegriffen werden soll, muss als public deklariert sein.
6.12
Zusammenfassung
Die Hauptbestandteile der Sprache Java lassen sich in folgende logische Struktur unterteilen: 1.
Token
2.
Typen
3.
Ausdrücke
4.
Anweisungen
5.
Klassen
6.
Schnittstellen
7.
Pakete
Token heißt übersetzt Zeichen oder Merkmal und kann als Sinnzusammenhang verstanden werden. Es gib in Java fünf Arten von Token: 1.
Bezeichner oder Identifier
2.
Schlüsselworte
3.
Literale
4.
Operatoren
5.
Trennzeichen
Daneben gibt es noch Kommentare und Leerräume. Java stellt Token im Unicode-Zeichensatz da.
Java 2 Kompendium
337
Kapitel 6
Java – die Hauptbestandteile der Sprache Ein Datentyp gibt in der Computersprache an, wie ein einfaches Objekt (wie zum Beispiel eine Variable) im Speicher des Computer dargestellt wird. Er enthält normalerweise ebenfalls Hinweise darüber, die Operationen mit und an ihm ausgeführt werden können. Java besitzt acht primitive Datentypen. Ausdrücke drücken einen Wert entweder direkt oder durch Berechnung aus. Es kann sich auch um Kontrollfluss-Ausdrücke handeln, die den Ablauf der Programmausführungen festlegen. Diese Ausdrücke können Konstanten, Variablen, Schlüsselworte, Operatoren und andere Ausdrücke beinhalten. Operatoren sind in diesem Zusammenhang spezielle Symbole (eine der Schlüsselkategorien von Token in Java), die verwendet werden, um die Durchführung von Operationen (Manipulationen) an Variablen oder Werten auszuführen. Anweisungen in Java werden der Reihe nach ausgeführt. Java hat viele verschiedene Arten von Anweisungen. Klassen sind einer der zentralen Begriffe in Java. Da Java absolut objektorientiert ist, können Sie keine prozeduralen Programme schreiben. Man verwendet statt dessen Klassen. Klassen definieren den Zustand und das Verhalten von Objekten. In der objektorientierten Sichtweise steht immer das Objekt im Mittelpunkt. Jedwede Operation ist in der Klasse implementiert, zu der ein Objekt gehört. Bei den Elementen einer Klasse, die als Felder bezeichnet werden, handelt es sich im Wesentlichen um Variablen, auf die die ganze Klasse und bei Bedarf auch andere Klassen zugreifen können. Die Ausführung einer Operation übernehmen die Objektmethoden. Soll eine bestehende Operation um eine neue Funktionalität erweitert werden, so werden die Veränderungen in der Klasse vorgenommen und die zugehörigen Methoden dort geschrieben. Die neue Form der Operation wird einfach als Erweiterung innerhalb der Klasse hinzugefügt. Nach außen erscheint das Objekt unverändert und lässt sich wie gehabt unter einem Namen ansprechen. Weitergehende Änderungen im Programm sind nicht notwendig. Daten und Methoden werden in Klassen gekapselt. Klassen sind eine Art Beschreibung oder Bauplan für konkrete Objekte, die Objekte selbst sind im OO-Sprachgebrauch Instanzen dieser Klassen. Ein großer Teil der Attraktivität von Klassen und OOP basiert auf der Fähigkeit der Vererbung. Dies gibt einem die Möglichkeit, auf Basis alter Klassen (mit geringerem Aufwand als bei vollständiger Neuentwicklung) neue Klassen zu erstellen. Weil diese neuen Klassen die Eigenschaften einer anderen Klasse erben können, werden sie Subklassen, und die Klasse, aus der sie abgeleitet werden, Superklasse genannt.
338
Java 2 Kompendium
Zusammenfassung
Kapitel 6
Jedes Java-Programm besteht aus einer Sammlung von Klassen. Der gesamte Code, der bei Java verwendet wird, wird in Klassen eingeteilt. Jede Klasse definiert das Verhalten eines Objekts durch verschiedene Methoden. Verhalten und Eigenschaften können von der einen Klasse zur nächsten weitervererbt werden. Alle Klassen in Java haben eine gemeinsame Oberklasse, die Klasse Object. Diese wiederum verfügt nur über eine Metaklasse. Methoden sind das Java-Gegenstück zu Funktionen in anderen Programmiersprachen. Sie bilden das Kernstück jeder Klasse und sind für die Handhabung aller Aufgaben zuständig, die von dieser Klasse durchgeführt werden sollen. Schnittstellen sind eine Sammlung von Methodennamen ohne konkrete Definition. Während Klassen Objekte richtig definieren, helfen Schnittstellen bei der Definition von Klassen. Schnittstellen können lediglich ein paar Methoden und Konstanten definieren, die dann von einem Objekt implementiert werden. Schnittstellen können nur abstrakte Methoden und finale Felder definieren, eine Angabe der Implementierung dieser Methoden ist allerdings nicht möglich. Objekte können eine beliebige Anzahl an Schnittstellen oder abstrakten Klassen implementieren. Schnittstellen bilden in Java den Ersatz für die Mehrfachvererbung, die Java nicht unterstützt. Eine Klasse kann nur eine Superklasse, jedoch dafür mehrere Schnittstellen haben. Schnittstellen werden auch dazu benutzt, eine bestimmte Funktionalität zu definieren, die in mehreren Klassen benutzt werden kann oder wenn die genaue Umsetzung einer Funktionalität noch nicht sicher ist. Pakete bilden in Java Gruppierungen von Klassen und Schnittstellen. Sie sind das Java-Analogon zu Bibliotheken vieler anderer Computersprachen. Die Java-Laufzeitbibliothek, das Java-API, wird in Form von Paketen zur Verfügung stellt. Ein Paket von Java-Klassen enthält normalerweise logisch zusammenhängende Klassen und Schnittstellen. Variablen sind Stellen im Hauptspeicher, in denen irgendwelche Werte gespeichert werden können. Dazu haben sie einen Namen, einen Datentyp und eben einen Wert. Java kennt nur drei unterschiedliche Arten von Variablen: 1.
Instanzvariablen
2.
Klassenvariablen
3.
Lokale Variablen
Eine besondere Form von Variable ist in Java diejenige, die das Schlüsselwort final voranstellt. Es ist die Java-Form von einer Konstanten. Java 2 Kompendium
339
7
Grundlagen der AppletErstellung
Bestandteile der Definition für Leben sind Wachstum, Bewegung und Reaktion auf äußere Ereignisse. Seit einiger Zeit scheint mit dieser Definition das World Wide Web anzufangen, eine Art Leben zu entwickeln, lebendig zu werden. Immer mehr neue Entwicklungen hauchen den bisher statischen Hypertext-Seiten Leben in Form von Animation und Bewegung bzw. komfortabler Interaktion mit den Anwendern ein. Das WWW ist die wichtigste Säule des Internets. Erst durch das auf das HTTP-Protokoll aufsetzende WWW (neben der E-Mail-Funktion als die zweite tragende Säule des Internets) ist das Internet wirklich anwenderfreundlich, für die breite Masse interessant und damit richtig populär geworden. Trotz der Vielzahl neuer Sprachentwicklungen ist das WWW nach wie vor im Wesentlichen durch so genannte Hypertexte aufgebaut, die mit der Internetsprache HTML entwickelt wurden (und werden). Ein Hypertext führt durch markierte Wörter (so genannte Hyperlinks) zu weiteren Seiten im WWW oder einem anderen Internetdienst. Hypertext ist also von der Bedeutung her erstmals eigentlich nur ein Text mit Verweisen auf andere Texte. Dieser Verweis kann mit der Maus angeklickt werden und es wird sofort zu dem gewünschten Dokument verzweigt. HTML-Dateien bestehen aus reinem ASCII-Text, das heißt Klartext. Dadurch bleiben HTML-Dokumente plattformunabhängig. Plattformabhängig ist immer nur die Software zum Interpretieren der HTML-Dateien (der Browser). Ein in HTML geschriebenes Dokument kann außer Text ebenso Grafiken sowie multimediale Elemente (Sound, Video usw.) enthalten. Solche Elemente werden als Referenz auf eine entsprechende Grafik- oder Multimedia-Datei notiert. Natürlich muss die Präsentations-Software entsprechende Softwaremodule und die Hardware die zugehörigen Komponenten (beispielsweise eine Soundkarte für akustische Daten) besitzen oder aufrufen, mit deren Hilfe solche Effekte dargestellt werden können. Daneben ist es mit HTML – mit gewissen Einschränkungen – möglich weitergehende Funktionalität wie Datenbankabfragen zu formulieren, die Java 2 Kompendium
341
Kapitel 7
Grundlagen der Applet-Erstellung Resultate optisch aufzubereiten und Menüstrukturen aufzubauen. Das Einbinden des E-Mail-Dienstes in HTML-Seiten ist eine weitere sehr wichtige Eigenschaft und auch Interaktionen mit Anwendern sind zu realisieren. Die grö ß ten Nachteile von HTML sind die unbefriedigende Interaktion mit den Anwendern, fehlende eigenständige Aktion (sprich ein echtes Programm auf Clientseite) und mangelnde echte Animation. Hier kommen nun die Java-Applets ins Spiel, die in HTML-Seiten eingebaut vollkommen neue Möglichkeiten schaffen. Wir müssen uns wohl nicht weiter über die Vorteile und Möglichkeiten von Java-Applets in HTML-Seiten unterhalten – Sie werden schon wissen, warum Sie in Java programmieren wollen (und warum Sie sich das Buch gekauft haben ;-) ). Wenn Sie sich gute Java-Applets als Beispiele zum Lernen oder auch nur zum Anwenden laden wollen, ist Gamelan die Adresse im Internet. Jede Menge Applets gibt es unter http://www.jars.com. Dort finden Sie ebenfalls diverse Informationen zu JavaScript und ActiveX. Auch ist Jars.com die richtige Adresse, wenn Sie bestimmte Java-Tools wie beispielsweise AppletEditoren benötigen.
7.1
Die Vorbereitung
Bevor wir mit der konkreten Programmierung anfangen, wollen wir zunächst einmal für die Applets eine sinnvolle Entwicklungsumgebung erstellen. Dies betrifft im Wesentlichen Verzeichnisstrukturen, in welchen für jedes Applet einzeln oder für alle Applets gemeinsam (hat beides Vorund Nachteile) der Applet-Code und die zugehörigen HTML-Seiten, sowie eventuelle Tondateien und Bilder platziert werden. Die folgende Verzeichnis-Strukturen erscheinen dafür ganz angebracht: Tabelle 7.1: VerzeichnisStrukturen
Verzeichnis
Beschreibung
/
Für .HTML/.HTM-Dateien
/
Für .java- und .classes-Dateien
//images
Für .gif- oder .jpeg-Bilddateien bzw. alle weiteren Bild-Dateien
Wenn Sie alle Applets in einer gemeinsamen Struktur verwalten, können Sie vor allem eventuell vorhandene Tondateien und Bilder leichter gemeinsam nutzen (wir werden das auf der Buch-CD so machen). Andererseits geht in der Praxis bei mehreren Projekten natürlich die Übersichtlichkeit verloren, weshalb eine solche Verzeichnisstruktur innerhalb eines eigenen AppletVerzeichnisses eigentlich sinnvoller ist.
342
Java 2 Kompendium
Grundlagen der Einbindung von Java-Applets in HTML-Seiten
7.2
Kapitel 7
Grundlagen der Einbindung von Java-Applets in HTML-Seiten
Kommen wir nun zur konkreten Einbindung der Java-Applets in HTMLSeiten. Es gab ursprünglich zwei konkurrierende Syntaxmethoden, um JavaApplets in einer HTML-Datei zu referenzieren. Die eine Syntax basiert auf einer Netscape-Entwicklung und wird unter anderem vom Navigator ab der Version 2.0 oder dem Internet Explorer interpretiert, die andere Syntax stammt von Sun selbst. Deren Browser HotJava in den frühen Versionen interpretierte diese Syntax. Die HotJava-Syntax gilt für Applets, die mit dem Java-1.0-alpha-Compiler erzeugt wurden, und wird mittlerweile nicht mehr unterstützt. Die Netscape-Syntax für Java-Applets gilt für alle Applets, die mit dem Java-1.0-beta-Compiler aufwärts erzeugt wurden, und der vom aktuellen HTML 4.0 unterstützte Standard bezieht sich auf die NetscapeSyntax. Auch der HotJava-Browser nutzt mittlerweile diese Syntax. Seit HTML 4.0 gibt es zwei zusätzliche Möglichkeiten, Java-Applets in eine Webseite zu integrieren. Java-Applets werden innerhalb von HTML-Seiten in Form von Referenzen eingebunden. HTML gibt als ein Dokumentenformat in Form von Klartext (ASCII-Text) unverbindliche Empfehlungen an eine Darstellungssoftware (den Browser), wie eine bestimmte Dokumentenstruktur darzustellen und welche Funktionalität wie auszuführen ist, damit sie dem geplanten Layout und der vorgesehenen Funktionalität entspricht. Es gibt in reinem HTML1 keine absolute Darstellungsvorschrift, weswegen sich Aufführungen von HTML-Seiten in verschiedenen Browsern oft erheblich unterscheiden können (eine Folge der geplanten Plattformunabhängigkeit). Um nun ein Applet in eine HTML-Seite integrieren zu können, müssen wir uns ein wenig mit dem Aufbau einer solchen Webseite beschäftigen.
7.2.1
HTML-Grundlagen
HTML ist die Abkürzung für Hypertext Markup Language und eine Dokumentbeschreibungssprache aus Klartext, mit der die logischen Strukturen eines Dokuments plattformunabhängig beschrieben werden. Das World Wide Web besteht in seiner Grundstruktur aus HTML. Hinter der Normung der Sprache HTML stand und steht auch heute noch das World Wide Web-Consortium (W3C – ursprünglich W3O – O für Organisation – genannt – http://www.w3.org). HTML liegt derzeit in der Version 4.0 vor. HTML ist viel einfacher als Programmiersprachen und auch als Scriptsprachen. Es gibt beispielsweise keinerlei Kontrollstrukturen in Form von Bedin-
1
von Stylesheets abgesehen, aber die gehören nicht direkt zu HTML
Java 2 Kompendium
343
Kapitel 7
Grundlagen der Applet-Erstellung gungen, Sprüngen oder Schleifen. Es gibt auch keine Befehle im Sinne von Befehlswörtern, die eine Aktion auslösen. HTML beinhaltet in seiner aktuellen Version nur einige Erweiterungen für den Aufruf von Scripten. Die Sprache HTML ist eine äußerst fehlertolerante Beschreibungssprache. Selbst in Situationen, wo in anderen Sprachen geschriebene Dokumente oder Programmstrukturen einen Fehler oder einen Programmabbruch auslösen würden, werden HTML-Dokumente trotzdem oft noch brauchbare Resultate liefern. Der Grund dafür ist das so genannte Prinzip der Fehlertoleranz von HTML. Dies bedeutet im Wesentlichen, dass die Programme zur Auswertung von HTML-Dokumenten so fehlertolerant wie irgend möglich programmiert werden, um syntaktisch unkorrekte Dokumente so weit wie möglich auswerten zu können. Soweit die Browser dann korrekte HTMLAnweisungen vorfinden, werden diese Anweisungen ausgeführt und angezeigt. Falsche oder unvollständige Anweisungen werden ganz einfach ignoriert. Dies kann so weit gehen, das sogar HTML-Dokumente ohne das – eigentlich zwingende – Grundgerüst einer Webseite weitgehend dargestellt werden. Jeder Text, den der Browser vorfindet und der nicht als fehlerhafte Syntax ignoriert wird, wird von ihm angezeigt. Reine HTML-Seiten haben in der Regel die Endung HTM, HTML oder SHTML. Alleine diese Endungen lassen das Dokument zu einem HTML-Dokument werden. Für eine HTML-Seite gibt es jedoch dennoch wie für jedes andere Dokument oder andere Programmiersprache immer ein Grundgerüst und gewisse Grundregeln. Dabei sind insbesondere zwei Begriffe wichtig: Steueranweisungen und Tags. HTML-Steueranweisungen Die HTML-Befehle werden Steueranweisungen genannt. Die HTML-Sprache hat vom Aufbau her eine gewisse Ähnlichkeit zur einfachen Batch-Programmierung unter DOS. Die Steueranweisungen werden in Form einer Stapeldatei in Klartext erstellt. Sprünge und Schleifen im Source kommen nicht vor, dafür sind Layout-Möglichkeiten in HTML sehr ausgeprägt. Alle HTML-Steueranweisungen werden in so genannte Tags geschrieben, die von spitzen Klammern – dem »kleiner« und dem »grö ß er«-Zeichen – begrenzt werden. Ein HTML-Tag sieht also von der Struktur her immer so aus: <xyz> Dabei unterscheidet man zwischen Einleitungs- und Abschluss-Tags. Abschluss-Tags sind bis auf einen den öffnenden Klammer folgenden Slash (/) identisch zum Einleitungs-Tag. Das Abschluss-Tag zum obigen Einleitungs-Tag würde wie folgt aussehen:
344
Java 2 Kompendium
Grundlagen der Einbindung von Java-Applets in HTML-Seiten
Kapitel 7
Muster: ... ...
Bei den HTML-Steueranweisung spielt es im Gegensatz zu Java-Token keine Rolle, ob sie groß oder klein geschrieben werden. Die Anweisung bewirkt das Gleiche wie . Auch eine unterschiedliche Groß- und Kleinschreibung im Einleitungs- und Abschluss-Tag hat keine negativen Auswirkungen, erhöht jedoch nicht gerade die Lesbarkeit. Für die Zukunft steht aber zu erwarten, dass auch in HTML Groß- und Kleinschreibung relevant wird. Dann werden (von XML beeinflusst) HTML-Anweisungen wahrscheinlich klein geschrieben. Es gibt Tags, die zwingend ein Einleitungs- und einen Abschluss-Tag benötigen. Beide zusammen bilden einen so genannten Container. Beispiel (Zentrieren von Elementen): ... Andere Tags kommen in der strengen HTML-Syntax hingegen nur als Einleitungs-Tag vor. Beispiel (ein Zeilenumbruch): Das Grundgerüst einer HTML-Seite Eine HTML-Seite wird immer in die Anweisung am Anfang und am Ende eingeschlossen. Die beiden Anweisungen bilden das äußere Gerüst einer HTML-Seite. Beispiel: ...weitere HTML-Anweisungen...
Davor dürfen höchstens Kommentarzeilen stehen, die natürlich auch überall im Inneren einer HTML-Seite verwendet werden können. Die Steuerzeichen für Kommentarzeilen sind ein Ausrufezeichen, zwei Striche am Anfang und zwei Striche am Ende des Tags. Jeder in diesem Tag stehende Text wird vom interpretierenden Browser als Kommentar betrachtet. Ein Kommentar kann über mehrere Zeilen gehen und natürlich ebenfalls im Inneren einer HTML-Seite verwendet werden. Auch ein Kommentar-Tag muss von spitzen Klammern eingeschlossen werden, also sehen die Zeichenfolgen so aus:
Java 2 Kompendium
345
Kapitel 7
Grundlagen der Applet-Erstellung Beispiel:
Ein wichtiger Spezialfall für die Verwendung der Kommentare ist die Einbindung von Script-Elementen in Webseiten. Sie werden damit vor dem HTML-Interpreter des Browsers versteckt, stehen hingegen dem ScriptInterpreter des Browsers unverändert zur Verfügung (ein HTML-Kommentar ist kein Script-Kommentar). Das weitere Grundgerüst einer HTML-Datei besteht grundsätzlich aus folgenden zwei Teilen: dem (optionalen) Header (Kopf) dem Body (Körper) Nach der einleitenden HTML-Anweisung kommt auf einer Webseite normalerweise ein Header-Teil, das heißt ein Kopfteil, in dem die allgemeinen Informationen über die Seite zusammengefasst werden. Beispielsweise der Titel, der in die Zeichenketten <TITLE> und eingeschlossen wird. Ein Header wird mit dem Steuerzeichen begonnen und mit entsprechend wieder geschlossen. Ein einfaches Headergerüst ist wie folgt aus: Beispiel: <TITLE>Titel
Die eigentlichen Daten, die der WWW-Browser einem Anwender auf dem Bildschirm anzeigen soll, werden in den Body geschrieben. Die Tags ... definieren den Body. In ihm ist der eigentliche Text mit Überschriften, Verweisen, Grafikreferenzen und auch die Referenz für Java-Applets zu notieren. Vor Beginn des HTML-Tags kann mit einem so genannten Tag ein Verweis auf eine DTD-Datei integriert werden. Diese legt die HTML-Version fest und im Prinzip muss jedem SGML-Dokument (so auch einer HTML-Seite) eine solche DTD-Datei zugeordnet sein. Dieses Tag ist jedoch optional, da das WWW so funktioniert, dass alleine die Dateiendung HTML oder HTM bereits ausreicht, um ein Dokument als HTML-Datei zu identifizieren. Der jeweilige Browser benötigt also nicht explizit die Document Type Definition. Es ist sogar so, dass die entsprechende Angabe von den meisten Browsern ignoriert wird bzw. keine weiteren Konsequenzen für die Darstellung der HTML-Datei hat. 346
Java 2 Kompendium
Grundlagen der Einbindung von Java-Applets in HTML-Seiten
Kapitel 7
Das vollständige Grundgerüst einer normalen HTML-Datei sieht also schematisch immer so aus. <TITLE>Titel Überschriften, Text, Verweise, Grafiken, Java-Applet-Referenz usw.
Listing 7.1: Schematisches Grundgerüst einer Webseite
HTML-Strukturen können verschachtelt werden, das heißt, zwischen einem einleitenden und einem abschließenden Tag können beliebige – sinnvolle – andere Steueranweisungen stehen. Dies ist sogar zwingend, da insbesondere die vollständige HTML-Seite ein einleitendes und ein abschließendes Tag benötigt werden und alle weiteren Steueranweisungen darin verschachtelt werden müssen. Bei der Verschachtelung von Tags sollte jedoch unbedingt die Reihenfolge eingehalten werden! Das bedeutet, wenn ein Container (Einleitungs- und Abschluss-Tag) weitere Tags enthalten soll, sollten diese vollständig darin enthalten sein und in umgekehrter Reihenfolge der Einleitungs-Tags die Abschluss-Tags aufweisen. Zwar kann in vielen Fällen auch eine sich überschneidende Verschachtelung funktionieren, sie ist aber schlecht lesbar und kann in einigen Fällen zu Fehlern führen. Wichtige HTML-Elemente HTML bietet inzwischen eine Menge Gestaltungselemente. Das fängt bei Farben an und geht über Textformatierung, Überschriften, Listen, Tabellen, Eingabefeldern, Buttons bis hin zu Multimedia-Effekten. Diese gehören jedoch nicht in ein Java-Buch, sondern in eine Abhandlung über HTML. Sie sollten jedoch den in unserem Zusammenhang wichtigsten Bestandteil kennen, die Referenz auf andere Objekte innerhalb einer Webseite. Der wichtigste Fall einer Referenz auf andere Objekte ist der Verweis oder Hyperlink. Das WWW lebt nur von diesen Hyperlinks, den lokalen Sprüngen und vor allem den Verweisen auf die verschiedensten anderen InternetInhalte. Mit einem Verweis verzweigen Sie durch Anklicken sofort zu einer angegebenen Adresse, laden ein Programm auf dem lokalen Rechner oder senden eine E-Mail an die im Verweis angegebenen Adresse. Die Grundstruktur eines Verweises in einer Datei setzt man mit den folgenden Steueranweisungen: Verweistext
Java 2 Kompendium
347
Kapitel 7
Grundlagen der Applet-Erstellung Hierbei müssen die einzelnen Bestandteile dieser Grundstruktur natürlich noch genauer bezeichnet werden, was wir uns hier schenken wollen. Nur so viel: Der Typ gibt die Art des Verweises an. Der Bezeichner ist die konkrete Adresse (eine URL). Bei einem Verweis unterscheidet man zwischen verschiedenen Typen von Verweisen und daraus ergibt sich die genaue Syntax des Verweises. Wir wollen uns nur exemplarisch ein paar Beispiele mit verschiedenen Verweistypen ansehen und diese dann kurz beschreiben.
Tabelle 7.2: Einige Beispiele für Hyperlinks
Referenz
Beschreibung.
Zur Datei
Beispiel für einen Verweis auf eine andere Datei innerhalb des gleichen Verzeichnisses.
Zur Datei
Beispiel für einen Verweis auf eine andere Datei in einem relativen Verzeichnis.
Dies ist ein Verweis mit absoluter Pfadangabe auf einem lokalen PC
Beispiel für einen Verweis auf eine lokale, absolut adressierte Datei.
Der Autor
Beispiel für einen nicht-lokalen Verweis.
Ab ins Mehl
Aufruf des E-Mail-Clients aus der Webseite heraus.
Ein anderer Typ von Referenz sind Grafikbezüge. Beispiel: Tabelle 7.3: Eine Grafikreferenz
Referenz
Beschreibung
Referenz auf eine Grafik.
Kommen wir nun zu dem nächsten Referenztyp: der Java-Applet-Referenz (und da wollen wir ja eigentlich hin).
348
Java 2 Kompendium
Konkrete Referenzierung eines Java-Applets
7.3
Kapitel 7
Konkrete Referenzierung eines Java-Applets
Es gibt derzeit drei aktuelle Varianten, wie Sie ein Applet in eine Webseite integrieren können. Mit dem <APPLET>-Tag, dem <EMBED>-Tag und dem -Tag. Wie bereits angesprochen, ist die ab dem HTML-Standard 4.0 erlaubte Referenzierung eines Applets über das -Tag mit Vorsicht zu genießen, weil dieser Standard noch nicht von vielen Browsern unterstützt wird und das <APPLET>-Tag sicher noch einige Jahre verwendet werden kann. Das <EMBED>-Tag sollte ebenso mit Vorsicht eingesetzt werden.
7.3.1
Die <APPLET>-Syntax
Schauen wir uns zuerst die konkrete Referenzierung eines Java-Applets mit dem <APPLET>-Tag an. Zwischen dem einleitenden Tag und dem abschließenden Tag können Sie gegebenenfalls benötigte Parameter, die dem Applet beim Aufruf übergeben werden, oder auch beliebigen Text eingeben. Die einfachste Form der Applet-Referenz Ein Java-Applet in der einfachsten Form (das heißt ohne irgendwelche Parameter und optionale Attribute) wird mit folgender HTML-Syntax in eine HTML-Seite eingebunden: <APPLET CODE="[classelement]" WIDTH=[Wert] HEIGHT=[Wert]>
Dabei bezeichnet [classelement] die Applet-Klasse, also das Applet selbst (mit Namenserweiterung .class oder auch ohne – es geht beides) WIDTH=[Wert] die Breite des Applets in Pixel. HEIGHT=[Wert] die Höhe des Applets in Pixel. WIDTH und HEIGHT bestimmen also die anfängliche Grö ß e der Darstellung des Applets innerhalb der HTML-Seite. Diese Grö ß enangaben haben nichts direkt mit der »echten« Grö ß e des Applets zu tun. Es kann also durchaus zu Verzerrungen (auch gewollten) des Applets kommen oder unter Umständen wird bei zu kleiner Wahl der Breite und Höhe nicht das vollständige Applet angezeigt.
Die Angabe der Breite und Höhe ist bei der Verwendung des Appletviewers zwingend, während beim Fehlen einer dieser Angaben in vielen Browsern das Applet dennoch angezeigt wird. Setzen Sie aber zur Sicherheit immer beide.
Java 2 Kompendium
349
Kapitel 7
Grundlagen der Applet-Erstellung
Abbildung 7.1: Da fehlt was
Es folgt ein Beispiel für die Syntax zur Einbindung eines einfachen JavaApplets ohne weitere Angaben (nur die Breite und Höhe). Wir verwenden am Anfang ein Applet, das wir ja sowieso noch haben sollten. Beispiel: Listing 7.2: Einfachste Form der Applet-Referenz
<APPLET CODE="HelloJavaApplet.class" WIDTH=150 HEIGHT=75>
Die Angabe der Applet-Klasse erfolgt in der Regel in Hochkommata und mit Dateierweiterung. Beides ist aber – zumindest in den meisten Browsern – nicht zwingend, aber sinnvoll. Der Anzeigebereich des Applets wäre in obigem Beispiel 150 Pixel breit und 75 Pixel hoch. Obwohl es für unser Applet noch nicht viel Sinn macht (der Text bleibt einfach so klein wie er ist), wollen wir die Grö ß enangaben einmal auf WIDTH=150 und HEIGHT=275 verändern. Über die optionale Angabe SRC kann man innerhalb der Applet-Referenzierung zusätzlich die Source-Datei (die Datei mit der Kennung .java) eines Java-Applets angegeben. Diese wird jedoch im Prinzip nicht benötigt, um ein Applet in eine Webseite einzubauen oder zu starten, nur zur Dokumentation der Funktionalität und einer eventuellen Modifizierung des Applets. 350
Java 2 Kompendium
Konkrete Referenzierung eines Java-Applets
Kapitel 7 Abbildung 7.2: Unser Applet 150 Pixel ist breit und 75 Pixel hoch
Abbildung 7.3: Andere Größ enangaben
Java 2 Kompendium
351
Kapitel 7
Grundlagen der Applet-Erstellung Beispiel:
Listing 7.3: Applet-Referenz mit Source-Angabe
<APPLET CODE="HelloJavaApplet.Class" SRC="HelloJavaApplet.Java" WIDTH=100 HEIGHT=50>
Applet-Referenz mit Pfaden Die bisherigen Beispiele funktionieren nur, wenn sich das Applet im gleichen Verzeichnis wie die aufrufende HTML-Datei befindet. Wenn sich das Applet in einem anderen Verzeichnis befindet, wird dieses über das Attribut CODEBASE bestimmt. Dies ist die Suchpfadangabe für das Applet – eine übliche URL. Hier ein Beispiel für die Einbindung eines Java-Applets mit zusätzlich Pfadangaben (relativ) auf das Unterverzeichnis classes. Beispiel: Listing 7.4: Applet-Referenz mit relativer Angabe eines Suchpfades für die .class-Datei
<APPLET CODEBASE="classes" CODE="HelloJavaApplet.class" WIDTH=100 HEIGHT=50>
Das Verzeichnis in der CODEBASE-Anweisung wird in der Regel ebenfalls in Hochkommata stehen und kann vor oder hinter der Codeanweisung stehen. In diesem Beispiel wird die Position des Applets relativ zur Position des HTML-Dokuments angegeben. Ein Applet kann genauso gut mit einer absoluten Pfadangabe referenziert werden. Dabei spielt es keine Rolle, ob sich das Applet auf dem gleichen Rechner oder einem anderen Server befindet, wie das folgende Beispiel für die Einbindung eines Java-Applets mit absoluten Pfadangaben zeigt (mit Kommentar innerhalb des Containers, der in nicht-Java-fähigen Browsern angezeigt wird). Beispiel:
Listing 7.5: Applet-Referenz mit absoluter URLAngabe eines Suchpfades für die .class-Datei
<APPLET CODEBASE="http://www.rjs.de" CODE="HelloJavaApplet.class" WIDTH=100 HEIGHT=50> Das Applet wird über http://www.rjs.de adressiert
Für CODEBASE kann also eine beliebige URL gesetzt werden. Es können also darüber ganz leicht Netzwerkzugriffe realisiert werden (wenngleich auf diese Art von außerhalb des Applets).
352
Java 2 Kompendium
Konkrete Referenzierung eines Java-Applets
Kapitel 7
Die genaue Position des Applets in der Webseite Die Position eines Applets auf der Seite ergibt sich aus der Stelle, wo das <APPLET>-Tag in das HTML-Dokument eingefügt ist. Dabei kann ein Applet genau wie andere Objekte (etwa Überschriften oder Grafiken) innerhalb der Seite mit allen üblichen HTML-Spezifikationen ausgerichtet werden. Ein Applet, das beispielsweise in die HTML-Zentrierungs-Tags und eingeschlossen ist, wird beim Anzeigen zentriert ausgegeben. Beispiel: <APPLET code="HelloJavaApplet.class" width="500" height="600">
Listing 7.6: Zentrierung eines Applets Abbildung 7.4: Das Applet zentriert
Zusätzlich lassen sich bei einem Applet noch Angaben über den Textfluss um das Applet herum machen, wie das nachfolgende Beispiel für die Einbindung eines Java-Applets mit Angaben zum Textfluss zeigt. Beispiel: <APPLET CODE="HelloJavaApplet.class" WIDTH=100 HEIGHT=50 ALIGN=middle VSPACE=25 HSPACE=30>
Java 2 Kompendium
Listing 7.7: Angaben zum Textfluss um das Applet herum
353
Kapitel 7
Grundlagen der Applet-Erstellung Dabei gibt es für die Angabe ALIGN, die die Ausrichtung von einem dem Applet folgenden Text angibt, folgende Werte: LEFT: links ausgerichtet RIGHT: rechts ausgerichtet MIDDLE: zentriert in der horizontalen Ausrichtung am Applet
Die Angabe VSPACE definiert den vertikalen Abstand zwischen Applet und Text oberhalb und unterhalb des Applets. HSPACE definiert den horizontalen Abstand links und rechts vom Applet. Der Container-Inhalt Zwischen dem einleitenden Tag <APPLET> und dem abschließenden -Tag kann beliebiger Text stehen. Dies nutzt man hauptsächlich dafür, in einem nicht Java-fähigen Browser deutlich zu machen, dass an dieser Stelle ein Java-Applet steht. Sie sollten sich nicht täuschen – so was gibt es wirklich noch. Und nicht zuletzt: Die Java-Option lässt sich auch in modernen Browsern deaktivieren. Es ist kein guter Stil, wenn man sich einfach auf den Standpunkt stellt, dass jeder Besucher einer Webseite gefälligst Javafähige Browser zu nutzen und die entsprechende Option zu aktivieren hat. Denken Sie daran, dass in der Regel nicht der Surfer etwas von Ihnen will, sondern Sie ihm etwas anbieten wollen. Machen Sie doch einfach Surfer mit fehlender Java-Funktionalität im Browser neugierig, indem Sie mit einem netten Sätzchen locken. Man macht sich übrigens bei dem Text zwischen den <APPLET>-Tags wie auch beispielsweise bei Grafiken oder der Frame-Technik das Prinzip der Fehlertoleranz zunutze. Wenn Browser unbekannte Tags – in diesem Fall das <APPLET>-Tag – finden, werden sie den Tag ignorieren und den nachstehenden Text als reinen ASCII-Text auf dem Bildschirm anzeigen. Javafähige Browser (mit aktivierter Java-Option) sind so konzipiert, dass sie den Text zwischen ein- und abschließenden -Tags nicht anzeigen, sondern eben das Applet selbst. Sie können zum Beispiel angeben, dass dort ein Java-Applet steht, das nicht Java-fähige Browser eben nicht anzeigen. Beispiel: <APPLET code="HelloJavaApplet.class" width="500" height="600">Wenn Sie hier diesen Text sehen, ist Ihr Browser nicht in der Lage, Java-Applets darzustellen oder Sie haben die entsprechende Option deaktiviert, denn hier sollte ein Applet zu sehen sein! Ich kann nur sagen – Sie verpassen etwas. Wie wäre es – aktivieren Sie Ihre Java-Option und laden Sie dann die Seite neu. Falls Sie keinen Java-fähigen Browser haben – etwas weiter unten
354
Java 2 Kompendium
Konkrete Referenzierung eines Java-Applets
Kapitel 7
befinden sich ein paar Hyperlinks, von denen Sie sich solche Browser laden können. Und dann: Come back and enjoy Java!
Der alte Mosaic-Browser von CompuServe ist ein Exempel für einen nichtJava-fähigen Browser. Hier wird statt des Applets der freundliche Hinweis auf ein Applet zu sehen sein. Abbildung 7.5: Langsam wird es Zeit für ein Update.
Die gleiche HTML-Datei sieht in einem Java-fähigen Browser wie wir oben schon gesehen haben oder vergleichbar aus. Bei Text, der das Applet umfließt, kann es zu Missverständnissen kommen, da vor und nach dem Applet stehender Text den Hinweis auf das Applet in nicht-Java-fähigen Browsern unter Umständen nicht auffällig genug macht. Verwenden Sie am besten irgendwelche Unterschiede zwischen normalem Text und dem Hinweis auf das Applet (etwa Farbe oder Schriftgrö ß e).
7.3.2
Die Parameter des <APPLET>-Tags
Eine ganz wichtige Art von Angaben kann zwischen dem einleitenden Tag <APPLET> und dem abschließenden Tag stehen – die Parameter, die über eigene Tags innerhalb des Containers notiert werden. Unter solchen Parametern versteht man Werte, die ein Programm – oder in diesem Fall das Applet – benötigt, um laufen zu können und die den Ablauf des Programms/ Applets beeinflussen. Die Situation ist uns ja nicht unbekannt. Bei vielen DOS-Befehlen muss man Parameterangaben machen, damit der Befehl erst einen Sinn bekommt. Denken Sie an den Kopierbefehl COPY, der erst durch Angabe von zwei Parametern – der Quelle und dem Ziel – vom Betriebssystem ausgeführt werden kann und auch dann erst Sinn macht.
Java 2 Kompendium
355
Kapitel 7
Grundlagen der Applet-Erstellung
Abbildung 7.6: Schlecht: Der Applethinweis ist kaum zu erkennen.
Abbildung 7.7: So ist es zumindest zu erkennen.
Wir kennen auch bereits einen Java-Fall, wo wir Parameter von außen an ein Java-Programm übergeben haben. Um Argumente auf Befehlszeilenebene an ein Java-Programm weiterzugeben, müssen diese einfach an den Programmaufruf – mit Leerzeichen abgetrennt – angehängt werden. Die Parameter in dem <APPLET>-Container der HTML-Seite sind nun bei einem Applet das Analogon zu den Argumenten auf Befehlszeile bei einer eigenständigen Applikation.
356
Java 2 Kompendium
Konkrete Referenzierung eines Java-Applets
Kapitel 7
Wenn Sie ein Applet nur in eine Webseite einbinden wollen, haben Sie keinen Einfluss auf die verwendeten Parameter. Dies ist natürlich eine andere Situation, als wenn Sie den Source des Applets erstellen. Welche Parameter ein Applet konkret benötigt, ist fest in dem Java-Applet einprogrammiert und kann von einem reinen Anwender beim Aufruf nicht verändert werden. Viele Applets benötigen keine Parameter, andere wiederum mehrere. Wenn Sie ein bestehendes Applet einbinden wollen, müssen Sie diese Information aus der Dokumentation des Applets entnehmen. Wenn Sie keinen Hinweis auf Parameter finden, ist es sehr wahrscheinlich, dass das Applet auch keine braucht. Auf jeden Fall gilt dann »Try and Error«. Wenn es funktioniert, dann benötigt es wirklich keine Parameter. Wir kommen in einem nachfolgenden Abschnitt zur Auswertung und Verwendung von Parametern im Source des Applets. Lassen Sie uns zuerst noch die HTML-Syntax für die Parameter beenden. Die Angabe von Parametern erfolgt einfach über Parameter-Tags – gegebenenfalls mehrere hintereinander. In diesem Fall spezifiziert jedes Parameter-Tag einen anderen AppletParameter. Sie sehen wie folgt aus: .
In den Parameterangaben werden zwei Bezeichner verwendet: name – der Name des Parameters value – der Wert des angegebenen Parameters
Ein Beispiel für die Einbindung eines Java-Applets mit Parameter-Angaben: <APPLET code="Ataxxlet.class" width=450 height=550>
Listing 7.8: Beispiel einer Applet-Referenz mit Parametern
Im gesamten -Tag kann man zwar Groß- und Kleinschreibung verwenden (es ist ja HTML), aber beim name- und value-Wert kann Großund Kleinschreibung eine Rolle spielen. Diese Werte werden ja an das JavaApplet übergeben und da kommt es darauf an, wie dieses die Werte verwendet. Abschließend soll besprochen werden, was für und was gegen die AppletReferenzierung mit dem <APPLET>-Tag spricht.
Java 2 Kompendium
357
Kapitel 7
Grundlagen der Applet-Erstellung Kontra: 1.
Der Tag ist vom W3C als deprecated erklärt worden. Das will aber nicht viel heißen, denn viele als deprecated erklärte Tags werden unverändert weiter verwendet. Oft ist es sogar so, dass deren Verwendung statt der offiziellen Befehle von Profis gerade ausdrücklich empfohlen wird. So beruhen Optimierungsmaßnahmen in einer HTML-Seite beispielsweise darauf, bei einer Zentrierung statt des align-Attributs für Absatz-bezogene HTML-Tags (offizielle Empfehlung) das als deprecated gekennzeichnete -Tag zu verwenden, da damit Zeichen gespart werden können und die Seite kleiner und damit schneller zu laden ist.
2.
Die Unterstützung der Java-Plug-Ins funktioniert mit diesem Tag nicht. Wenn in einem Applet also Java-APIs verwendet werden, die von der virtuellen Maschine des Browsers nicht unterstützt werden, wird das Applet nicht laufen. Um es drastisch zu formulieren – ein mit diesem Tag referenziertes Applet sollte nur Techniken verwenden, die maximal auf Java 1.0.2 beruhen oder zumindest in allen potenziellen Zielbrowsern getestet werden.
Pro: 1.
Der Tag ist einfach anzuwenden.
2.
Der Tag wird von allen Java-fähigen Browsern verstanden – auch den älteren.
3.
Es wird kein zusätzliches Plug-In benötigt.
4.
Der Tag ist populär. Das soll Verschiedenes bedeuten. Einmal kennen viele Anwender dieses Tag. Das führt dazu, dass jeder, der den Quelltext liest, leicht erkennen kann, dass hier ein Java-Applet referenziert wird. Die Konkurrenz-Tags machen das dem Laien nicht so deutlich. Diese suggerieren etwa im Fall des -Tags eine Verbindung zu der umstrittenen ActiveX-Technologie und das kann zur Ablehnung durch den Anwender führen. Die Popularität bedeutet aber auch, dass viele HTML-Tools dieses Tag automatisch verwenden, wenn ein Anwender ein Java-Applet einbindet.
7.3.3
Die <EMBED>-Syntax
Das <EMBED>-Tag ist eine Netscape-Erweiterung von HTML 3.2 zur allgemeinen Einbindung von Multimedia-Elementen und auch Applets in eine Webseite. Er wird gelegentlich auch Java-Plug-In-Tag genannt. Die Verwendung sollte mit Vorsicht geschehen, da die Technik im Wesentlichen auf Netscape Browser ab der Version 4.x beschränkt ist (einige Ausprägungen
358
Java 2 Kompendium
Konkrete Referenzierung eines Java-Applets
Kapitel 7
des Tags werden aber auch in neueren Versionen des Internet Explorers verstanden). Java-Applets werden damit mit dem angegebenen Java-Plug-In von Sun gestartet. Die allgemeine Syntax sieht so aus: <EMBED type="[PlugIn]" pluginspage ="[URL]" code="[NameApplet].class" name="[NameApplet]" object="[serializedObjectOderJavaBean]" codebase="[VerzeichnisclassFile]" width="[Breite]" height="[Höhe]" align = [Ausrichtung] vspace = [Textfluss vertikal] hspace = [Textfluss vertikal] > ...
Listing 7.9: Schema für die Einbindung eines Applets mit <EMBED>
Über das Attribut type geben Sie das verwendete Plug-In an. Diese Angabe kann das Plug-In noch genauer spezifizieren (etwa "application/x-java-applet;version=1.2"). Über die optionale Angabe pluginspage kann man eine Downloadmöglichkeit in Form einer URL angegeben, wo der ein lokal nicht vorhandenes Plug-In bei Bedarf automatisch geladen werden kann (etwa pluginspage="http://java.sun.com/products/plugin/1.3/plugin-install.html"). Die Angaben code und object dürfen nicht gleichzeitig verwendet werden, die restlichen Attribute sind wie beim <APPLET>-Tag zu verstehen. Die Einbindung von HelloJavaApplet.class funktioniert beispielsweise so: <EMBED type="application/x-java-applet;version=1.3" CODE = "HelloJavaApplet.class" CODEBASE = "." NAME = "HelloJavaApplet" WIDTH = 400 HEIGHT = 300 ALIGN = middle VSPACE = 0 HSPACE = 0 pluginspage= "http://java.sun.com/products/plugin/1.3/plugin-install.html"> Java 2 Kompendium
Listing 7.10: Die Einbindung des Applets HelloJavaApplet.class mit <EMBED>
359
Kapitel 7
Grundlagen der Applet-Erstellung
Abbildung 7.8: Die Darstellung im Navigator funktioniert mit <EMBED>
Mit dieser Form der Einbindung gibt es leider diverse Probleme. Kontra: 1.
Der Tag ist vom W3C als deprecated erklärt worden. Das ist aber wie beim <APPLET>-Tag nur ein »Papier-Tiger«-Argument.
2.
Der Tag ist mit seinen Parametern relativ komplex. Bei vielen »HobbyHomepage-Bauern« wird das ein Problem.
3.
Der Tag ist in dieser vollständigen Version auf neuere Navigator-Versionen und kompatible Browser beschränkt. Man muss also für Internet-Explorer-Anwender zusätzlich ein weiteres Tag verwenden.
4.
Ein Applet kann nicht dargestellt werden, wenn das angegebene PlugIn nicht zur Verfügung steht und gerade keine Onlineverbindung besteht, der Anwender kein extra Plug-In installieren möchte oder der Browser mit dem Plug-In nicht zurechtkommt.
5.
Wenn das Java-Plug-In verwendet werden soll, muss beim Client eine passende, zusätzliche virtuelle Maschine vorhanden sein.
Pro: 1.
Das Java-Plug-In wird unterstützt. Das bedeutet, ein Java-Applet kann theoretisch die gleichen APIs nutzen wie eine vollständige Java-Applikation. Das setzt aber voraus, dass auf der Client-Plattform eine passende virtuelle Maschine vorhanden ist.
7.3.4
Die -Syntax
Das -Tag ist das neue Standard-Tag unter HTML 4.0 zum Einbinden von Applets und beliebigen Multimedia-Objekten in eine Webseite. Eingeführt wurde der Tag in der Grundform ursprünglich als Erweiterung des Internet Explorer 4.x für HTML 3.2. Die Syntax sieht schematisch so aus: 360
Java 2 Kompendium
Konkrete Referenzierung eines Java-Applets
Kapitel 7
Der Wert des classID-Attributs ist die weltweit eindeutige ActiveX-ID für das Java-Plug-In (immer classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93"), das Sie setzen müssen, damit das -Tag es im Browser aktiviert. Das codebase-Attribut wird dafür verwendet, das Java-Plug-In aus dem Netz zu laden, wenn es fehlt (etwa codebase="http://java.sun.com/products/plugin/ 1.3/jinstall-13-win32.cab#Version=1,3,0,0"). Verwechseln Sie nicht das codebase-Attribut des -Tags mit dem Parameter codebase (<param name="codebase" value="[Verzeichnis-Class-File]">), der optional eine relative URL zur Angabe des Ortes von der Applet-Klasse spezifizieren kann. Die Angabe <param name="JAVA_CODE"... ist eine Spezifizierung für den Fall, dass es Konflikte zwischen anderen Parametern der Form <param name="CODE"... gibt. Das type-Parameter-Tag wird zum Laden des Plug-Ins verwendet. In der NAME="type" Regel ist das wieder eine Angabe wie
Für ein serialisiertes Objekt oder JavaBean wird das type-Parameter-Tag in der Regel so aussehen: <param name="type" value="application/x-java-bean;version=1.1">
oder <param name="type" value="application/x-java-bean">
Was spricht nun für und gegen den Einsatz dieses Tags? Java 2 Kompendium
361
Kapitel 7
Grundlagen der Applet-Erstellung Kontra: 1.
Der Tag ist mit seinen Parametern relativ komplex. Mehr noch als beim <EMBED>-Tag werden viele Webseiten-Ersteller von der Syntax abgeschreckt. Allerdings können viele HTML-Tools die manuelle Erstellungen abnehmen.
2.
Der Tag ist auf neuere Internet-Explorer-Versionen und kompatible Browser beschränkt. Man muss also für Navigator-Anwender zusätzlich einen weiteren Tag verwenden.
3.
Ein Applet kann nicht dargestellt werden, wenn das angegebene PlugIn nicht zur Verfügung steht und gerade keine Onlineverbindung besteht, der Anwender kein extra Plug-In installieren möchte oder der Browser mit dem Plug-In nicht zurechtkommt.
4.
Wenn das Java-Plug-In verwendet werden soll, muss beim Client eine passende, zusätzliche virtuelle Maschine vorhanden sein.
5.
Der Tag arbeitet mit einer ActiveX-ID. ActiveX-bezogene Technik wird weder von allen Browsern im Internet unterstützt, noch erlauben halbwegs sicherheitsbewusste Surfer deren Verwendung. Unnötigerweise wird eine absolut sichere Technologie wie Java mit dem wohl grö ß ten Sicherheitsrisiko im Web in Verbindung gebracht.
Pro: 1.
Der Tag ist vom W3C zum Standard-Tag erklärt worden. Aber genau wie bei den als deprecated gekennzeichneten Tags ist das kein echtes Argument. Weder Anwender noch die meisten Browser-Hersteller kümmert das in irgendeiner Weise.
2.
Das Java-Plug-In wird unterstützt. Das bedeutet, ein Java-Applet kann theoretisch die gleichen APIs nutzen wie eine vollständige Java-Applikation. Das setzt aber voraus, dass auf der Client-Plattform eine passende virtuelle Maschine vorhanden ist.
7.3.5
Welches Tag ist sinnvoll?
Da es für jede der drei beschriebenen Referenzmodell Pro- und KontraArgumente gibt, muss jeder Anwender abwägen, was ihm am wichtigsten ist. Einige Aussagen können dabei aber als Leitschnur dienen: Wenn das Applet möglichst große Verbreitung erhalten soll und keine Java-Technologie jenseits von Java 1.0.2 verwendet wird, ist das <APPLET>-Tag die einzig sinnvolle Wahl. Offizielle Quellen hin oder her. Diese argumentieren politisch.
362
Java 2 Kompendium
Die interne Arbeitsweise eines Applets
Kapitel 7
Wird Java-Technologie verwendet, deren Unterstützung in den virtuellen Maschinen der Browser man sich nicht sicher ist, sollte man mit dem <APPLET>-Tag die potenziellen Zielbrowser testen. Läuft das Applet mit der <APPLET>-Referenz, sollte diese auf jeden Fall genommen werden. Verwendet man Technologie, die jenseits der virtuellen Maschinen der Zielbrowser angesiedelt ist, kommt man um die Tags zum Aufruf des Java-Plug-Ins nicht herum. Mit allen Konsequenzen. In einem Intranet sind die negativen Auswirkungen zu minimieren. Im Internet sollte man sehr gut abwägen. Die bessere Alternative ist es sicher, wenn es irgend geht, bei Applets auf Java-APIs zu verzichten, die in Browsern Probleme machen können. Nutzen Sie den in Kapitel 1 näher beschriebenen HTML-Konverter (zu laden von der Sun-Plug-In-Homepage http://java.sun.com/products/ plugin), der aus einer Webseite mit dem <APPLET>-Tag eine solche mit -Tag (für den Internet Explorer) bzw. <EMBED>-Tag (für den Netscape Navigator) macht.
7.3.6
Die ursprüngliche HotJava-Syntax
Nur der Vollständigkeit halber sei die HotJava-Syntax zum Referenzieren eines Applets innerhalb einer HTML-Seite noch kurz angerissen. Sie definieren die Referenz auf ein Applet mit dem Befehl <APP>. Dieser Befehl hat offiziell kein abschließendes Tag. Parameterangaben werden in Form von Zusatzangaben innerhalb des <APP>-Tags notiert. Beachten muss man, dass die Klasse ohne .class angegeben werden muss.
7.4
Die interne Arbeitsweise eines Applets
Was passiert nun mit den Parametern, die beispielsweise mit dem Tag an das Applet weitergereicht werden? Und wie wird eigentlich ein Applet in seinem Inneren funktionieren? Es geht also ab jetzt darum, HTML wieder zu verlassen und zu Java zurückzukehren. Um diese Fragen zu beantworten, sollten wir uns an den Aufbau eines Applets erinnern, wie wir ihn schon vor unserem ersten Applet angesprochen haben. Eine eigenständig lauffähige Java-Applikation benötigt – wie wir jetzt schon mehrfach gesehen haben – immer nur eine main()-Methode. Ein Java-Applet besitzt im Gegensatz zu einem Java-Programm im Allgemeinen keine main()-Methode2, die beim Laden gestartet wird und das Programm solange am Leben erhält, bis der Benutzer es beendet. Statt dessen sorgen bei voller Funktions-
2
oder benutzt zumindest keine
Java 2 Kompendium
363
Kapitel 7
Grundlagen der Applet-Erstellung fähigkeit mindestens vier andere Methoden – die init()-Methode, die start()-Methode, die stop()-Methode und die destroy()-Methode – im Programm dafür, dass sich das Applet in seine Umgebung korrekt verhält. Ein Applet befindet sich immer innerhalb eines Containers und muss sich kooperativ zu ihm verhalten. Es muss also die Möglichkeiten nutzen, die ihm der Container zur Verfügung stellt. Um nun erklären zu können, was beispielsweise konkret mit den Parametern passiert, müssen wir noch ein wenig weiter ausholen.
7.4.1
Erstellung und Grundmethoden eines Applets
Um ein Applet zu erstellen, müssen Sie immer eine Subklasse der Klasse java.applet.Applet (fundamentaler Bestandteil von Java) erzeugen. Diese Klasse beinhaltet bereits sämtliche Eigenschaften, die die Kooperation mit dem Container sicherstellen, die Fähigkeiten des AWT für Oberflächenprogrammierung (Menüs, Button, Mausereignisse usw.) nutzbar machen und auch die lebenswichtigen Grundmethoden für ein Applet. Letzteres war auch der Grund, warum unser erstes Applet funktioniert hat, obwohl wir auf diese Methoden verzichtet haben. Sie wurden einfach aus der Superklasse gezogen. Normalerweise werden Sie diese jedoch überschrieben. Jede Applet-Klasse wird mit folgender Syntax als Unterschrift erstellt: public class <AppletName> extends java.applet.Applet
Java setzt übrigens für die sinnvolle Verwendung voraus, dass eine AppletKlasse als public deklariert wird.
Applets sollten java.applet.* importieren, um sich im Laufe des Quelltexts Schreibarbeit zu sparen. Applets mit grafischen Schnittstellen benötigen meist auch java.awt.*. Im Inneren des Applet-Codes werden dann die notwendigen Methoden für die Funktionalität des Applets platziert. Darunter fallen natürlich ebenfalls die Grundmethoden, die das Leben eines Applets festlegen. Schauen wir uns nun diese vier Stationen im Leben eines Applets im Detail an. Sie werden normalerweise in einem Applet sie teilweise oder gar allesamt überschreiben. Die Initialisierung Der Einstieg in ein Applet-Leben ist immer die Initialisierung. Diese erfolgt immer mit der Methode public void init(), die automatisch aufgerufen
364
Java 2 Kompendium
Die interne Arbeitsweise eines Applets
Kapitel 7
wird, sobald das Applet in den Browser (oder einen anderen Container wie den Appletviewer) geladen wird. Hier wird das System zur Ausführung des Applets vorbereitet. An dieser Stelle werden beispielsweise die Bildschirmanzeige initialisiert, Netzwerkverbindungen oder Datenbankzugriffe aufgebaut oder Schriften eingestellt. Und hier ist auch die Stelle, wo die Parameter aus dem HTML-Code der Seite eingelesen werden können. Sie können hier mit der Methode public String getParameter(String name) auf diese Parameter zugreifen. Mehr zur Auswertung von Übergabewerten an ein Applet finden Sie etwas weiter unten auf Seite 368. Der Start Die Methode public void start() wird beim Start des Applets direkt nach der Initialisierung aufgerufen. Während die Initialisierung nur einmal erfolgt, kann (und wird normalerweise) der Start beliebig oft wiederholt werden. Beispielsweise, wenn das Applet gestoppt wurde oder das Applet durch scrollen des HTML-Dokuments erneut in den sichtbaren Bereich des Browsers geholt wird. Um das Startverhalten Ihres Applets zu spezifizieren, überschreiben Sie die start()-Methode. Dort können diverse Funktionalitäten stehen, beispielsweise das Starten eines Threads oder der Aufruf einer anderen Methode. Das Stoppen eines Applets Das Stoppen eines Applets hört sich schlimmer an, als es wirklich ist. Die Methode public void stop() wird automatisch aufgerufen, sobald Sie die HTML-Seite, auf der das Applet läuft, verlassen. Ein gestopptes Applet ist jedoch weiter voll funktionsfähig und wird bei der Rückkehr auf die Seite automatisch durch die Methode start() wieder zu neuem Leben erweckt. Erinnern Sie sich an den Appletviewer? In dem Menü finden Sie die Startund Stop-Funktionalitäten wieder. Das Zerstören eines Applets Wenn ein Applet-Container beendet wird, ermöglicht die Methode public void destroy() dem Applet, vernünftig hinter sich aufzuräumen und die letzten Systemressourcen, die es belegt hatte, freizugeben. Unter solche Aufräumarbeiten fallen die Beendigung von laufenden Threads oder die Freigabe von anderen laufenden Objekten. Normalerweise müssen Sie die destroy()-Methoden nicht überschreiben, sofern Sie nicht explizit Ressourcen freigeben müssen.
Java 2 Kompendium
365
Kapitel 7
Grundlagen der Applet-Erstellung Noch einmal zur Erinnerung: Die allgemeinere finalize()-Methode ist hier nicht zu verwenden, da diese eher für ein einzelnes Objekt irgendeines Typs zum Aufräumen verwendet wird und vor allem, weil im Gegensatz zu der finalize()-Methode die destroy()-Methode immer beim Beenden eines Browsers oder beim Neuladen eines Applets automatisch ausgeführt wird.
7.4.2
Ein Musterapplet als Schablone
Nachfolgend sehen Sie ein Musterapplet, das als Gerüst für sämtliche Applets verwendet werden kann. Es besteht im Wesentlichen aus dem Import einiger wichtiger Java-Pakete, die für das Applet von Bedeutung sein können. Diese sind in der Schablone auskommentiert und müssen bei Bedarf aktiviert werden. Wenn Sie weitere Pakete benötigen, können Sie diese selbstverständlich hinzufügen. Sie können sogar alle denkbaren Pakete importieren (ob Sie diese dann brauchen oder nicht), denn das Java-Importieren ist ja kein Einbeziehen der Pakete in den Source wie mit include in C. Daher werden Sie keinen grö ß eren Sourcecode bekommen und auch sonst keine Verluste erleiden. Es könnte nur sein, dass Ihr Quellcode an Übersichtlichkeit verliert. Der Klassenname in der Top-level-Klassen-Deklaration des Applets muss natürlich durch die korrekte Bezeichnung ersetzt werden. Listing 7.11: Eine Schablone für ein Applet
366
// Name der Klasse // Beschreibung: // Import der Pakete // import java.applet.*; // import java.awt.*; // import java.awt.image.*; // import java.awt.peer.*; // import java.io.*; // import java.lang.*; // import java.net.*; // import java.util.* // Top-level-Klassen-Deklaration des Applets. public [Klassenname] extends java.applet.Applet { // Variablen-Deklaration // Bauen Sie hier Ihre eigenen Methoden ein // Die Methoden, die überschrieben werden public void init() { } public void start() { } public void stop() { } public void destroy() { } // Optional, aber sehr wahrscheinlich –
Java 2 Kompendium
Die Applet-Klasse // // // // // // }
Kapitel 7
die Ausgabemethode public void paint(Graphics g) { } Bei Multithreading die run()-Methode verwenden. public void run() { }
7.5
Die Applet-Klasse
Wir haben jetzt gesehen, dass ein Applet von der Applet-Klasse abgeleitet wird. Die Applet-Klasse selbst wiederum erbt von einer ganzen Reihe anderer Klassen. Eine Klasse, die java.applet.Applet erweitert, kann Variablen und Methoden (sowie Konstruktoren) aus java.lang.Object, java.awt.Component, java.awt.Container und java.awt.Panel verwenden. Schauen wir uns die Applet-Klasse noch ein bisschen genauer an. Die Methoden der Applet-Klasse ermöglichen Applets diverse Grundfunktionen. Sie erlauben Applets, Parameter zu handhaben, Grafik zu handhaben, Bilder zu importieren, Sound-Clips zu importieren und abzuspielen, mit dem Browser oder dem Appletviewer zu interagieren oder Lebenszyklen des Applets zu verwalten. Wir wollen uns ein paar wichtige Methoden ansehen, die diese Vorgänge unterstützen. Dabei muss als Hinweis vorausgeschickt werden, dass diese Vorstellung nur ein Schlaglicht auf die Möglichkeiten eines Applets werfen wird. Insbesondere werden nicht sämtliche Varianten der angesprochenen Methoden aufgeführt, sondern jeweils eine sinnvolle Version ausgewählt. Selbst ein einfaches Applet besteht also in der Regel aus mehr als nur den genannten vier Grundmethoden. Da sind zum einen die selbst definierten Methoden, die z.B. in der start()-Methode aufgerufen werden können. Es gibt jedoch noch ein paar Standardmethoden, die so wichtig sind, dass wir sie hier behandeln wollen. Einige davon kennen Sie bereits. Wir haben sie in unserem ersten Applet verwendet.
Java 2 Kompendium
367
Kapitel 7
Grundlagen der Applet-Erstellung
7.5.1
Ausgabe in einem Applet mit der paint()-Methode
Erinnern Sie sich an die Ausgabe in unserem ersten Applet? Wir haben dazu nicht die println()-Methode, sondern eine Ausgabe innerhalb der public void paint(Graphics g)-Methode verwendet. Wir haben unseren Text gezeichnet und nicht geschrieben. Zeichnen bestimmt im Allgemeinen, wie ein Applet etwas auf dem Bildschirm ausgibt. Dies kann beliebig oft während eines Applet-Lebens vorkommen. Um einem Applet das Zeichnen zu ermöglichen, müssen sie diese paint()-Methode überschreiben. Diese steht jedem Applet defaultmäß ig zur Verfügung und wird automatisch aufgerufen, wenn es notwendig ist (d.h. beim Starten des Applets, wenn das Applet zum Teil verdeckt wurde und wieder ein grö ß erer Bereich angezeigt wird usw.). Die paint()-Methode wird automatisch aufgerufen, wenn Sie das Applet (erneut) starten.
Es wird Ihnen sicher auffallen, dass die paint()-Methode einen Parameter hat: eine Instanz der Klasse java.awt.Graphics. Deshalb müssen Sie sicherstellen, dass die Graphics-Klasse in Ihren Applet-Code importiert wird, oder den Parameter vollqualifiziert angegeben. Die Grafik-Ausgabe ist ein so wichtiges Thema, dass wir es hier abbrechen und in einem eigenen Kapitel gründlich durchsprechen wollen. Nichtsdestotrotz wird in den nachfolgenden Beispielen die paint()-Methode verwendet werden. Auch auf eigenständigen Applikationen mit grafischer Oberfläche steht die paint()-Methode zur Verfügung.
7.5.2
Übergabewerte für ein Applet
Über die getParameter()-Methode können Sie auf jedes Argument zugreifen, das unter HTML in der Applet-Referenz mit dem name-Wert spezifiziert wurde. Dieser Wert von name ist eine Zeichenkette, die in der getParameter()-Methode in den Klammern angeben werden muss. Die Rückgabe der getParameter()-Methode ist der mit value in der HTML-Datei spezifizierte Wert. Beispiel: String parameter1 = getParameter("htmlParameterName")
Falls Sie einen Parameter spezifizieren, der in der HTML-Datei nicht angegeben wurde, gibt die getParameter()-Methode null zurück. Dies sollte man immer mit einer Abfrage auswerten und einen Standardwert einstellen. 368
Java 2 Kompendium
Die Applet-Klasse
Kapitel 7
Beispiel: if (parameter1 == null) parameter1 = "Standardwert1"
Der im HTML-Parameter-Tag angegebene Name und der in der getParameter()-Methode spezifizierte Name müssen absolut identisch sein, auch in Bezug auf Groß- und Kleinschreibung. Da die Rückgabe der getParameter()-Methode immer eine Zeichenkette ist, müssen alle Übergabeparameter eines Java-Applets in Form von Zeichenketten übergeben und dann im Source ebenso behandelt werden. Wenn Sie die Parameter in einem anderen Datentyp verwenden wollen, müssen Sie diese konvertieren. Sie können allerdings (wie im letzten Kapitel besprochen) nicht per Casting vorgehen, sondern müssen die Instanzen der als Ersatz dafür vorhandenen Wrapper-Klassen verwenden. Diese sind für alle primitiven Datentypen vorhanden. Wenn Sie z.B. einen Parameter in einen long-Wert umwandeln wollen, kann das so erfolgen: long l = new Long(stringParameter).longValue()
Wie ein Applet-Code Standard-Parameter aus einem HTML-Dokument erhält, haben wir also damit gesehen. Wie aber stellt das Applet Informationen über benötigte Parameter bereit? Was von einem Anwender des Applets verlangt wird, ist ja, dass er die Parameter in der HTML-Datei richtig angibt, also den Namen der Übergabewerte, den jeweiligen Datentyp und eine Beschreibung. Diese Informationen kann ein Applet in einer Methode dokumentieren, die ein Applet-Client dann beispielsweise auswerten kann. Es handelt sich um die Methode public String[][] getParameterInfo().
Diese gibt ein Zeichenketten-Array wieder, wo alle Parameter aufgelistet werden können, die das Applet benötigt. Diese Zeichenkette spezifiziert für jeden Parameter den Namen, den Typ und eine Beschreibung. Beispiel (mit zwei Parametern): public String[][] getParameterInfo() { String[][] pinfo = { {"MeinPara1", "String", "Stringübergabewert"}, {"MeinPara2", "int", "int-Übergabewert"}, }; return pinfo; }
Listing 7.12: Beispiel für eine überschriebene getParameterInfo()Methode
In den gleichen Zusammenhang fällt die Methode public String getAppletInfo().
Java 2 Kompendium
369
Kapitel 7
Grundlagen der Applet-Erstellung Mit dieser Methode kann man Angaben über das Applet machen, etwa den Autor, die Version, das Erstellungsdatum usw. Auch diese Methode kann in einem Applet-Client abgefragt werden. Direkt mit dem Applet-Client kommuniziert die Methode public void showStatus(String msg).
Diese schreibt einen Text in die Statuszeile des Browsers. Allerdings kollidiert diese Methode in einigen Situationen mit Standardausgaben vom Browser und vor allem dem Appletviewer und ist deshalb oft nicht sehr effektiv. Wenden wir diese Methoden nun in einem Beispiel an. Zuerst wird eine HTML-Datei mit Übergabe-Parametern erstellt. Listing 7.13: Die HTML-Datei mit der Applet-Referenz und Parametern
<APPLET CODEBASE = "." CODE = "UebergabeApplet.class" NAME = "TestApplet" WIDTH = 400 HEIGHT = 300 HSPACE = 0 VSPACE = 0 ALIGN = middle>
An das Applet wird ein String übergeben, der dann im Applet ausgegeben werden soll. Der zweite Übergabewert ist ein int-Wert, der intern im Applet zum Setzen der Hintergrundfarbe verwendet wird. Die paint()-Methode wird zur Ausgabe verwendet. Beachten Sie, dass wir in dem Beispiel bewusst das Schlüsselwort this einsetzen. An einigen Stellen wird es aus Demonstrationsgründen eingesetzt, um in der gleichen Situation an anderer Stelle darauf zu verzichten. Listing 7.14: Das Applet mit der Auswertung von Übergabewerten plus der Bereitstellung von Informationen gegenüber Applet-Clients
370
import java.awt.*; import java.applet.*; public class UebergabeApplet extends Applet { String meinP1; int meinP2; // Laden der Parameterwerte // Mit Test, ob der Parameter auch vorhanden ist public String getParameter(String key, String def) { return getParameter(key) != null
Java 2 Kompendium
Die Applet-Klasse
Kapitel 7 Abbildung 7.9: Das Applet – in der Statuszeile ist die Reaktion auf die Methode showStatus() zu sehen.
? getParameter(key) : def; } //Initialisieren des Applets public void init() { meinP1 = this.getParameter("MeinPara1", "Vorbelegung"); meinP2 = Integer.parseInt(getParameter("MeinPara2", "2")); // Setze Hintergrundfarbe switch (meinP2) { case 1: this.setBackground(Color.cyan); break; case 2: this.setBackground(Color.red); break; case 3: this.setBackground(Color.yellow); break; default: this.setBackground(Color.white); } // Setze die Groesse des Applets this.setSize(new Dimension(400,300));
Java 2 Kompendium
371
Kapitel 7
Grundlagen der Applet-Erstellung } public void paint(Graphics g) { g.drawString(meinP1,50,50); g.drawString("Farbwahl war: " + new Integer(meinP2).toString(),50,150); showStatus("Das Applet wurde gestartet"); } //Die Applet-Information public String getAppletInfo() { return "Die Applet Information: (C) RJS, Version 1.0, 11/2000 "; } // Parameterinfo public String[][] getParameterInfo() { String[][] pinfo = { {"MeinPara1", "String", "Text, der ausgeben werden soll"}, {"MeinPara2", "int", "Farbwahl"}, }; return pinfo; } }
Im Appletviewer kann man über das Menü auf einfache Weise die bereitgestellten Informationen des Applets abgreifen. Abbildung 7.10: Im Appletviewer kann man auf die Methoden getAppletInfo() und getParameterInfo() zugreifen.
372
Java 2 Kompendium
Die Applet-Klasse
Kapitel 7 Abbildung 7.11: Die Infos, die das Applet bereitstellt, im Appletviewer
7.5.3
Bilder importieren
Selbstverständlich können Applets Bilder importieren und ausgeben. Dazu können die folgenden Methoden genutzt werden: getImage() drawImage()
Die Methode public Image getImage(URL url, String name) lädt BildDateien in ein Applet. Beispiel: bild1 = getImage(getCodeBase(), "bild.gif");
Das Beispiel lädt das Bild bild.gif. In unserem Beispiel verwenden wir die Methode getCodeBase() als Argument für die Ermittelung des URLs, d.h. wir laden das Bild von der gleichen Stelle, wo sich auch die HTML-Datei befindet (gleich noch mehr dazu). Als Bild können Sie jedes der unter Java erlaubten Formate (etwa GIF oder JPEG) verwenden. Das Thema Bildverarbeitung unter Java ist natürlich mit diesem kurzen Beispiel nicht erschöpft, sondern wird im Rahmen des Kapitels über Grafik und Animation ausführlich erläutert. Dort finden Sie dann auch nähere Erklärungen zu der Methode. Hier soll nur die prinzipielle Möglichkeit eines Applets zum Laden eines Bildes angesprochen werden. Mit der Methode public abstract boolean drawImage(Image img, int x, int y, ImageObserver observer) können Sie ein geladenes Bild anzeigen. Beispiel: g.drawImage(meinBild, 50, 50, this);
Wir wollen ein kleines Applet erstellen, das ein Bild lädt und auf dem Bildschirm wieder ausgibt. Die folgende HTML-Datei dient zum Einbinden.
Java 2 Kompendium
373
Kapitel 7 Listing 7.15: Die HTML-Datei
Grundlagen der Applet-Erstellung <APPLET CODEBASE = "." CODE = "DrawImage.class" WIDTH = 400 HEIGHT = 300>
Das Applet sieht so aus: Listing 7.16: Ein Applet mit Bild-Ausgabe
import java.awt.Image; import java.awt.Graphics; public class DrawImage extends java.applet.Applet { Image samImage; public void init() { // ein Bild wird geladen samImage = getImage(getDocumentBase(), "images/baby.jpg"); resize(320, 240); // Grö ß e des Applets } public void paint(Graphics g) { // Ausgabe des Bildes g.drawImage(samImage, 0, 0, this); } }
Abbildung 7.12: Ein Bild in einem Applet
Das Beispiel setzt natürlich voraus, dass im dem Unterverzeichnis images die entsprechende Grafik vorhanden ist. In diesem Fall wird in der init()Methode die Grafik geladen und dann die Applet-Grö ß e angepaßt. Nach der Initialisierung des Applets wird als nächstes die paint()-Methode aufgerufen. Diese verwendet unsere hier besprochene Methode dann zum Anzeigen des aktuellen Bildes. Mehr dazu finden Sie beim Abschnitt über Grafiken.
374
Java 2 Kompendium
Die Applet-Klasse
7.5.4
Kapitel 7
Importieren und Abspielen von Sound
Multimedia-Eigenschaften waren ja am Anfang ein Grund für den einschlagenden Erfolg von Java beim ersten Auftauchen im Internet3. Dazu zählt natürlich ebenfalls die Akustik. Ab der Java-Version 1.2 können Sie MIDIDateien (Typ 0 und Typ 1) sowie RMF-, WAVE-, AIFF-, und AU-Dateien in hoher Tonqualität abspielen und zusammenmischen. Ein Applet kann über die Methode public AudioClip getAudioClip(URL url, String name) sowohl Audio-Clips als auch Sound-Clips importieren. Beispiel: meinSoundClip = getAudioClip(getCodeBase(), "history.wav");
Die Methoden public void play(URL url, String name) und public abstract void loop() dienen zum Abspielen und Wiederholen des Clips.
7.5.5
Die Interaktion des Applets mit der Umgebung und den Lebenszyklus eines Applets verwalten
Jedes Applet kann mit seiner Umgebung in Kontakt treten und auf sie reagieren. Dies ergibt sich in einfacher Form schon allein deshalb, weil es eine Ableitung der Klasse Applet ist. Die einfachsten (und grundlegendsten) Funktionalitäten eines Applets sind, dass es sich starten lässt und – hier kommt die Interaktion zur Laufzeit ins Spiel – vernünftig beenden lässt (es reagiert auf Schließbefehle des Fensters). Java besitzt dazu einen Mechanismus, durch den Applets in Schlüsselmomenten, in denen bestimmte Ereignisse eintreten, die Kontrolle übernehmen können. Die schon behandelten Methoden init(), start(), stop() und destroy() werden entsprechend aufgerufen, wenn das Applet geladen wird, mit der Ausführung beginnt, mit der Ausführung stoppt und zerstört wird. Wenn ein Applet zu einem dieser Schlüsselmomente etwas ausführen soll, dann müssen Sie nur die entsprechende Methode überschreiben, indem Sie Ihre eigene Implementierung davon in das Applet einbauen. Interaktionen gehen jedoch selbstverständlich noch weiter. Ein etwas komplizierteres Applet wird auf Buttons, Mausaktionen im Allgemeinen, Menübefehle und/oder Tastatureingaben reagieren. Dies ist sicher nicht überraschend. So etwas nennt man ein Programm mit einer (sinnvollen) Anwenderschnittstelle. Java stellt Ihnen Methoden zur Verfügung, die das System bei bestimmten Ereignissen aufrufen kann. Wenn Sie irgendeine Aufgabe als Reaktion auf ein Ereignis erledigen wollen, müssen Sie nur die entsprechende Methode überschreiben. 3
Mittlerweile ist das exzessive Multimedia-Bombardement per HTML-Grafiken, JavaScript, Java oder ActiveX leider nur noch als Anti-Kriterium für das Laden von Webseiten zu sehen.
Java 2 Kompendium
375
Kapitel 7
Grundlagen der Applet-Erstellung Interaktionen betreffen jedoch auch die Plattform, unter der das Applet läuft (Zugriffe auf Ressourcen, soweit dies erlaubt ist), sowie Kontakte über Netzwerkverbindungen hinweg. Interaktion ist also ein recht weit gefasster Begriff. Schauen wir uns ein paar Methoden an, mit denen ein Applet auf seine Umwelt reagiert und darauf Einfluss nimmt. Die Methode getAppletContext() Die Methode public AppletContext getAppletContext() gibt ein Objekt vom Typ AppletContext (eine Schnittstelle) zurück, das Sie dazu verwenden können, Informationen und Kontrolle über die Umgebung des Applets zu erlangen. AppletContext-Methoden ermöglichen es Ihnen, herauszufinden, welche Applets außer dem aktuellen noch auf derselben Seite laufen, Videos und Sounds zu importieren, eine andere Webseite aus dem Applet heraus zu laden, den Kontext eines anderen Applets zu importieren und dessen Methoden aufzurufen oder Meldungen an andere Applets weiterzugeben. Konkret haben Sie über ein Objekt dieses Typs folgende Methoden zur Verfügung: public public public public public public public
AudioClip getAudioClip(URL url) Image getImage(URL url) Applet getApplet(String name) Enumeration getApplets() void showDocument(URL url) void showDocument(URL url, String target) void showStatus(String status)
Schauen wir uns davon einige in der Praxis an. Das folgende Beispiel lädt aus dem Applet heraus über einen AppletContext direkt eine neue Webseite. Dazu verwenden wir die showDocument()-Methode. Beachten Sie, dass wir dieser ein URL-Objekt übergeben müssen, das wir anonym innerhalb der Parameterklammern erzeugen. Dabei müssen wir eine Ausnahmebehandlung vornehmen. Auf die näheren Details dazu gehen wir noch ein. In dem Beispiel wird die Webseite Applet2.html geladen. Listing 7.17: Laden einer neuen Webseite aus einem Applet heraus
376
import import import public
java.awt.*; java.applet.*; java.net.*; class Applet1 extends Applet {
Java 2 Kompendium
Die Applet-Klasse
Kapitel 7
public void init() { try { getAppletContext().showDocument(new URL(getDocumentBase(),"Applet2.html")); } catch(Exception e) { System.out.println(e.toString()); } } }
Das nächste Beispiel gibt Informationen über andere in der Webseite geladenen Applets aus. In der nachfolgenden Webseite sind zwei Applets geladen. <APPLET CODEBASE = "." CODE = "UebergabeApplet.class" NAME = "TestApplet" WIDTH = 400 HEIGHT = 300 HSPACE = 0 VSPACE = 0 ALIGN = middle> <APPLET CODEBASE = "." CODE = "Applet2.class" WIDTH = 500 HEIGHT = 200>
Listing 7.18: Eine Webseite mit zwei Applets
Das zweite Applet greift mittels der Methode getApplets() und dort via nextElement() auf das erste Applet zu und gibt Informationen darüber aus. import java.awt.*; import java.applet.*; public class Applet2 extends Applet { public void paint(Graphics g) { g.drawString(getAppletContext().getApplets(). nextElement().toString(),50,100); } }
Listing 7.19: Zugriff auf Interna des NachbarApplets in der Webseite
Die Methoden getCodeBase() und getDocumentBase() Schon an verschiedenen Stellen in diesem Kapitel ist die Methode public URL getCodeBase() verwendet worden. Damit wird der4 URL des Applets selbst zurückgegeben. Die Methode public URL getDocumentBase() funktio4
»Der« URL oder »die« URL ist wieder so ein Fachbegriff, wo sich Duden und Fachleute streiten. Ich verwende gegen die Empfehlung des Duden »der«.
Java 2 Kompendium
377
Kapitel 7
Grundlagen der Applet-Erstellung niert ähnlich, nur wird der URL des Verzeichnisses mit dem HTML-Dokument zurückgegeben. Die Methode isActive() Die Methode public boolean isActive() stellt fest, ob ein Applet aktiv ist. Sie können diese Methode zum Überprüfen des Zustands eines anderen Applets mit dem Aufruf von AppletContext.getApplets() im Kontext und durch den Aufruf von isActive() für ein bestimmtes Applet benutzen. Die Methode setStub() Sie können die Methode public final void setStub(AppletStub stub) verwenden, um eine neue AppletStub-Schnittstelle zu erstellen. Eine mögliche Verwendung ist die Implementation eines neuen Appletviewers. Normalerweise wird der AppletStub automatisch durch das System gesetzt. Wir verfolgen die Methode nicht weiter. Die Methode resize() Diese Methode werden Sie in vielen Applets entdecken. Damit kann man die Grö ß e eines Fensters und im Prinzip auch jedes Applets festlegen. Die Methode public void resize(Dimension d) bzw. public void resize(int x, int y) legt die Grö ß e eines Fenster bzw. des Anzeigebereichs des Applets fest. Dabei ist zu beachten, dass die äußeren Grö ß enangaben der HTMLDatei die angezeigte Grö ß e eines Applets beeinflussen, d.h., in einem Browser gelten die dort festgelegten Grö ß enangaben. Diese Methode kann nicht bewirken, dass ein über HTML-Grö ß enangaben festgelegtes Applet in einer Webseite innerhalb eines Browsers redimensioniert wird. Der Appletviewer hingegen reagiert dementsprechend mit einer Anpassung der Grö ß e. Sinn macht die Methode aber vor allem, wenn ein eigenständiges Folgefenster aktiviert wird.
7.5.6
Methoden zur Ereignisbehandlung in Applets
Natürlich kann ein Applet auch auf Ereignisse wie Mausbewegungen oder Tastatureingaben reagieren. Wir wollen dieses Verhalten noch im Rahmen eines Abschnittes über das prinzipielle Handling von Ereignissen in Java ausführlicher behandeln. Wir werden dort sehen, wie Java konkret auf die nachfolgenden Aktionen reagiert. Hier interessieren uns nur die Möglichkeiten und die Anwendung in einfachen Beispielen. Die meisten der nachfolgenden Methoden werden von Sun als deprecated bezeichnet. Wie aber schon verschiedentlich besprochen, müssen Sie sich bei Applets in vielen Fällen auf Java 1.0.x beschränken, was insbesondere die Verwendung des Eventmodells 1.0 erzwingt. Dieses unterscheidet sich erheblich von dem mittlerweile verwendeten Eventmodell 1.1. Die nachfol-
378
Java 2 Kompendium
Die Applet-Klasse
Kapitel 7
genden Methoden gehören allesamt zum Eventmodell 1.0. Wir werden im Abschnitt über die beiden Eventmodelle darauf noch näher eingehen und die beiden Modelle gegenüberstellen. Mausaktionen Zu den wichtigsten Interaktionen eines Applets (und natürlich eines gewöhnlichen Programms) zählt die Reaktion auf Mausereignisse. Dabei soll hier nicht die Auswertung von Button und Menüeinträgen im Blickpunkt stehen, sondern die Reaktion auf Mausereignisse, die sich irgendwo im Bereich des Applets abspielen. Obwohl es nicht zwingend ist, beschränken wir uns auf die übliche linke Maustaste. Es gibt sechs mögliche Mausereignisse: Die Maustaste wird gedrückt. Die gedrückte Maustaste wird wieder losgelassen. Die Maus wird mit gedrückter Maustaste bewegt (Drag). Die Maus wird mit losgelassener Maustaste bewegt. Der Mauszeiger verlässt den Bereich des Applets. Der Mauszeiger kommt (wieder) in den Bereich des Applets. Für jedes Ereignis stellt Java in dem alten Eventmodell eine Methode zur Verfügung, die zur Applet-Klasse gehört. Über die Methode public boolean mouseDown(Event evt, int x, int y)
können Sie Aktionen auslösen, wenn der Benutzer eine Maustaste drückt. Dabei ist Event die Ereignisklasse, die die Informationen über das Ereignis enthält und x und y sind die Koordinaten, bei denen die Maustaste gedrückt wurde. Das Gegenstück zu der Methode mouseDown() ist die Methode public boolean mouseUp(Event event, int x, int y),
über die Sie Aktionen auslösen können, wenn der Benutzer eine Maustaste losgelassen hat. Die mouseUp()-Methode verwendet die gleichen Argumente wie mouseDown().
Java 2 Kompendium
379
Kapitel 7
Grundlagen der Applet-Erstellung Die Methode public boolean mouseEnter(Event event, int x, int y)
wird immer dann aufgerufen, wenn der Mauszeiger in den Bereich des Applets bewegt wird. Die Maustaste muss nicht extra gedrückt werden, damit die Methode aufgerufen wird. Die Methode verwendet die gleichen Argumente wie die bereits beschriebenen. Die Methode public boolean mouseExit(Event event, int x, int y)
ist das Gegenstück zu mouseEnter() und wird immer dann aufgerufen, wenn der Mauspfeil aus den Bereich des Applets bewegt wird. Die Maustaste muss nicht extra gedrückt werden und es gelten wieder die üblichen Argumente. Die Methode public boolean mouseDrag(Event event, int x, int y)
wird immer dann aufgerufen, wenn die Maus mit gedrückter Taste im Bereich des Applets bewegt wird und die Methode public boolean mouseMove(Event event, int x, int y)
wird immer dann aufgerufen, wenn die Maus bewegt wird und keine Taste gedrückt ist. Es gelten wieder die üblichen Argumente. Jedes Mal, wenn die Maustaste gedrückt wird, werden vor dem Aufrufen von mouseDown() die Methoden mouseExit() und mouseEnter() aufgerufen. Das bedeutet, dass die Methode mouseEnter() keinen Code enthalten sollte, der bei der Aktion mouseDown() schaden kann. Wir wollen zum Ende des Kapitels ein etwas komplexeres Applet zusammenbauen, das einige der bisher erarbeiteten Funktionalitäten nutzt. Es soll auf Mausaktionen reagieren, die getInfo()- und getCodeBase()-Methoden unterstützen und auf Wunsch ausgeben, Übergabeparameter verwenden, auf Arrays zugreifen, ein Bild laden und dieses dann ausgeben. Der Source wird mit Kommentaren dokumentiert, sodass er sicher verständlich bleibt. Listing 7.20: Ein Applet, das auf verschiedene Mausaktionen reagiert
380
import java.applet.*; import java.awt.*; public class AppletReaktionen extends Applet { /* Variablen zur Aufnahme von Übergabeparameter 1 – 3 */ private String m_Para1 = "";
Java 2 Kompendium
Die Applet-Klasse
Kapitel 7
private String m_Para2 = ""; private String m_Para3 = ""; /* Die Parameternamen in der HTML-Datei. */ private final String PARAM_Para1 = "Para1"; private final String PARAM_Para2 = "Para2"; private final String PARAM_Para3 = "Para3"; /* Eine Image-Variable*/ Image samImage; /* Eine Instanz von Graphics, die für diverse Ausgabeaktionen genutzt wird. */ private Graphics ausgabe; public String getAppletInfo() { // Die Info return "Name: AppletReaktionen " + "Autor: Ralph Steyer"; } // Die Parameter-Info public String[][] getParameterInfo() { String[][] info = { { PARAM_Para1, "String", "Parameterbeschreibung für unseren ersten Übergabeparameter" }, { PARAM_Para2, "String", "Parameterbeschreibung Nummer 2" }, { PARAM_Para3, "String", "Hier kommt ein dritter Übergabeparameter" }, }; return info; } // die Initialisierung public void init() { // lokale Testvariable zum Auswerten der // Übergabeparameter String param; // Test des 1. Übergabeparameters // Wenn der Übergabeparameter vorhanden ist, // Zuweisung des 1. Übergabeparametes in die // entsprechende Variable param = getParameter(PARAM_Para1); if (param != null) m_Para1 = param; // Test des 2. Übergabeparameters // Wenn der Übergabeparameter vorhanden ist, // Zuweisung des 2. Übergabeparametes in die // entsprechende Variable param = getParameter(PARAM_Para2); if (param != null) m_Para2 = param; // Test der 3. Übergabeparameters – wie oben param = getParameter(PARAM_Para3); if (param != null) m_Para3 = param; // Laden eines Bildes samImage = getImage(getDocumentBase(), "images/ba2.jpg"); // Initialisierung der Graphic-Instanz ausgabe = getGraphics(); } public void paint(Graphics g) { /* Automatische Ausgabe des ersten Übergabeparameters */ g.drawString(m_Para1, 10, 20); /* Zeige das geladene Bild an */ Java 2 Kompendium
381
Kapitel 7
Grundlagen der Applet-Erstellung g.drawImage(samImage, 20, 50, this); } public boolean mouseDown(Event evt, int x, int y) { // Wir verwenden hier einen der Übergabeparameter // an das Applet. // Hier wird der Übergabeparameter 3 rechts oben // im Appletbereich angezeigt, wenn der Mauszeiger // gedrückt wird. ausgabe.drawString(m_Para3, 300, 10); return true; } public boolean mouseUp(Event evt, int x, int y) { // Die Parameterinfo wird eingeholt und damit ein // 2-dimensionales Zeichen-Array gefüllt. String [][] parainfo = getParameterInfo(); // Wenn der Mauszeiger gedrückt und dann wieder // losgelassen wird, wird die Appletinfo // angezeigt. ausgabe.drawString(getAppletInfo(), 5, 180); // Zusätzlich wird ein Teil der Parameterinfo // angezeigt. Es erfolgt der Zugriff auf das Feld // 3,3 ausgabe.drawString(parainfo[2][2], 5, 200); return true; } public boolean mouseDrag(Event evt, int x, int y) { // Zeige das geladene Bild an anderer Stelle // an, wenn die Maus gezogen wird. ausgabe.drawImage(samImage, 300, 20, this); return true; } public boolean mouseMove(Event evt, int x, int y) { return true; } public boolean mouseEnter(Event evt, int x, int y) { // Hier werden alle in den anderen Mausaktionen // ausgegebener Zeichenketten wieder gelöscht, // wenn der Mauszeiger wieder in den Appletbereich // kommt. ausgabe.clearRect(0, 0, 500, 500); return true; } public boolean mouseExit(Event evt, int x, int y) { // Wir verwenden hier einen der Übergabeparameter // an das Applet. // Hier wird der Übergabeparameter 2 links unten // im Appletbereich angezeigt, wenn der Mauszeiger // den Appletbereich verlässt. ausgabe.drawString(m_Para2, 20, 230); return true; } }
382
Java 2 Kompendium
Die Applet-Klasse
Kapitel 7 Abbildung 7.13: Das Applet nach dem Start
Abbildung 7.14: Das Applet in voller Pracht. Einige der Mausaktionen wurden ausgelöst.
Java 2 Kompendium
383
Kapitel 7
Grundlagen der Applet-Erstellung Die HTML-Datei zum Einbinden sieht so aus:
Listing 7.21: Die HTML-Datei mit den ÜbergabeParametern
<APPLET CODE = "AppletReaktionen.class" WIDTH = 500 HEIGHT = 500> <param name=Para1 value="Parameter Nummer 1"> <param name=Para2 value="Hier kommt der zweite"> <param name=Para3 value="Alle guten Dinge sind 3">
Tastaturereignisse Mit Sicherheit zählen auch Tastaturereignisse zu den Grundereignissen der Interaktion zwischen einem Applet (und erst recht einer eigenständigen Applikation) und seiner Umwelt. Jedesmal, wenn ein Anwender eine Taste auf der Tastatur drückt, kann diese ein Ereignis auslösen, das das Applet auswerten kann. Dabei unterscheidet man zwischen verschiedenen Typen von Tastaturereignissen. Irgendeine Taste wird gedrückt. Die genaue Taste spielt keine Rolle. Eine bestimmte Taste wird gedrückt und ausgewertet. Eine so genannte Ergänzungstaste wird gedrückt. Dennoch haben Tastaturereignisse sicher nicht die Bedeutung wie Mausereignisse, denn das WWW arbeitet ja möglichst selten mit der Tastatur. Die Methode public boolean keyDown(Event event, int key)
wird immer dann aufgerufen, wenn eine Taste gedrückt wird. Die durch die Methode keyDown() erfassten Tastenanschläge sind Ganzzahlen und werden über den Parameter key an die Methode übergeben. Die Ganzzahlen stellen den ASCII-Wert des gedrückten Zeichens dar. Es können sämtliche Tastenanschläge ausgewertet werden, die einen ASCII-Code besitzen. Um die Ganzzahlen aus dem key-Parameter als Zeichen verwenden zu können, müssen sie per Casting in Zeichen umgewandelt werden. Die Syntax dafür ist beispielsweise einZeichen = (char)key;
384
Java 2 Kompendium
Die Applet-Klasse
Kapitel 7
Wenn Sie den Parameter key nicht auswerten wollen, heißt dies, dass eine beliebige Taste gedrückt werden kann und alleine darauf hin eine Aktion erfolgt. Wir wollen uns wieder ein einfaches Beispiel zu Gemüte führen, das über die keyDown()-Methode Tastenanschläge auswertet und im Anzeigebereich des Applets ausgibt. import java.applet.*; import java.awt.Event; import java.awt.*; public class AppletTaste extends Applet { static char taste; public void paint(Graphics g) { g.drawString(String.valueOf(taste), 50, 30); } public boolean keyDown(Event evt, int key) { taste=(char)key; repaint(); return true; } }
Listing 7.22: Das Applet wertet Tastatureingaben aus
Beachten Sie im Beispiel die Methode repaint(). Damit wird nach jeder Tastatureingabe die paint()-Methode aufgerufen. Mehr dazu bei der Behandlung der Grafikmöglichkeiten von Java. Aufgerufen wird das Applet mit <APPLET CODE = "AppletTaste.class" WIDTH = 500 HEIGHT = 500>
Listing 7.23: Die HTML-Datei zum Einbinden
Das Beispiel wird unter Umständen im Appletviewer des JDK 1.2 nicht laufen. Es gibt zwar keinerlei Fehlermeldung, aber die Anzeige der gedrückten Tasten unterbleibt seltsamerweise. Im Navigator oder Explorer gibt es keine Schwierigkeiten. Auch nicht in anderen Versionen des Appletviewers (auch die des JDK 1.3 ist okay). Standardtasten der Event-Klasse Die Event-Klasse stellt einige Klassenvariablen zur Verfügung, die einige nicht alphanumerische Standardtasten repräsentieren. Dies sind z.B. die Pfeiltasten. Diese Klassenvariablen können Sie anstelle der numerischen ASCII-Werte in der keyDown()-Methode verwenden. Damit wird eine bessere Übersichtlichkeit des Sourcecodes gewährleistet.
Java 2 Kompendium
385
Kapitel 7
Grundlagen der Applet-Erstellung Folgende Standardvariablen stehen zur Verfügung:
Tabelle 7.4: Standardtasten der Event-Klasse
Taste
Klassenvariable
Funktionstasten F1 bis F12
Event.F1 – Event.F12
Pos1
Event.home
Ende
Event.end
Bild nach oben
Event.pgup
Bild nach unten
Event.pgdn
Pfeil nach oben
Event.up
Pfeil nach unten
Event.down
Pfeil nach links
Event.left
Pfeil nach rechts
Event.right
Die Ergänzungstasten Es gibt drei wichtige Tasten, welchen kein ASCII-Code direkt zugeordnet ist, die aber für sich so wichtig sind, dass sie dennoch ausgewertet werden müssen. Es handelt sich um: die Shift-Taste die STRG oder Control-Taste die Meta-Taste Das besondere an diesen drei Tasten ist, dass sie keine Tastenereignisse auslösen, die mit der keyDown()-Methode ausgewertet werden können. In einigen Fällen ist es klar, dass die Shift-Taste gedrückt wurde, nämlich immer dann, wenn ein Großbuchstabe oder ein sonstiges oberes Zeichen der Tastatur als ASCII-Wert interpretiert werden kann. Dies ist jedoch eine implizite Vorgehensweise. Es gibt indes noch eine andere Strategie. Wenn ein gewöhnliches Tastaturoder Mausereignis eintritt, kann über zusätzliche Methoden getestet werden, ob eine der drei Tasten zusätzlich gedrückt wurde. Die Event-Klasse enthält drei Methoden, die testen, ob zusätzlich zu den gewöhnlichen Tastatur- oder Mausereignissen eine der drei Zusatztasten gedrückt wurden. Es handelt sich um die shiftDown()-, die metaDown()- und die controlDown()-Methode. 386
Java 2 Kompendium
Multithreading bei Applets
Kapitel 7
Alle drei Methoden geben boolesche Werte zurück. Sie können diese Methoden in jeder beliebigen Ereignishandlung (Tastatur- oder Mausereignis) verwenden, indem Sie sie zusätzlich zu dem Ereignis-Objekt, das an die jeweilige Methode weitergegeben wurde, aufrufen. Ein kurzes Beispiel für jede Ergänzungsmethode soll die Technik verdeutlichen: public boolean keyDown(Event event, int key) { if (event.shiftDown) ... irgendwelche Anweisungen ... else ... irgendwelche Anweisungen ... } public boolean mouseDrag(Event event, int x, int y) { if (event.metaDown) ... irgendwelche Anweisungen ... else ... irgendwelche Anweisungen ... } public boolean mouseDown(Event evt, int x, int y) { if (event.contolDown) ... irgendwelche Anweisungen ... else ... irgendwelche Anweisungen ... }
Listing 7.24: Skizzierter Einsatz der Ergänzungstasten
Die Standardmethoden zur Handhabung von Ereignissen in Applets gehören eigentlich zum AWT-Event-Handler. Mehr dazu erörtern wir im Kapitel über das AWT und das prinzipielle Eventhandling von Java.
7.6
Multithreading bei Applets
Kommen wir nun zum ersten Mal zu dem Begriff Threads innerhalb von Applets. Das Thema Multithreading in Java soll ebenfalls in einem eigenen Kapitel behandelt werden, aber an dieser Stelle soll zumindest noch die Bedeutung der run()-Methode erklärt werden. Java unterstützt Multithreading. Die genauere Erklärung von Multithreading finden Sie in Kapitel 8. Multithreading bedeutet für uns erst einmal nur, dass mehrere Aufgaben oder Prozesse quasi gleichzeitig ausgeführt werden können. Durch die Verwendung von Threads können Sie Applets so erstellen, dass diverse Prozesse in ihrem eigenen Thread laufen, ohne andere Teile des Systems zu beeinflussen. Zwar werden eventuelle Engpässe in der Kapazität des Rechners die diversen Threads unter Umständen verlangsamen, aber sie sind und bleiben unabhängig voneinander.
Java 2 Kompendium
387
Kapitel 7
Grundlagen der Applet-Erstellung Wie wird nun ein Applet Multithreading-fähig? Sie müssen im Wesentlichen vier Schritte durchführen. 1.
Erweitern Sie die Unterschrift des Applets um implements Runnable (Sie integrieren also diese Schnittstelle).
2.
Fügen Sie eine Instanzvariable ein, die den Thread des Applets enthält.
3.
Reduzieren Sie die start()-Methode so, dass sie außer dem Start des Threads (bzw. ggf. dem Erzeugen eines Thread-Objekts) keinen weiteren Aufruf enthält.
4.
Jetzt kommt eine neue Methode ins Spiel – die run()-Methode, die ab sofort den eigentlichen Code enthält, den das Applet ausführen soll. Sie ersetzt damit die start()-Methode weitgehend.
Schauen wir uns die Schritte anhand eines Beispiels an. Listing 7.25: Ein MultithreadingApplet
388
/* Datumsausgabe mit Threads*/ import java.awt.Graphics; import java.awt.Font; import java.util.Date; public class DatumThread extends java.applet.Applet implements Runnable { Font schriftArt = new Font("TimesRoman",Font.BOLD,24); Date Datum1, Datum2; Thread MeinThread1, MeinThread2; public void start() { if (MeinThread1 == null); { MeinThread1 = new Thread(this); MeinThread1.start(); } if (MeinThread2 == null); { MeinThread2 = new Thread(this); MeinThread2.start(); } } public void stop() { if (MeinThread1 != null) { MeinThread1.stop(); MeinThread1 = null; } if (MeinThread2 != null) { MeinThread2.stop(); MeinThread2 = null; } } public void run() { while (true) { try { Datum1 = new Date(); MeinThread1.sleep(2017);
Java 2 Kompendium
Multithreading bei Applets
Kapitel 7
repaint(); } catch (InterruptedException e) { } try { Datum2 = new Date(); MeinThread2.sleep(5029); repaint(); } catch (InterruptedException e) { } } } public void paint(Graphics g) { g.setFont(schriftArt); g.drawString(Datum1.toString(),15,30); g.drawString(Datum2.toString(),15,80); } }
Wenn Sie nun das Applet mit einer einfachen HTML-Seite einbinden, können wir sehen, was es tut. <APPLET CODE = "DatumThread.class" WIDTH = 500 HEIGHT = 500>
Listing 7.26: Die HTML-Seite für das MultithreadingApplet
Das Applet gibt zweimal Datum und Uhrzeit aus. Die Threading-Eigenschaft äußert sich darin, dass die beiden Zeiten nicht identisch sind und asynchron in verschieden langen Intervallen aktualisiert werden. Das liegt daran, dass nach Erzeugung eines jeweiligen Datumsobjekts der gerade aktive Thread »schlafen geht« und deshalb bis zur Erzeugung des zweiten Datumsobjekts ein gewisses Intervall vergeht. Zudem ist der Aufruf der repaint()-Methode entsprechend damit gekoppelt, weshalb die Anzeige immer nur eine der beiden Datumsausgaben aktualisieren kann. Abbildung 7.15: Wer außer Buchautoren arbeitet schon sonntagabends so spät?
Java 2 Kompendium
389
Kapitel 7
Grundlagen der Applet-Erstellung Analysieren wir das Applet ein wenig: public class DatumThread extends java.applet.Applet implements Runnable
ist unser Schritt 1 – Implementieren der Schnittstelle Runnable. Thread MeinThread1, meinThread2;
ist der Schritt 2, das Einfügen der Instanzvariablen, die die Threads des Applets enthalten. Schritt 3 ist die start()-Methode, die eine Abprüfung auf den Thread enthält und nur wenn kein Thread vorhanden ist, einen Thread startet. Sonst tut die start()-Methode nichts. Die eigentliche Aktivität des Applets muss – wie wir in Schritt 4 festgelegt haben – innerhalb der run()-Methode stattfinden. Genau das passiert hier: Bis zum Auslösen der stop()-Methode wird eine Endlosschleife ausgeführt, die mittels der repaint()-Methode (zu dieser gleich mehr) immer wieder eine Ausgabe der aktualisierten Zeit auf dem Bildschirm erzeugt. Zu einer Behandlung von potenziellen Ausnahmen werden einige Anweisungen in einem try-catch-Block platziert.
7.7
Zusammenfassung
Applets benötigen immer einen Container, in dem sie zum Leben erweckt werden. In diesen werden sie als eine Referenz in einer HTML-Seiten geladen. Ein Java-Applet in der einfachsten Form (ohne irgendwelche Parameter und optionale Attribute) kann mit folgender HTML-Syntax in eine HTMLSeite eingebunden werden: <APPLET CODE="[classelement]" WIDTH=[Wert] HEIGHT=[Wert]>
Daneben gibt es noch diverse optionale Angaben (etwa Positionsangaben, Quelltextverweise, Parameterübergabe). Sie können ein Applet auch mit anderen HTML-Tags in eine Webseite einbinden, die aber komplizierter zu handhaben sind und durch ihre Einschränkungen auf spezielle Voraussetzungen das <APPLET>-Tag noch nicht abgelöst haben. Um ein Applet zu erstellen, müssen Sie immer eine Subklasse der Klasse Applet erzeugen. Diese Klasse beinhaltet sämtliche Eigenschaften, die die Kooperation mit dem Container sicherstellen, die Fähigkeiten des AWT für Oberflächenprogrammierung nutzbar machen und auch die lebenswichtigen Grundmethoden für ein Applet. 390
Java 2 Kompendium
Zusammenfassung
Kapitel 7
Der Einstieg in ein Applet-Leben ist die Initialisierung. Diese erfolgt immer mit der Methode init(), die automatisch aufgerufen wird, sobald das Applet in den Browser (oder einen anderen Container wie den Appletviewer) geladen wird. Die Methode start() startet das eigentliche Applet direkt nach der Initialisierung. Während die Initialisierung nur einmal erfolgt, kann (und wird normalerweise) der Start beliebig oft wiederholt werden. Die Methode stop() stoppt ein Applet, das dann mit start() wieder zu neuem Leben erweckt werden kann. Engültig zerstört wird ein Applet mit der Methode destroy(). Neben diesen Grundmethoden gibt es noch eine Vielzahl von Methoden, um die Funktion eines Applets sinnvoll zu erweitern.
Java 2 Kompendium
391
8
Multithreading
Multithreading bedeutet im Wesentlichen, dass mehrere Aufgaben oder Prozesse quasi gleichzeitig ausgeführt werden können. Einfachstes (und bestes) Beispiel für ein Multithreading-System ist ein Mensch, der viele Dinge gleichzeitig erledigt. Bei Computersystemen werden natürliche Dinge wie Multithreading zu einem großen Problem und viele ältere Betriebssysteme wie DOS waren nie in der Lage, Multithreading auch nur ansatzweise auszuführen. Auch Windows 3.x unterstützte nicht einmal echtes Multitasking, geschweige denn Multithreading. Neben den Betriebssystemen haben ebenso die Konzepte bisheriger Programmiersprachen den MultithreadingVorgang gar nicht oder nur sehr eingeschränkt integriert. Der Vorgang des Multithreading wird durch die oft verwendete Übersetzung von Threads mit »Aufgaben« ungenau beschrieben und verschleiert den Unterschied zu Multitasking, was ja streng genommen genauso bedeutet, dass mehrere Aufgaben (Tasks) gleichzeitig ausgeführt werden können. Ein Thread ist im Grunde statt mit »Aufgabe« besser (wie es auch im Wörterbuch steht) mit »Faden«, »Faser« oder am besten »Zusammenhang« zu übersetzen. Nicht die quasi gleichzeitige Ausführung von mehreren Programmen wie beim Multitasking, sondern die gleichzeitige, parallele Ausführung von mehreren einzelnen Programmschritten (oder zusammenhängenden Prozessen) ist echtes Multithreading. Dies kann ebenso bedeuten, dass innerhalb eines Programms mehrere Dinge gleichzeitig geschehen, mehrere Fäden/Fasern eines Programms synchron verfolgt und abgearbeitet werden. Bei Java ist das Multithreadingkonzept voll integrierter Bestandteil der Philosophie. Der hochentwickelte Befehlssatz in Java, um Threads zu erstellen und bei Bedarf zu synchronisieren, ist in die Sprache integriert, macht diese stabil und einfach in der Anwendung. Java verwendet hochintelligente Synchronisationsanweisung für den Umgang mit Multithreading. Die Schlüsselworte synchronized und threadsafe werden zum Markieren von Blöcken und Methoden benutzt, die eventuell vor gleichzeitiger Verwendung geschützt werden sollen.
Java 2 Kompendium
393
Kapitel 8
Multithreading Grundsätzlich sollte man das Verfahren von Multithreading nicht überschätzen. Gerade auf Singleprozessor-Rechnern und/oder Betriebssystemen, die kein echtes Multithreading unterstützen, bedeutet die Technik nur, dass Prozesse abwechselnd die so genannte Zeitscheibe des Prozessors bekommen. Echte Gleichzeitigkeit ist damit nicht möglich.
8.1
Klassen Thread-fähig machen
Es gibt zwei Wege, wie Sie Teile Ihrer Anwendungenen und Klassen in separaten Threads laufen lassen können. Einmal, indem Sie die Thread-Klasse erweitern oder indem Sie die Schnittstelle Runnable implementieren. Beide finden Sie im Paket java.lang. Damit stehen sowohl die Klasse als auch die Schnittstelle überall zur Verfügung.
8.1.1
Die Erweiterung der Klasse Thread
Sie können eine Klasse als Thread lauffähig machen, indem Sie die Klasse java.lang.Thread erweitern. Dies gibt Ihnen direkten Zugriff auf alle Thread-Methoden. Beispiel: public class MeineThreadKlasse extends Thread
In der als Thread lauffähig gemachten Klasse müssen Sie die Methode public void run()
überschreiben. Aus der so vorbereiteten Klasse erzeugen Sie dann mit einem der zahlreichen Konstruktoren (Thread(), Thread(Runnable target), Thread(Runnable target, String name), Thread(String name), Thread(ThreadGroup group, Runnable target), Thread(ThreadGroup group, Runnable target, String name), Thread(ThreadGroup group, String name)) ein
Thread-Objekt. Wir wenden verschiedene der Konstruktoren im Laufe des Kapitels an. Start von Threads Wenn Sie dann aus dieser Klasse ein Objekt erzeugen, können Sie den Thread über diese Objektrepräsentation und die Methode start() laufen lassen1. Die Methode wirft die Ausnahme vom Typ IllegalThreadStateException aus, wenn der Thread bereits läuft. 1
394
Das gilt auch für die später noch besprochene Implementation von Runnable.
Java 2 Kompendium
Klassen Thread-fähig machen
Kapitel 8
Die Methode run() sollte nie direkt aufgerufen werden. In diesem Fall wird das als normaler Methodenaufruf interpretiert und nicht als Start eines Threads. Schauen wir uns ein Beispiel an. class ErsterThread extends Thread { long erg; public void run() { while(true) { System.out.println(erg++); } } } class ZweiterThread extends Thread { long erg; public void run() { while(true) { System.out.println(erg--); } } } public class ThreadTest1 { public static void main(String args[]) { ErsterThread p = new ErsterThread(); ZweiterThread q = new ZweiterThread(); p.start(); q.start(); } }
Listing 8.1: Eine Applikation mit zwei Threads
Das Programm lässt sich nur durch die Tastenkombination STRG+C in der DOS-Box abbrechen In dem Beispiel arbeiten wir mit zwei Thread-fähig gemachten Klassen, die beide die run()-Methode überschreiben. Dort läuft jeweils eine Endlosschleife. Eine zählt eine Variable hoch und gibt sie aus, die andere zählt die Variable herunter. In der eigentlichen Applikation erzeugen wir jeweils ein Objekt und starten dann die beiden Threads. Beachten Sie, dass damit die main()-Methode beendet ist und ein nicht Multithreading-fähiges Programm unmittelbar beendet werden würde. Insbesondere würde es aber auch gar nicht zum Aufruf von q.start() kommen, denn die erste Endlosschleife würde das verhindern. So aber werden sie sehen, dass beide Threads gestartet werden und die Ausgabe zwischen positiven und negativen Zahlen hinund herwechselt (je nachdem, welcher Thread gerade die Ausgabe erzeugt). Threads abbrechen Ein Thread wird beendet, wenn das Ende seiner run()-Methode erreicht ist oder das umgebende Programm endet. Es gibt aber auch die Möglichkeit, einen Thread über einen Stopaufruf an sein Objekt zu beenden oder zumindest zu unterbrechen. Die mittlerweile
Java 2 Kompendium
395
Kapitel 8
Multithreading als deprecated geltende Methode stop() kann das tun (wenngleich mit nicht vorhersagbarem Ausführungszeitpunkt). Testen wir die Methode.
Listing 8.2: Stoppen des ersten Threads mit stop()
class DritterThread extends Thread { long erg; public void run() { while(true) { System.out.println(erg++); } } } class VierterThread extends Thread { DritterThread p = new DritterThread(); public void run() { p.start(); while(true) { if(p.erg>100) { p.stop(); break; } } } } public class ThreadTest2 { public static void main(String args[]) { VierterThread q = new VierterThread(); q.start(); } }
Das Beispiel unterscheidet sich nur auf den ersten Blick nicht sonderlich von dem ersten. Sie sind aber grundverschieden. Zwar arbeiten wir auch hier mit zwei Thread-fähigen Klassen, aber nur die erste beinhaltet eine Endlosschleife ohne interne Abbruchmöglichkeit. In der zweiten Klasse wird der Thread der ersten Klasse erzeugt und im Rahmen der run()-Methode gestartet (nicht im Hauptprogramm). Die zweite Thread-Klasse hat die Aufgabe, in einem eigenen, parallel laufenden Thread zu überwachen, wann der erste Thread abgebrochen werden soll. Im Rahmen der eigentlichen Applikation wird dann aus dieser zweiten Klasse ein Thread-Objekt erzeugt und nur dieses gestartet. Der erste Thread zählt wieder eine Variable hoch, der zweite Thread durchläuft permanent eine if-Schleife, in der kontrolliert wird, ob dieser Wert grö ß er als 100 ist. In diesem Fall wird die stop()-Methode für den ersten (!) Thread ausgelöst und mit einem break die Endlosschleife des zweiten Threads beendet. Damit endet auch automatisch seine run()Methode. Das Verfahren funktioniert soweit zuverlässig, nur wird Ihnen bei einem Test auffallen, dass die Zählvariable des ersten Threads beim Abbruch bedeutend grö ß er als 100 sein wird und vor allem, dass der Wert bei jedem Test verschieden sein wird. Wie bereits angedeutet, hat man auf diese Art und Weise keine genaue Kontrolle über den Zeitpunkt des Abbruchs. Außerdem ist unter gewissen Umständen die gesamte Technik mit der stop()-Methode unsicher. Die Methode gilt deshalb als deprecated. Dennoch – für zahlreiche Anwendungen ist diese einfache Technik immer noch sinnvoll.
396
Java 2 Kompendium
Klassen Thread-fähig machen
Kapitel 8
Es gibt aber mittlerweile eine bessere Variante, die wir in dem nachfolgenden, umgebauten Beispiel einsetzen wollen. Sie basiert auf den folgenden drei Methoden: interrupt() interrupted() isInterrupted()
Diese Technik basiert auf einem Unterbrechungsstatus, der gesetzt werden kann und dann in Form von Botschaften an einen Thread weitergereicht wird. Dieser nimmt den Status, wenn der Thread wieder vom Prozessor die Zeitscheibe bekommt, unmittelbar zur Kenntnis und man kann entsprechende Aktionen auslösen. Die Methode public void interrupt()
unterbricht den Thread, auf den sie angewendet wird. Über die Methode public static boolean interrupted()
kann man testen, ob ein aktueller Thread unterbrochen wurde (falls ja, Rückgabe true). Der Aufruf der Methode löscht den Interrupted-Status von dem Thread, sodass ein zweiter Aufruf false zurückliefern wird (es sei denn, der Thread ist zwischenzeitlich erneut unterbrochen worden). Die Methode public boolean isInterrupted()
testet ebenso, ob ein Thread unterbrochen wurde, nur wird der InterruptedStatus bei einem Aufruf unverändert gelassen. Bei allen drei Methoden sollte man beachten, dass man damit nur mit der Unterbrechung eines Threads agiert, ihn aber noch nicht explizit beendet. Darum muss man sich noch selbst kümmern. Der Unterbrechungsstatus ermöglicht jedoch auf einfache Weise, eine Abbruchanweisung im unterbrochenen Thread auszulösen, etwa wie in dem folgenden Beispiel. class FuenfterThread extends Thread { long erg; public void run() { while(true) { System.out.println(erg++); if(isInterrupted()) { System.out.println("Erster Thread unterbrochen"); break; } } } } class SechsterThread extends Thread { FuenfterThread p = new FuenfterThread();
Java 2 Kompendium
Listing 8.3: Unterbrechung von Threads
397
Kapitel 8
Multithreading public void run() { p.start(); while(true) { if(p.erg>100) { p.interrupt(); break; } } } } public class ThreadTest3 { public static void main(String args[]) { SechsterThread q = new SechsterThread(); q.start(); } }
Zwei mittlerweile als deprecated erklärte Methoden sind suspend() und resume(). Diese dienten ursprünglich dazu, einen Thread anzuhalten und wieder zu starten. suspend() versetzt einen Thread in Wartezustand, die Methode resume() kehrt zu dem schlafenden Thread zurück und aktiviert ihn wieder. Sie sollten nicht mehr verwendet werden, da damit leicht ein Deadlock erzeugt werden kann. Von Interesse ist in diesem Zusammenhang die Methode public final boolean isAlive(), womit getestet werden kann, ob ein Thread beendet wurde. Falls er noch nicht beendet wurde, wird true zurückgegeben. Eine weitere Methode zur Änderung des Laufstatus von Threads ist public void destroy(). Im Allgemeinen sollten Sie destroy() jedoch nicht anwenden, denn damit werden keine Säuberungen am Thread durchgeführt, sondern er wird lediglich zerstört. Threads schlafen legen Eine sehr interessante Methode ist sleep(), die es in zwei Versionen gibt: static void sleep(long millis) static void sleep(long millis, int nanos)
Damit kann man einen Thread für ein in Millisekunden und optional Nanosekunden angegebenes Zeitintervall »schlafen legen«. Diese Zeitangaben sind aber (gerade auf einem Singleprozessorsystem) nur sehr ungenau und können von diversen Faktoren abhängen (Betriebssystem, Hardware, parallel laufende Programme usw.). Die Methode ist eine Klassenmethode und kann deshalb auch ohne konkretes Thread-Objekt angewandt werden. Dies kann dazu verwendet werden, das Hauptprogramm (was ja selbst auch als eigener Thread zu verstehen ist) selbst eine gewisse Zeit pausieren zu lassen. Testen wir die Methode.
398
Java 2 Kompendium
Klassen Thread-fähig machen import java.util.*; class SiebterThread extends Thread { int a; public void run() { while(a<20) { Date d = new Date(); System.out.println(d.toString()); a++; try { sleep(5000); } catch( InterruptedException e) { System.out.println(e.getMessage()); } } } } public class ThreadTest4 { public static void main(String args[]) { SiebterThread q = new SiebterThread(); q.start(); } }
Kapitel 8 Listing 8.4: Nach jeder Ausgabe geht das System ca. 5 Sekunden schlafen
Abbildung 8.1: Alle 5 Sekunden arbeitet der Thread
Die Methode wirft eine Ausnahme vom Typ InterruptedException aus und muss entsprechend in einen passenden try-catch-Block eingeschlossen werden. In einem ähnlichen Zusammenhang wie sleep() ist die join()-Methode (public final void join() throws InterruptedException) zu sehen. Es gibt aber eine zusätzliche Funktionalität. Damit können Sie gezielt eine angegebene Zeitspanne auf die Beendigung eines Threads warten. So etwas ist immer dann sinnvoll, wenn es zeitaufwändige Operationen gibt, die vor dem Aufruf einer Folgeoperation noch beendet werden müssen. Dieser zeitaufwändigen Operation wird sinnvollerweise ein eigener Thread zugeordnet. Die join()-Methode lässt sich auch als Alternative zu der sleep()-
Java 2 Kompendium
399
Kapitel 8
Multithreading Methode verwenden und unterstützt wie diese eine Wartezeit in Millisekunden oder Millisekunden und Nanosekunden: public final synchronized void join(long millis) throws InterruptedException public final synchronized void join(long millis, int nanos) throws InterruptedException
Java stellt auch diverse Methoden bereit, mit denen man beispielsweise die Anzahl der laufenden Threads ermitteln (public static int activeCount()) oder deren Namen abfragen (public final String getName()) bzw. setzen (public final void setName(String name)) kann. Um Namen für Threads zu setzen, gibt es die Möglichkeit, diese im Konstuktor der Thread-Klasse als String anzugeben. Falls der Konstruktor mit leeren Klammern verwendet wird, wird ihm standardmäß ig ein Standardname Thread-x zugewiesen, wobei x eine eindeutige Nummer für diesen Thread ist und ab 0 indiziert wird. Ganz wichtig ist auch die Methode zum Abfragen des derzeit ausgeführten Threads: public static Thread currentThread()
Schauen wir uns die Methoden in einem praktischen Beispiel an. Listing 8.5: Vier Threads plus der Thread und diverse Methoden zur Bestimmung der Situation
400
class ThreadKlasse extends Thread { long erg; public void run() { while(true) { erg++; System.out.println(getName()); System.out.println(activeCount()); System.out.println(currentThread()); } } } public class VerschiedeneThreadMethoden { public static void main(String args[]) { ThreadKlasse p = new ThreadKlasse(); ThreadKlasse q = new ThreadKlasse(); ThreadKlasse r = new ThreadKlasse(); ThreadKlasse s = new ThreadKlasse(); s.setName("Mein Thread, du lauefst so stille"); p.start(); q.start(); r.start(); s.start(); }}
Java 2 Kompendium
Klassen Thread-fähig machen
Kapitel 8
In dem Beispiel setzen wir explizit den Namen eines Threads. Innerhalb der run()-Methode geben wir die Namen des gerade aktiven Threads, die Anzahl aller laufenden Threads (immer konstant 5 in dem Beispiel – das Hauptprogramm zählt mit) und das gerade aktive Thread-Objekt aus. Abbildung 8.2: Die Ausgabe gibt diverse Informationen über Anzahl, Name und Aktivität der Threads.
Die Priorität verändern Threads bekommen vom System bestimmte Prioritäten zugewiesen, was gleichbedeutend mit der zur Verfügung gestellten Prozessorzeit ist. Die Defaultpriorität (5) kann verändert und damit festgelegt werden, dass die höher priorisierten Threads schneller abgearbeitet werden. Dazu steht die Methode public final void setPriority(int newPriority) throws IllegalArgumentException zur Verfügung. Diese Methode erlaubt es, die Priorität für einen Thread mit Werten zwischen 1 (niedrig) und 10 (hoch) festzulgen. Andere Angaben lösen die Ausnahme IllegalArgumentException aus. Die Partnermethode public final int getPriority() wird benutzt, um die aktuelle Priorität eines Threads zu ermitteln. class ErsterThread extends Thread { int erg; public void run() { System.out.println("Prioritaet von Thread 1: " + getPriority()); while(erg < 100) { System.out.print(erg++ + ", "); } } } class ZweiterThread extends Thread { int erg; public void run() { System.out.println("Prioritaet von Thread 2: " + getPriority());
Java 2 Kompendium
Listing 8.6: Abfrage der (Default-)Priorität
401
Kapitel 8
Multithreading while(erg < 100) { System.out.print(200 + erg++ + ", "); } } } public class ThreadPrio1 { public static void main(String args[]) { ErsterThread p = new ErsterThread(); ZweiterThread q = new ZweiterThread(); p.start(); q.start(); } }
Das Beispiel zeigt, das beide Threads die Defaultpriorität 5 haben. Durch die schnell zu erledigende Aufgabe wird erst der erste Thread vollständig abgearbeitet und dann der zweite. Abbildung 8.3: Bei gleicher (Default-)Priorität wird zuerst der erste und dann der zweite Thread abgearbeitet.
Wenn das Beispiel geringfügig verändert wird, kann man die Reihenfolge der Abarbeitung beeinflussen. Listing 8.7: Die Priorität von Thread 1 wird reduziert.
402
class ErsterThread_a extends Thread { int erg; public void run() { System.out.println( "Anfangs-Prioritaet von Thread 1: " + getPriority()); setPriority(4); System.out.println( "Neue Prioritaet von Thread 1: " + getPriority()); while(erg < 100) { System.out.print(erg++ + ", "); } } } class ZweiterThread extends Thread { int erg;
Java 2 Kompendium
Klassen Thread-fähig machen
Kapitel 8
public void run() { System.out.println("Prioritaet von Thread 2: " + getPriority()); while(erg < 100) { System.out.print(200 + erg++ + ", "); } } } public class ThreadPrio2 { public static void main(String args[]) { ErsterThread_a p = new ErsterThread_a(); ZweiterThread q = new ZweiterThread(); p.start(); q.start(); } }
Die Reduzierung der Priorität von Thead 1 führt dazu, dass unmittelbar nach dessen Start die Zeitscheibe zu Thread 2 weitergegeben wird, dieser abgearbeitet und erst dann mit Thread 1 weiter gemacht wird. Das äußert sich auch darin, dass die zweite Ausgabe der Priorität von Thread 1 erst nach der Abarbeitung von Thread 2 erfolgt. Abbildung 8.4: Thread 2 hat eine höhere Priorität und wird zuerst beendet.
Die Variante mit der Erweiterung der Klasse Thread blockiert den Weg der Vererbung und ist deshalb in einigen Situationen nur bedingt sinnvoll. Für Applets scheidet sie sowieso aus, denn diese müssen ja von java.applet.Applet abgeleitet werden. Deshalb gibt es einen zweiten, in diesem Fall sinnvolleren Weg – die Implementation von Runnable.
8.1.2
Implementation von Runnable
Wenn Sie eine Klasse in die Lage versetzen, Threads laufen zu lassen, werden Sie unter Umständen auch die Fähigkeiten einiger anderer Klassen erweitern wollen. Da Java keine Mehrfachvererbung unterstützt, können Sie für die Multithreadingfähigkeit das Alternativkonzept der Schnittstellen verwenden. Sie implementieren einfach die Schnittstelle Runnable. TatsächJava 2 Kompendium
403
Kapitel 8
Multithreading lich implementiert sogar die Klasse Thread selbst Runnable. Die RunnableSchnittstelle besitzt nur eine Methode: run(). Diese Schnittstelle kennen wir bereits aus unserem Multithreading-Applet im letzten Kapitel. Jedesmal wenn Sie in eine Klasse Runnable implementieren, werden Sie die run()-Methode in Ihrer Klasse überschreiben (müssen). Es ist dann die Methode, die all diejenige Arbeit erledigt, die Sie von einem oder mehreren Thread(s) getan haben wollen. Beispiel: public class DatumThread extends java.applet.Applet implements Runnable
Grundsätzlich unterscheidet sich dabei die Vorgehensweise zwischen eigenständigen Applikationen und Applets nicht allzu sehr. Um Applets Multithreading-fähig machen, ist hier nicht viel zu beachten, was nicht auch für beliebige Klassen gilt. Wir haben im vorherigen Kapitel über die Grundlagen der Applet-Erstellung bereits gesehen, dass ein Applet im Wesentlichen über vier Schritte Multithreading-fähig gemacht wird. Das übertragen wir nun auf den allgemeinen Fall: Erweitern der Unterschrift der Klasse um implements Runnable. Hinzufügen einer Instanzvariablen, der der Thread zugewiesen wird. Bei einem Applet, das selbst die Schnittstelle implementiert, wird die start()-Methode so reduziert, dass sie außer dem Start des Threads und einer ggf. notwendigen Erzeugung eines Thread-Objekts keinen weiteren Aufruf enthält. Wenn eine eigenständige Applikation die Schnittstelle implementiert, gilt das Gleiche für die main()-Methode. Hinzufügen der run()-Methode, die den eigentlichen Code enthält, welchen der Thread ausführen soll. Die Variante, bei der ein Applet die Schnittstelle Runnable direkt implementiert, haben wir bereits im letzten Kapitel durchgespielt. Hier folgt eine Möglichkeit, wie ein Applet diese Schnittstelle nicht direkt implementiert, sondern eine externe Klasse verwendet, die diese implementiert (wobei diese auch die Klasse Thread erweitern könnte). Listing 8.8: Verwendung einer Multithreadingfähig gemachten Klasse in einem Applet
404
import java.awt.*; import java.applet.*; import java.util.*; class ThreadEins implements Runnable { Date d; public void run() { while(true) {
Java 2 Kompendium
Klassen Thread-fähig machen
Kapitel 8
d = new Date(); System.out.println(d); } } } public class AppletThread1 extends Applet { Thread q; public void start() { ThreadEins p = new ThreadEins(); q = new Thread(p); q.start(); } }
Das Beispiel erzeugt eine Ausgabe in der DOS-Box (die jeweils aktuelle Zeit samt Datum), sobald die HTML-Seite, in der das Applet integriert ist, mit dem Appletviewer aufgerufen wird. Beachten Sie, dass der Thread-Kontruktor ein Objekt der Appletklasse übergeben bekommt. Erstellen wir nun noch ein kleines Beispiel, in dem eine vollständige Applikation die Schnittstelle Runnable direkt in der Hauptklasse implementiert. public class ThreadTest5 implements Runnable { int a; public void run() { while(a<100) { System.out.print(a + ", "); a++; } } public static void main(String args[]) { ThreadTest5 a = new ThreadTest5(); Thread q = new Thread(a); q.start(); } }
Listing 8.9: Eine Applikation mit main() und run()
Beachten Sie auch bei diesem Beispiel, dass das Objekt, das aus der Klasse selbst erzeugt wurde, als Parameter an den Konstruktor der Klasse Thread() übergeben wird. Im Allgemeinen ist es bei eigenständigen Applikationen sinnvoller, wenn jeder Thread in einer eigenen Klasse definiert wird und diese dann die Schnittstelle Runnable implementiert oder von der Klasse Thread abgeleitet wird. Das ist übersichtlicher. In der Applikation wird dann ein Objekt dieser Klasse erzeugt und der Thread nur gestartet. Bei Applets ist die direkte Implementation der Schnittstelle Runnable meist die glücklichere Wahl. Mehr Beispiele dazu sollen in Verbindung mit dem nachfolgenden Thema erfolgen. Es handelt sich um die Gruppierung von Threads.
Java 2 Kompendium
405
Kapitel 8
Multithreading
8.2
Thread-Gruppen
Threads können in so genannte Thread-Gruppen eingeteilt werden, die im Wesentlichen eine Sicherheitsstrategie für Threads festlegen. Sie können damit vermeiden, dass ein Thread andere Threads ruhig stellt oder deren Reihenfolge ändert. Thread-Gruppen werden hierarchisch so angeordnet, dass jede Thread-Gruppe eine Ursprungsgruppe (parent group) hat. Ein Thread darf nur Threads innerhalb seiner eigenen Thread-Gruppe oder einer seiner Untergruppen verändern. Über die Klasse ThreadGroup, die zu dem Paket java.lang gehört, können Sie die Gruppierung von Threads vornehmen. So ermöglicht es eine Gruppierung auch, verschiedene Operationen auf mehrere Threads gemeinsam anzuwenden.
8.2.1
Eine Thread-Gruppe erstellen
Sie können eine Thread-Gruppe ganz einfach über den Konstruktor ThreadGroup(string groupName)
erstellen. Sie können eine Thread-Gruppe ebenso als Untergruppe einer bereits existierenden Thread-Gruppe erstellen: public ThreadGroup(ThreadGroup existingGroup, String groupName) throws NullPointerException
Viele der ThreadGroup-Operationen sind, solange sinnvoll, mit normalen Thread-Operationen identisch, außer, dass sie für alle Threads ihrer Gruppe gültig sind. Die maximale Priorität eines jeden Threads einer Gruppe können Sie durch das Anwenden der Methode public final synchronized void setMaxPriority(int priority)
auf die Gruppe beschränken und mit public final int getMaxPriority()
abfragen. Die übergeordnete Gruppe einer Thread-Gruppe können Sie mit public final ThreadGroup getParent()
406
Java 2 Kompendium
Thread-Gruppen
Kapitel 8
herausfinden. Dabei wird neben dem Namen auch die maximal erlaubte Priorität der übergeordneten Gruppe angegeben. Die unterschiedlichen enumerate()-Methoden ermöglichen es Ihnen herauszufinden, welche Threads und Thread-Gruppen zu einer bestimmten Thread-Gruppe gehören: public int enumerate(Thread[] threadListe) public int enumerate(Thread[] threadListe, boolean rekursivEinstellung) public int enumerate(ThreadGroup[] groupListe) public int enumerate(ThreadGroup[] groupListe, boolean rekursivEinstellung)
Der Parameter rekursivEinstellung der enumerate()-Methoden sorgt dafür, dass die enumerate()-Methode bei dem Wert true all seine Untergruppen durchforstet, um eine komplette Liste seiner Nachkommen zu erhalten. Die Anzahl der aktiven Threads und Thread-Gruppen einer Gruppe erhalten Sie durch die Verwendung von public synchronized int activeCount()
und public synchronized int activeGroupCount().
8.2.2
Hinzufügen eines Threads zu einer Thread-Gruppe
Wenn Sie einen Thread einer Thread-Gruppe hinzufügen wollen, müssen Sie diese bei seiner Erstellung mit einem alternativen Konstruktor angeben: public Thread(ThreadGroup group, String name) public Thread(ThreadGroup group, Runnable target, String name)
Über den optionalen Parameter name können Sie einen Thread-Namen vergeben, der zum Unterscheiden der einzelnen Threads verwendet werden kann. Wenn der Thread nicht benannt wird, kann der Thread-Name null angegeben werden. Das Attribut target im zweiten Konstruktor gibt das Objekt an, dessen run()-Methode beim Start des Threads aufgerufen werden soll. In einigen Situationen ist das notwendig. Testen wir Thread-Gruppen in einem Beispiel. public class ThreadGruppe extends Thread { int erg=1; public void run() { while(erg<10000) {
Java 2 Kompendium
Listing 8.10: Thread-Gruppen und verschiedene Operationen darauf
407
Kapitel 8
Multithreading erg++; } } public static void main(String args[]) { ThreadGroup grp1 = new ThreadGroup("Haupt"); ThreadGroup grp2 = new ThreadGroup(grp1, "Neben1"); ThreadGroup grp3 = new ThreadGroup(grp1, "Neben2"); ThreadGruppe a = new ThreadGruppe(); Thread p = new Thread(grp1,a,"Eins"); Thread q = new Thread(grp1,a,"Zwei"); Thread r = new Thread(grp2,a,"Drei"); Thread s = new Thread(grp2,a,"Vier"); Thread t = new Thread(grp3,a,"Fuenf"); Thread u = new Thread(grp3,a,"Sechs"); Thread v = new Thread(grp3,a,"Sieben"); grp1.setMaxPriority(9); grp2.setMaxPriority(6); grp3.setMaxPriority(4); System.out.println( "Uebergeordnete Gruppe grp1: "+ grp1.getParent()); System.out.println( "Uebergeordnete Gruppe grp2: " + grp2.getParent()); System.out.println( "Uebergeordnete Gruppe grp3: " + grp3.getParent()); System.out.println("Max Prioritaet grp1: " + grp1.getMaxPriority()); System.out.println("Max Prioritaet grp2: " + grp2.getMaxPriority()); System.out.println("Max Prioritaet grp3: " + grp3.getMaxPriority()); System.out.println("Aktive Threads grp1: " + grp1.activeCount()); System.out.println("Aktive Threadgruppen grp1: " + grp1.activeGroupCount()); System.out.println("Aktive Threads grp2: " + grp2.activeCount()); System.out.println("Aktive Threadgruppen grp2: " + grp2.activeGroupCount()); System.out.println("Aktive Threads grp3: " + grp3.activeCount()); System.out.println("Aktive Threadgruppen grp3: " + grp3.activeGroupCount()); p.start(); q.start(); r.start(); s.start();
408
Java 2 Kompendium
Dämonen
Kapitel 8
t.start(); u.start(); v.start(); } }
Das Beispiel benutzt eine einzige Klasse, die Thread-fähig ist. Diese tut nicht viel. Es wird nur eine Variable hoch gezählt. Aus dieser Klasse werden sieben Threads erstellt, die verschiedenen Thread-Gruppen zugeordnet werden. Es gibt eine Hauptgruppe, die wiederum zwei Untergruppen beinhaltet. Für die Thread-Gruppen werden maximal erlaubte Prioritäten festgelegt. Abbildung 8.5: Threads zu Gruppen zusammengefasst
8.3
Dämonen
Vielleicht werden Sie sich fragen, was Java mit einem übernatürlichen Wesen bzw. einer übernatürlichen Macht – nicht notwendigerweise böse – zu tun hat. Nun, Java-Threads können einem von zwei Typen angehören: Benutzer-Threads Dämon-Threads Der Name Dämon-Thread bzw. Daemon-Thread stammt einigen Quellen zu Folge aus der UNIX-Welt und könnte die Abkürzung für »Disk And Execution Monitor« sein. Darauf deutet ebenso die Aufgabe hin, für die Dämonen hauptsächlich verwendet werden. Die Dämon-Threads haben einige nette Eigenschaften, die sie gegenüber Benutzer-Threads für manche Aufgaben priorisieren. Sie müssen sich beispielsweise niemals darum kümmern, ob ein einmal ins Rennen geschickter Dämon-Thread wieder zurückkommt. Nachdem ein Dämon-Thread gestar-
Java 2 Kompendium
409
Kapitel 8
Multithreading tet wurde, muss er auch nicht gestoppt werden. Wenn der Thread das Ende seiner Aufgabe erreicht, wird er automatisch stoppen und sein Status wird deaktiviert. Ein weiterer sehr wichtiger Unterschied (und der wahrscheinliche Grund für den Namen) zwischen Dämon-Threads und Benutzer-Threads ist der, dass Dämon-Threads die ganze Zeit laufen können. Wenn der Java-Interpreter feststellt, dass nur noch Dämon-Threads laufen, beendet er seine Ausführung, ohne sich darum zu kümmern, ob die Dämon-Threads fertig sind. Das ist sehr nützlich, da es gestattet, Threads zu starten, die Dinge wie Verwaltungsaufgaben, Beobachtung und Säuberungen im Hintergrund ausführen; sie werden von alleine sterben, wenn nichts anderes mehr läuft. Normalerweise endet ein Java-Programm erst, wenn alle seine (normalen) Threads beendet sind2. Die Dämon-Eigenschaft veranlasst die virtuelle Maschine von Java, diese Threads beim Überprüfen aller offenen Threads vor Beendigung eines Programms zu ignorieren. Die Nützlichkeit dieser Dämonen-Technik war in früheren Java-Versionen weitgehend auf grafische Java-Anwendungen beschränkt, da per Standard einige der Basis-Threads nicht als Dämon definiert sind. Dazu gehören AWT-Input, AWT-Motif, Main und Screen-Updater. Dies bedeutet, dass eine Anwendung, die die AWT-Klasse benutzte, keine Dämon-Threads besitzt, sondern Threads, die die Anwendung daran hindern, sauber beendet zu werden, bevor alle Threads beendet sind. Ab Java 2 und dem JDK 1.2 haben Dämonen eine beträchtliche Aufwertung erfahren. Die Erweiterung des RMI-Konzepts erlaubte es nun, Objekte anhand einer Referenz zu reaktivieren, wenn diese zuvor persistent gemacht wurden (Remote Object Activation). Die Umsetzung erfolgt mit dem neu im JDK 1.2 eingeführten Tool rmid – Java RMI Activation System Daemon. Dieses Tool startet den so genannten Activation System Daemon, einen speziellen Dämon, mit dessen Hilfe Objekte in einer JVM registriert und aktiviert werden können. Die intensive Behandlung von RMI und dem Umfeld sprengt den Rahmen dieses Buchs, aber Sie finden dazu in der Dokumentation des Tools im Anhang eine umfangreiche Anleitung. Zudem wird im Kapitel über die erweiterten Java-Techniken auf RMI und verwandte Techniken eingegangen. Zwei Methoden sind im Wesentlichen für den Dämonen-Status eines Threads zuständig. Dies sind: public final boolean isDaemon() public final void setDaemon(boolean on) 2
410
Das ist auch der Grund, warum die Beendigung der main()-Methode bei noch aktiven weiteren Benutzer-Threads nicht mit dem Programmende identisch ist.
Java 2 Kompendium
Dämonen
Kapitel 8
Die Methode isDaemon() wird benutzt um den Status eines bestimmten Threads zu testen. Gelegentlich ist dies für ein als Thread laufendes Objekt nützlich um festzustellen, ob es als Dämon oder als regulärer Thread läuft. Die Methode liefert true zurück, wenn der Thread ein Dämon ist und false im anderen Fall. Die Methode setDaemon(boolean) wird benutzt, um den Dämonen-Status eines Threads zu verändern. Um einen Thread zu einem Dämon zu machen, setzen Sie den Eingangswert auf true. Um wieder zurück zu einem BenutzerThread zu wechseln, setzen Sie den Wert auf false. Wenn Sie in einem Dämon-Thread einen anderen Thread starten, wird auch dieser beendet, wenn der Dämon-Thread beseitigt wird (auch ein BenutzerThread). Lassen Sie uns ein kleines Beispiel durchspielen, das auf einem von uns schon benutzten Exempel basiert. Es geht um das Beispiel, in dem ein Thread eine Endlosschleife enthält, die von einem zweiten Thread beobachtet und unterbrochen wird, wenn eine Zählvariable einen gewissen Wert überschreitet. class KeinDaemon extends Thread { long erg; public void run() { while(true) { // System.out.print(erg++); erg++; if(isInterrupted()) { // KEIN ABBRUCH System.out.println("Thread p unterbrochen " + erg); } } } } class EinDaemon extends Thread { KeinDaemon p = new KeinDaemon(); public void run() { System.out.println("Ist p Daemon? " + p.isDaemon()); System.out.println("Ist aktiver Thread Daemon? " + this.isDaemon()); // p wird zum Daemon p.setDaemon(true); System.out.println("Ist p Daemon? " + p.isDaemon()); p.start(); while(true) { if(p.erg>50) { p.interrupt(); break;
Java 2 Kompendium
Listing 8.11: Einsatz eines Dämons
411
Kapitel 8
Multithreading } } } } public class Luzifer { public static void main(String args[]) { EinDaemon q = new EinDaemon(); q.setDaemon(false); q.start(); } }
In dem Beispiel wird der Thread p explizit als Dämon deklariert, der von dem Benutzer-Thread q aufgerufen wird. Irgendwann unterbricht q den Thread p. Dieser Thread p wird aber explizit nicht (!) beendet, sondern der Benutzer-Thread q. Wenn p ein Benutzer-Thread wäre, müsste er auf jeden Fall noch explizit beendet werden. Da es sich jedoch um einen Dämon handelt, wird der Interpreter trotz aktivem Thread das Programm beenden (allerdings erst nach einigen nicht genau vorhersehbaren Zwischenschritten, wie die Ausgabe zeigt – darum geht es aber bei der Demonstration nicht). Abbildung 8.6: Der BenutzerThread wird beendet und »zieht den Dämon mit ins Grab«.
8.4
Schutzmaßnahmen bei Multithreading
Multithreading setzt eine gewisse Umsicht und gegebenenfalls auch Vorsichtsmaßnahmen voraus. Da innerhalb eines Programms verschiedene Prozesse parallel laufen, kann es durchaus passieren, dass diese gleichzeitig auf Ressourcen (etwa einen Datensatz in einer Datenbank) zuzugreifen versuchen oder ein Prozess fertig ist und einen anderen, noch nicht bereiten Prozess nun benötigt. In vielen Multithreading-Applikationen müssen Vorgänge synchronisiert werden oder auch Prozesse ein wenig »an die Kandare« genommen werden, damit sie anderen Prozessen nicht in die Quere kommen. Java stellt dazu einige ausgefeilte Techniken bereit.
412
Java 2 Kompendium
Schutzmaßnahmen bei Multithreading
8.4.1
Kapitel 8
Methoden synchronisieren
Methoden zu synchronisieren bedeutet, eine Datenverletzung zu verhindern, die passieren kann, wenn zwei Methoden gleichzeitig versuchen, auf dieselben Daten zuzugreifen. So etwas kann durchaus vorkommen, wenn Sie mit Multithreading arbeiten. Dies ist ähnlich dem Fall, wo zwei oder mehr Anwender in einer Netzwerkdatenbank den gleichen Datensatz allokieren und dann verändern wollen. Indem Sie in Java das Schlüsselwort synchronized vor eine Methodendeklaration setzen, können Sie die Datenverletzungen verhindern. Beispiel: synchronized void toggleStatus() Um Datenverletzungsprobleme zu vermeiden, deklarieren Sie am besten alle betreffenden Methoden als synchronized. In einem bestimmten Objekt dürfen keine zwei synchronisierten Methoden gleichzeitig laufen. Wenn eine Methode aufgerufen wird, erhält Sie ein »Schloss« für das Objekt. Wenn dann eine weitere synchronisierte Methode aufgerufen wird, muss die zweite solange warten, bis die erste fertig ist und das Schloss für dieses Objekt wieder geöffnet wurde. Auf diese Weise können Sie Zugriffsprobleme verhindern, wie sie durch paralleles Laufen von zwei Threads entstehen können, die beide gleichzeitig auf die gleichen Daten zugreifen wollen. Wenn Sie also den Modifier synchronized vor beide Methoden setzen, kann keine von beiden gleichzeitig mit der anderen laufen.
8.4.2
Vorsicht, Deadlock!
Dabei ist indes eine gewisse Sorgfalt angebracht, weil sich mehrere synchronisierte Methoden auch gegenseitig blockieren können, indem die eine Methode auf ein Ereignis wartet, das nur von der synchronisierten und deshalb gerade blockierten Methode abhängt. Sie kennen vielleicht das berühmte Beispiel von den drei Chinesen mit dem zwei Paar Essstäbchen, das in verschiedenen Varianten erzählt wird. Es handelt sich aber immer um eine Situation, die der folgenden Beschreibung ähnlich ist: Die drei Chinesen sitzen zum Essen um einen Tisch und haben nur zwei Paar Essstäbchen zur Verfügung. Jeder bekommt also nur ein Stäbchen. Das vierte Stäbchen wird in die Mitte des Tisches gelegt. Die Absprache (oder auch Programmierung) ist nun die, dass jeder Teilnehmer des seltsamen Mahls von seinem linken (oder rechten, es muss nur eindeutig sein) Nachbarn das vierte Stäbchen gereicht bekommt, sobald dieser etwas zu sich genommen hat. Wenn derjenige, der das Stäbchen bekommen hat, etwas zu
Java 2 Kompendium
413
Kapitel 8
Multithreading sich genommen hat, gibt er es an den rechten Nachbarn weiter. So können alle mit der Zeit etwas zu sich nehmen und es wird sicher ein unterhaltsames Mahl. Wie sieht es in der Praxis aus? Fangen wir ohne Beschränkung der Allgemeinheit mit Chinese Nummer 1 an. Er wartet darauf, dass er das Stäbchen von seinem linken Nachbarn (Nummer 3) bekommt. Dieser wartet darauf, es von seinem linken Nachbarn (Nummer 2) zu bekommen. Und dieser wiederum wartet darauf, es von seinem linken Nachbarn (Nummer 1) zu bekommen. Da Chinesen höflich sind, wird keiner als erstes das 4. Stäbchen aufnehmen und dementsprechend nie das Ereignis eintreten, auf das alle – langsam sicher extrem hungrigen – Teilnehmer des geplatzten Mahls warten. Um Sie zu beruhigen – die drei armen Kerle werden nicht verhungern. Sehr wahrscheinlich siegt irgendwann der Hunger über die Höflichkeit oder die Etikette (dann wird halt mit der Hand gegessen oder einer greift doch nach dem Stäbchen). Bei Threads entsteht Hunger höchstens beim Anwender und das hilft in der Situation überhaupt nichts. Selbst oder gerade synchronisierte Threads dürfen auf keinen Fall so gebaut werden, dass sie auf ein Ereignis warten, das sie selbst blockieren. Man nennt eine solche Patt-Situation zwischen Threads einen Deadlock.
8.4.3
Synchronisierte Blöcke
Bei der Technik des Multithreadings muss es die Möglichkeit geben, genau wie bei Methoden ganze Blöcke, die auf die gleiche Information zugreifen wollen, zu synchronisieren. Um Blöcke zu synchronisieren, kann man auch hier wieder das Schlüsselwort synchronized verwenden. Diesmal wird damit allerdings ein synchronisierter Block erstellt. Ein skizziertes Java-Programm soll eine Verwendung des Schlüsselworts synchronized demonstrieren. Listing 8.12: Skizze für synchronisierte Blöcke
414
class Netzwerk { Zugriff oeffneSchnittstelle; ... synchronized (oeffneSchnittstelle) { online(oeffneSchnittstelle.sende[1]); online(oeffneSchnittstelle.sende[2]); } }
Java 2 Kompendium
Zusammenfassung
Kapitel 8
Genau wie synchronisierte Methoden verschließen synchronisierte Blöcke ein spezifisches Objekt, solange sie laufen. Damit werden auch verschiedene synchronisierte Blöcke nicht gleichzeitig abgearbeitet, solange ein anderer synchronisierter Prozess abläuft. Es gibt jedoch in der Tat Unterschiede zwischen synchronisierten Methoden und synchronisierten Blöcken. Synchronisierte Methoden verschließen die derzeitige Klasse, während synchronisierte Blöcke nur die spezifizierte Methode während der Ausführung verschließen. Im obigen Beispiel wird die Instanz namens oeffneSchnittstelle verschlossen, nicht jedoch die Klasse Netzwerk.
8.4.4
Thread-sicherer Zugriff auf Datenstrukturen und Objekte
Das Multithreadingkonzept erzwingt die Möglichkeit eines Thread-sicheren Zugriffs auf Datenstrukturen und Objekte. Dieser ist in Java realisiert und trägt viel zur allgemeinen Stabilität eines Java-Systems bei. Dazu verwendet Java einen neuen und einzigartigen Ansatz, um Funktionen aufzurufen. Normalerweise rufen PC-Programme Funktionen über eine numerische Adresse auf. Da diese Adresse eine numerische Folge ist und diese Folge so programmiert werden kann, wie der Programmierer es sich wünscht, kann eine beliebige Zahl genommen werden, um dem Programm zu sagen, wie es eine Funktion ausführen soll. Damit wird eine Anonymität aufrechterhalten, mit der es unmöglich ist herauszufinden, welche Funktionen tatsächlich verwendet werden, wenn das Programm läuft. Java seinerseits verwendet Namen zum Aufruf von Methoden und Variablen. Auf Methoden und Variablen kann allein mit dem Namen zugegriffen werden. Zu bestimmen, welche Methoden verwendet werden, ist also einfach. Diese Verifizierung wird u.a. dazu verwendet, sicherzustellen, dass der Bytecode nicht beschädigt oder verändert wurde und dabei die Anforderungen von Java als Sprache erfüllt.
8.5
Zusammenfassung
Threads ermöglichen Java, Aufgaben in einem Programm sinnvoll aufzuteilen. Multithreading bedeutet im Wesentlichen, dass mehrere Aufgaben oder Prozesse quasi gleichzeitig ausgeführt werden können. Bei Java ist das Multithreadingkonzept voll integrierter Bestandteil der Philosophie. Der hochentwickelte Befehlssatz in Java, um Threads zu synchronisieren, ist in die Sprache integriert, macht diese stabil und einfach in der Anwendung. Java verwendet hochintelligente Synchronisationsanweisungen für den Umgang mit Multithreading. Die Schlüsselworte synchronized und threadsafe werden zum Markieren von Blöcken und Methoden benutzt, die eventuell vor gleichzeitiger Verwendung geschützt werden sollen.
Java 2 Kompendium
415
Kapitel 8
Multithreading Sie können Ihre Anwendungen und Klassen auf zwei Arten Multithreadingfähig machen. Einmal, indem Sie die Thread-Klasse erweitern oder indem Sie die Schnittstelle Runnable implementieren. Applets nutzen meist die zweite Variante, um multithreading-fähig zu werden und verwenden die run()-Methode statt der start()-Methode zum Aufruf des eigentlichen Programmcodes. Java stellt eine Vielzahl von Methoden und weitergehenden Konzepten zur Verfügung, die die Multithreading-Technik unterstüzen. Insbesondere können Threads in so genannte Thread-Gruppen eingeteilt werden, die im Wesentlichen eine Sicherheitsstrategie für Threads festlegen.
416
Java 2 Kompendium
9
Grafik und Animation
Eine der für viele Anwender wichtigsten Fähigkeiten von Java ist Grafikfähigkeit. Dies betrifft sowohl Java-Applets als auch normale Programme. Die meisten der schon von Anfang an vorhandenen Grafikfähigkeiten von Java sind in der Graphics-Klasse untergebracht, die ein Bestandteil des Paketes java.awt ist. Andere finden Sie in der Image-Klasse, die ebenso zu diesem Paket gehört. Diese Klassen oder am besten das ganze Paket sollte auf jeden Fall am Anfang jeder Sourcedatei importiert werden, bevor man die Grafikfähigkeiten von Java vollständig nutzen kann. Seit Java 1.2 gibt es noch zahlreiche weitere Pakete, die insbesondere für 2D-Java und Swing wichtig sind. Auch diese müssen Sie natürlich importieren, wenn Sie damit arbeiten wollen oder entsprechend qualifizierte Angaben machen. Wir werden am Beginn des Kapitels die Grafikmöglichkeiten von Java ansprechen, die schon in der ersten Variante vorhanden waren. Dies sind grundlegende Grafikvorgänge, die auch die Basis für die neuen Grafiktechniken wie 2D-Java (diese folgen im zweiten Abschnitt) sind. Es gibt durchaus technische Gründe, zuerst die älteren Grafiktechniken durchzusprechen. Zum einen muss berücksichtigt werden, dass die neuen Grafikmöglichkeiten noch einige Zeit nicht in allen Darstellungplattformen – insbesondere den Browsern – wiedergegeben werden können. Java 1.0x ist unter gewissen Umständen immer noch relevant für die Programmierung von Applets. Auch die prinzipiell in die Java-2-Plattform integrierte Lösung mit dem standardmäßig in die Plattform integrierten Java-Plug-In kann nicht immer eingesetzt werden. Die Version 1.0x ist deshalb vielfach der kleinste gemeinsame Nenner, der von allen Java-fähigen Browsern verstanden werden sollte. Folgetechniken werden dagegen im Wesentlichen für die Programmierung von neuen Applikationen oder Applets, die auf neueren Plattformen laufen sollen, verwendet. Aber sogar bei Applikationen wird noch nach dem 1.0x-Standard programmiert. Entweder, um dem Integrationskrieg um die Java-Versionen nach 1.0x zu entgehen, weil die alten Möglichkeiten ausreichen oder wenn bereits zu viele Bestandteile in diesem Standard erstellt wurden. Es gibt aber auch didaktische Gründe für einen Start mit den alten Grafiktechniken. Es werden die grundlegenden Techniken zur Verwendung von den Grafikmöglichkeiten von Java anhand dieser einfachen Methoden leichJava 2 Kompendium
417
Kapitel 9
Grafik und Animation ter deutlich. Festhalten sollte man aber schon jetzt, dass es unter den neuen Techniken für sämtliche der Methoden, die das java.awt.Graphics nutzen, einen Ersatz gibt oder dessen Anwendung sich direkt ableiten lässt. Wir werden aus der Vielzahl von Grafikmethoden nur eine kleine Auswahl präsentieren können, die aber die wichtigsten Grundtechniken beschreiben sollten. Insbesondere sollten Sie beachten, dass es für viele der vorgestellten Methoden mehrere Varianten des gleichen Namens gibt, von denen wir dann nur eine ausgewählte Version präsentieren. Wir kennen bereits einige Fähigkeiten zur Darstellung von Bildern und Text. Dazu kommen noch reichlich weitere Möglichkeiten, die das Zeichnen von Zeilen, Gebilden, Bildern und Text in verschiedenen Schriften, Stilen und Farben ermöglichen.
9.1
Zeichnen, Update und neu zeichnen
Allgemeine Zeichnenvorgänge in Java erfolgen, indem eine beliebige Grafikmethode innerhalb einer Methode aufgerufen wird. Dies muss nicht zwingend die uns bereits bekannte paint()-Methode sein. Diese ist »nur« eine ganz besondere Methode, um bestimmte Grafikvorgänge auszulösen. Großer Vorteil dieser Methode ist, dass sie in jedem Applet automatisch zur Verfügung steht und mit zahlreichen Mechanismen gekoppelt ist, die eine sinnvolle Verwaltung des Bildschirms gewährleisten. Aber nicht nur Applets können diese Methode automatisch verwenden. Auch eigenständige Applikationen, die mit einer grafischen Oberfläche ausgestattet sind, können diese Methode vollkommen analog einsetzen. Diese Methode spart Ihnen auch, dass Sie sich selbst um die Beschaffung eines Grafikkontexts kümmern müssen. Zwar kann man das im Allgemeinen Fall über die Syntax Graphics m_Graphics; m_Graphics = getGraphics();
machen und sich eine Instanz von Graphics erstellen, die zur Ausgabe verwendet wird. Sie können anschließend beispielsweise mit m_Graphics.drawString("Melde irgendwas", 10, 40);
auf sämtliche Grafikmethoden zugreifen. 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.
418
Java 2 Kompendium
Zeichnen, Update und neu zeichnen
Kapitel 9
Das ist jedoch recht umständlich und wird wie gesagt alles in der paint()Methode elegant im Hintergrund erledigt. Deshalb werden die meisten Zeichnenoperationen in der paint()-Methode durchgeführt werden. Wir wissen bereits, dass Java-Applets sich selbst neu zeichnen, indem Sie die paint()-Methode überschreiben. Wie jedoch wird die paint()-Methode aufgerufen? Es gibt dazu drei verschiedene Methoden, die verwendet werden, um das Applet oder die grafische Oberfläche einer Applikation neu zu zeichnen. 1.
Die Methode public void paint(Graphics g) selbst. Sie wird immer dann aufgerufen, wenn eine oder die grafische Oberfläche einer Applikation neu gezeichnet werden muss. Dies ist immer beim ersten Aufruf des Applets bzw. Programms der Fall, aber auch jedes Mal dann, wenn das Fenster verschoben oder zwischenzeitlich von einem anderen Fenster überlagert wurde. Der paint()-Methode wird eine Instanz der Graphics-Klasse weitergegeben, die sie für das Zeichnen verschiedener Formen und Bilder verwenden kann. Sie können übrigens auch Java jederzeit dazu veranlassen, ein Applet unabhängig von solchen oben beschriebenen Standardereignissen nachzuzeichnen. Dies erfolgt jedoch nicht mit einem direkten Aufruf von paint(), wie man sich vielleicht zuerst denkt. Es muss die repaint()-Methode verwendet werden.
2.
Die Methode public void repaint() kann jederzeit aufgerufen werden, wann auch immer das Applet oder die grafische Oberfläche einer Applikation neu gezeichnet werden muss, unabhängig von einem Standardereignis. Sie ist der Auslöser, der Java veranlasst, die paint()Methode sobald wie möglich aufzurufen und das Applet neu zu zeichnen. Die Aussage »so bald wie möglich« weist bereits darauf hin, dass die paint()-Methode unter Umständen mit einer gewissen Verzögerung ausgeführt werden kann, wenn andere Ereignisse zuerst zu Ende geführt müssen. Die Verzögerung ist jedoch meist sehr gering.
3.
Die Methode public void update(Graphics g) wird von repaint() aufgerufen. Die Standard-update()-Methode löscht den vollständigen Zeichenbereich des Applets bzw. der grafischen Oberfläche einer Applikation (oft ruft das den unangenehmen Flimmereffekt bei schnellen Bildsequenzen hervor) und ruft anschließend die paint()-Methode auf, die dann das Applet vollständig neu zeichnet. Es ist also genau genommen so, dass nicht die repaint()-Methode direkt die paint()-Methode aufruft, sondern nur indirekt über die update()-Methode.
Java 2 Kompendium
419
Kapitel 9
Grafik und Animation
9.2
Punkte, Linien, Kreise und Bögen
Die Graphics-Klasse verfügt über verschiedene Arten von Methoden zum Zeichnen von Grafiken, unter anderem über die folgenden Arten: Methoden zum Zeichnen von Linien Methoden zum Zeichnen von Kreisen und Ellipsen Methoden zum Zeichnen von Bögen Methoden zum Zeichnen von Rechtecken und Polygonen Methoden zum Zeichnen von Bildern Methoden zum Zeichnen von Text in verschiedenen Schriften, Farben und Schriftstilen
9.2.1
Das Koordinatensystem
Bevor wir nun tatsächlich unsere künstlerische Ader testen wollen, müssen wir uns noch ein bißchen mit Mathematik (ebenfalls eine Kunst) beschäftigen. Es geht um den Begriff Koordinatensystem. Intuitiv kann sich wahrscheinlich jeder unter dem Begriff etwas vorstellen, aber wie würden Sie ihn exakt definieren? Ich möchte dazu das umfangreiche Bronstein-Taschenbuch der Mathematik (angeblich die Bibel der Mathematiker, in der Tat aber nur die Bibel der mathematischen Randwissenschaft – Mathematiker rechnen nicht ;-)) – geringfügig verständlicher formuliert zitieren: Als Koordinatensystem eines n-dimensionalen affinen Raums wird eine Menge bezeichnet, die aus einem Punkt 0 des affinen Raums (Ursprung des Koordinatensystems) und n linear unabhängigen Verktoren des zum affinen Raum gehörigen Vektorraums besteht. Jeder Punkt des affinen Raums lässt sich, ausgehend vom Punkt 0, in eineindeutiger (wirklich einein...) Weise als Linearkombination der n Vektoren darstellen. Alles klar? Wenn nicht, machen Sie sich keine Sorgen: So abstrakt werden wir die Definition eines so genannten kartesischen Koordinatensystems nicht benötigen. Ich werde es deshalb einfacher formulieren. In einem Koordinatensystem lässt sich durch die Angabe von (in unserem 2-dimensionalen Fall) zwei Werten (so genannte Vektoren, die vom Ursprung des Koordinatensystems ausgehen) ein beliebiger Punkt in dem Koordinatensystem eindeutig festlegen. Schiffe-Versenken arbeitet mit einem solchen Koordinatensystem oder Excel, Schach, geografische Karten. Dabei werden allerdings unterschiedliche Einheiten zum Festlegen eines Punktes in dem Koordinatensystem verwendet.
420
Java 2 Kompendium
Punkte, Linien, Kreise und Bögen
Kapitel 9
Um in dem bei Java verwendeten einfachen, zweidimensionalen, kartesischen Koordinatensystem einen Punkt festlegen zu können, sind zwei Angaben notwendig – ein x-Wert und ein y-Wert. Sie werden dies aus dem Mathematikunterricht in der Schule kennen. Die Angaben von x und y legen einen Punkt eineindeutig fest. Wir müssen allerdings bei Java aufpassen, denn das dort verwendete Koordinatensystem unterscheidet sich ein wenig von dem sonst oft verwendeten System. Die obere linke Ecke des Bildschirms (genauer: des Bildschirmbereichs eines Applets oder dem Fenster einer Applikation) wird mit (0, 0) – also dem Koordinatensystem-Ursprung – abgebildet (sonst ist es meist die untere linke Ecke). Dabei ist der x-Wert die Anzahl der Bildschirm-Pixel von links ausgehend, also in der Waagrechten, und y ist die Zahl der Pixel, von oben angefangen, also in der Senkrechten. Das Koordinaten-Tupel (42, 10) beschreibt z.B. einen Punkt, der 42 Pixel vom linken Rand des Bildschirmbereichs und 10 Pixel vom oberen Rand des Bildschirmbereichs entfernt ist. Jede Grafikmethode von Java verwendet irgendwelche Koordinatenangaben, denn es muss ja festgelegt werden, wo eine Ausgabe beginnt und oft noch bis wohin die Ausgabe erfolgen soll. Die Grafikfähigkeit von Java umfasst so viele spezielle Zeichen-Techniken, Fonts, Farben und Animationstechniken, die wir nicht alle individuell diskutieren können. Wir wollen hier die Grundlagen der verfügbaren Merkmale und Methoden und die (hoffentlich für Sie ebenfalls) wichtigsten Aspekte als Einführung erörtern. Weitergehende Informationen finden Sie bei der Beschreibung der Java-Klassenbibliotheken. Schauen wir uns nun die wichtigsten Methoden an.
9.2.2
Eine Linie zeichnen – die drawLine()-Methode
Zu den einfachsten Techniken der Graphics-Klasse gehört das Zeichnen von Linien. Dies geschieht mit der Methode public abstract void drawLine(int x1, int y1, int x2, int y2).
Sie hat zwei Koordinatenpaare: Das Tupel (x1, y1), das den Anfangspunkt einer Linie bestimmt. Das Tupel (x2, y2), das den Endpunkt einer Linie bestimmt. Zwischen den beiden Tupeln wird eine Linie gezogen.
Java 2 Kompendium
421
Kapitel 9
Grafik und Animation Beispiel:
Listing 9.1: Eine Linie wird gezeichnet.
import java.awt.Graphics; public class ZieheLinie extends java.applet.Applet { public void paint(Graphics g) { g.drawLine(42,42,100,100); } }
Abbildung 9.1: Eine Linie
9.2.3
Ein Rechteck zeichnen – die drawRect()-Methode
Kommen wir nun zum Zeichnen von Rechtecken. Um ein Rechteck zu zeichnen, könnten Sie die drawLine()-Methode verwenden, indem Sie jeweils am Endpunkt einer Linie im rechten Winkel eine neue Linie ansetzten. Sofern die beiden gegenüber liegenden Linien gleich lang sind, haben Sie ein Rechteck. Diese Methode ist sehr flexibel und erlaubt Rechtecke, die in jede denkbare (zweidimensionale) Richtung gedreht sind. Sie setzt jedoch viel Sorgfalt und ein gewisses Gehirnschmalz voraus. Für Rechtecke, die nicht in der vertikalen/horizontalen Ausrichtung gedreht werden sollen, gibt es eine eigene Graphics-Methode – die Methode public void drawRect(int x, int y, int width, int height).
Sie verwendet wieder zwei Koordinaten-Tupel, die jedoch anders als die drawLine()-Tupel zu verstehen sind.
Das Tupel (x, y), das die linke obere Ecke eines Rechtecks bestimmt Das Tupel (width, height), das die Breite und die Höhe des Rechtecks festlegt Um beispielsweise ein Rechteck bei dem Punkt (150, 100) zu beginnen, das 200 Pixel breit und 120 Pixel hoch ist, würde der Methodenaufruf folgendermaßen aussehen.
422
Java 2 Kompendium
Punkte, Linien, Kreise und Bögen
Kapitel 9
Beispiel: import java.awt.Graphics; public class MaleRec extends java.applet.Applet { public void paint(Graphics g) { g.drawRect(150, 100, 200, 120); } }
Listing 9.2: Ein Rechteck
Abbildung 9.2: Ein Rechteck
9.2.4
Ein gefülltes Rechteck zeichnen – fillRect()
Die drawRect()-Methode zeichnet nur die Umrandung einer Box. Wenn Sie einen gefüllten Kasten zeichnen wollen, können Sie die Methode public abstract void fillRect(int x, int y, int width, int height)
verwenden, die die gleichen Parameter wie drawRect() verwendet (siehe Abbilfung 9.3). import java.awt.Graphics; public class MaleRecFil extends java.applet.Applet { public void paint(Graphics g) { g.fillRect(10, 100, 200, 42); } }
9.2.5
Listing 9.3: Gefülltes Rechteck
Löschen eines Bereichs – clearRect()
Ganz wichtig ist die Möglichkeit, einen rechteckigen Bereich wieder löschen zu können. Sie können dazu mit der Methode public abstract void clearRect(int x, int y, int width, int height)
Java 2 Kompendium
423
Kapitel 9
Grafik und Animation
Abbildung 9.3: Ein gefülltes Rechteck
arbeiten. Sie verwendet die gleichen Parameter wie drawRect(), lässt sich indes natürlich zum Löschen sämtlicher Kunstwerke (unabhängig von der zum Erzeugen verwendeten Methode) in dem spezifizierten Bereich verwenden. Beispiel: g.clearRect(10, 100, 200, 42); Um einen vollständigen Bereich eines Applets zu löschen, macht die folgende Syntax Sinn, denn sie beinhaltet die exakten Grö ß enangaben eines beliebigen Applets als Eckpunkte des zu löschenden Bereichs. g.clearRect(0, 0, this.size.width(), this.size.height());
9.2.6
Kopieren eines Bereichs – copyArea()
Ebenfalls wichtig ist die Möglichkeit, einen rechteckigen Bereich in einen anderen Bildschirmbereich kopieren zu können. Sie können dazu mit der Methode public abstract void copyArea(int x, int y, int width, int height, int dx, int dy)
arbeiten. Sie verwendet weitgehend die gleichen Parameter wie clearRect(). Jedoch müssen Sie außerdem den Zielbereich über ein zusätzliches (x, y)Tupel spezifizieren. Dieser wird über die Angaben dx – die horizontale Distanz zum Kopieren der Pixel – und dy – die vertikale Distanz zum Kopieren der Pixel – festgelegt. Beispiel: g.copyArea(10, 100, 200, 42, 200, 200);
424
Java 2 Kompendium
Punkte, Linien, Kreise und Bögen
9.2.7
Kapitel 9
Ein 3D-Rechteck zeichnen – die draw3dRect()-Methode
Die Graphics-Klasse verfügt ebenso über eine Methode zum Zeichnen von dreidimensionalen Rechtecken. Leider zeichnet die Graphics-Klasse diese Elemente mit einer sehr geringen Höhe oder Tiefe, wodurch der 3D-Effekt kaum sichtbar wird. Die Syntax für public void draw3DRect(int x, int y, int width, int height, boolean raised)
ist ähnlich der von drawRect(), nur dass diese einen booleschen Extraparameter am Ende hat, der angibt, ob das Rechteck erhöht angezeigt werden soll oder nicht. Der Erhöhungs-/Vertiefungseffekt wird erzeugt, indem helle und dunkle Linien um die Grenzen des Rechtecks gezogen werden. import java.awt.Graphics; public class MaleRec3D extends java.applet.Applet { public void paint(Graphics g) { g.draw3DRect(10, 100, 200, 42,true); } }
Listing 9.4: Ein 3D-Rechteck
Abbildung 9.4: Man sieht leider kaum einen 3DEffekt.
9.2.8
Ein gefülltes 3D-Rechteck zeichnen – fill3dRect()
Die fill3dRect()-Methode ist das dreidimensionale Analogon zu der fillRect()-Methode. Die Syntax für public void fill3DRect(int x, int y, int width, int height, boolean raised)
ist identisch mit der von draw3DRect() und zeigt wie diese kaum einen 3DEffekt an (vor allem bei unglücklicher Farbwahl).
Java 2 Kompendium
425
Kapitel 9 Listing 9.5: Gefülltes 3DRechteck
Grafik und Animation import java.awt.Graphics; public class MaleRec3DFil extends java.applet.Applet { public void paint(Graphics g) { g.fill3DRect(10, 100, 200, 42,true); } }
9.2.9
Abgerundete Rechtecke – drawRoundRect()
Neben normalen und 3D-Rechtecken können Sie ebenso Rechtecke mit abgerundeten Ecken zeichnen. Die Syntax der Methode public abstract void drawRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight)
ist drawRect() ähnlich, nur dass sie noch über zwei weitere Parameter verfügt: arcWidth arcHeight
Diese Parameter zeigen an, wie stark die Ecken abgerundet werden sollen. Das erste Argument arcWidth bestimmt den Winkel der Abrundung auf der horizontalen und der zweite Parameter arcHeight den Winkel auf der vertikalen Ebene. Das bedeutet, je grö ß er die Winkelwerte sind, desto stärker gerundet erscheint das Rechteck. Listing 9.6: Abgerundetes Rechteck
import java.awt.Graphics; public class MaleRecRound extends java.applet.Applet { public void paint(Graphics g) { g.drawRoundRect(10, 100, 50, 42,5,5); g.drawRoundRect(110, 200, 100, 42,5,50); g.drawRoundRect(150, 250, 25, 42,50,25); } }
9.2.10
Abgerundete gefüllte Rechtecke – fillRoundRect()
Das Analogon für abgerundete gefüllte Rechtecke ist die Methode public abstract void fillRoundRect(int x, int y, int width, int height, int arcWidth, int arcHeight).
Die Syntax ist identisch zu der drawRoundRect()-Methode.
426
Java 2 Kompendium
Punkte, Linien, Kreise und Bögen
Kapitel 9 Abbildung 9.5: Abgerundete Ecken
import java.awt.Graphics; public class MaleRecRoundFil extends java.applet.Applet { public void paint(Graphics g) { g.fillRoundRect(10, 100, 200, 42,42,42); g.fillRoundRect(10, 200, 200, 42,15,20); g.fillRoundRect(100, 250, 200, 42,20,5); } }
Listing 9.7: Gefüllte Rechtecke mit abgerundeten Ecken
Abbildung 9.6: Gefüllte Rechtecke mit abgerundeten Ecken
9.2.11
Zeichnen von Polygonen – drawPolygon()
Unter einem Polygon oder Vieleck versteht man eine grafische Form mit einer nicht zwingend vorgebenden Anzahl von Ecken und Kanten und beliebigen Winkeln zwischen den Kanten. Die Summe der Winkel muss bei einem geschlossenen Vieleck immer 360 Grad sein und die letzte Linie muss in diesem Fall den Anfangspunkt der ersten Linie berühren.
Java 2 Kompendium
427
Kapitel 9
Grafik und Animation Ein Polygonzug kann aber auch offen sein. Dies bedeutet, dass der Endpunkt der letzten Linie nicht mit dem Anfangspunkt der ersten Linie übereinstimmt. In dem Fall kann (und wird meist) die Summe der Winkel von 360 Grad abweichen. Ein Rechteck ist der Spezialfall eines geschlossenen Vierecks (sicher keine Überraschung), daneben fallen darunter Dreiecke, Fünfecke usw. Besonderheit ist bei Vielecken, dass im Regelfall keine Symmetrie zwischen den Kanten (wie etwa beim Rechteck oder Quadrat) herrscht. Um ein Vieleck zu zeichnen, können Sie wieder die drawLine()-Methode bemühen, indem Sie jeweils am Endpunkt einer Linie im beliebigen Winkel eine neue Linie ansetzten. Sofern sich die letzte Linie am Endpunkt und die erste Linie am Anfangspunkt berühren, haben Sie ein geschlossenes Vieleck. Diese Methode ist sehr flexibel und erlaubt beliebige Vielecke. Sie setzt jedoch viel Sorgfalt voraus und ist eigentlich unsinnig, denn es gibt eine eigene und genauso flexible Methode: public abstract void drawPolygon(int[] xPoints, int[] yPoints, int nPoints)
Die fehlende Symmetrie macht jedoch mehr Angaben über den Verlauf eines Vielecks notwendig, als es bei dem Spezialfall eines Rechtecks notwendig war. Sie haben zwei Optionen, wenn Sie Polygone zeichnen. Sie können entweder die zwei Datenbereiche (Arrays), die die x- und die y-Koordinaten der Punkte enthalten, weitergeben, oder Sie können eine Instanz einer PolygonKlasse weitergeben. Machen wir uns an einem Beispiel das erste Verfahren deutlich. Listing 9.8: Ein Polygonzug
import java.awt.Graphics; public class ZeichnePolygon extends java.applet.Applet { // Definiere ein array mit X-Koordinaten int xCoords[] = { 10, 40, 60, 30, 10 }; // Definiere ein array mit Y-Koordinaten int yCoords[] = { 20, 0, 10, 60, 40 }; public void paint(Graphics g) { // Zeichne ein 5-Eck g.drawPolygon(xCoords, yCoords, 5); } }
Es wird in dem Beispiel eine Linie von dem Tupel (xCoords[0], yCoords[0]) zu dem Tupel (xCoords[1], yCoords[1]), dann zu dem Tupel (xCoords[2], yCoords[2]) usw. gezogen.
428
Java 2 Kompendium
Punkte, Linien, Kreise und Bögen
Kapitel 9 Abbildung 9.7: Ein Polygonzug
Das zweite Verfahren verwendet ein Polygon-Objekt. Dieses Verfahren ist beispielsweise dann nützlich, wenn Sie nachträglich Punkte in das Vieleck einfügen wollen. Die Klasse Polygon gehört zum Paket java.awt.
import java.awt.Graphics; import java.awt.Polygon; public class ZeichnePolygon2 extends java.applet.Applet { // Definiere ein Array mit X-Koordinaten int xCoords[] = { 10, 40, 60, 300, 10, 30, 88 }; // Definiere ein Array mit Y-Koordinaten int yCoords[] = { 20, 0, 10, 60, 40, 121, 42 }; // Bestimme Anzahl Ecken über Methode length // des Array-Objekts mit x-Koordinaten int anzahlEcken = xCoords.length; public void paint(Graphics g) { Polygon poly = new Polygon(xCoords, yCoords, anzahlEcken); // Zeichne ein 7-Eck g.drawPolygon(poly); } }
Listing 9.9: Ein weiteres Polygon
Die beiden Arrays müssen für die x- und y-Koordinaten gleich groß sein. Wenn dies nicht der Fall ist, kann nur ein n-Eck gezeichnet werden, dessen maximale Anzahl der Ecken dem Index des kleineren Arrays entspricht. Wenn Sie eine Instanz einer Polygon-Klasse erstellt haben, können Sie die Methode public Rectangle getBoundingBox()
verwenden, um den Bereich zu ermitteln, der von diesem Polygon abgedeckt wird (die Minimum- und Maximum-x- und y-Koordinaten): Rectangle boundingBox = myPolygon.getBoundingBox();
Java 2 Kompendium
429
Kapitel 9
Grafik und Animation Die Rectangle-Klasse, die von getBoundingBox() zurückgegeben wird, enthält Variablen, die die x- und y-Koordinaten, die Höhe und die Breite des Rechtecks anzeigen. Ab der JDK-Version 1.1 ist die Methode getBoundingBox() durch getBounds() ersetzt. Sie können auch bestimmen, ob ein Punkt in einem Polygon liegt oder sich außerhalb befindet, indem Sie public boolean inside(int x, int y)
mit den x- und y-Koordinaten des Punktes aufrufen: if (myPolygon.inside(5, 10)) { // der Punkt (5, 10) ist innerhalb diese Polygons }
Die Methode inside() ist als deprecated bezeichnet und ab der JDK-Version 1.1 durch die Methode public boolean contains(int x, int y) ersetzt, die die identische Funktionalität bietet.
9.2.12
Zeichnen von gefüllten Polygonen – fillPolygon()
Sie können natürlich gefüllte Polygone zeichnen. Dazu dient die Methode public abstract void fillPolygon(int[] xPoints, int[] yPoints, int nPoints),
die in der Syntax wieder identisch zu der drawPolygon()-Methode ist. Beachten Sie jedoch, dass bei der fillPolygon()-Methode nur geschlossene Polygone erzeugt werden, d.h., Anfangs- und Endpunkt sind identisch. Listing 9.10: Gefüllter Polygonzug
430
import java.awt.Graphics; public class ZeichnePolygonFil extends java.applet.Applet { // Definiere ein array mit X-Koordinaten int xCoords[] = { 10, 30, 10, 30, 10, 15, 42 }; // Definiere ein array mit Y-Koordinaten int yCoords[] = { 10, 20, 10, 60, 70, 42, 42 }; int anzahlEcken = xCoords.length; public void paint(Graphics g) { // Zeichne ein 7-Eck g.fillPolygon(xCoords, yCoords, anzahlEcken); } }
Java 2 Kompendium
Punkte, Linien, Kreise und Bögen
Kapitel 9 Abbildung 9.8: Ein gefülltes Polygon, das sich in zwei Teile teilt
9.2.13
Zeichnen von Kreisen und Ellipsen – drawOval()
Ich möchte noch mal die Mathematik zu Hilfe rufen. Was ist eigentlich der Unterschied zwischen einem Kreis und einer Ellipse? Es gibt keinen, außer dass der Kreis eine ganz spezielle Ellipse ist. Wenn die beiden Brennpunkte der Ellipse in einen Punkt fallen, hat man eben einen Kreis. Folgerichtig hat die Graphics-Klasse keine extra Methode für einen Kreis, sondern nur eine Methode für Ellipsen – die drawOval()-Methode. Diese besitzt die gleichen Koordinaten-Angaben wie die drawRect()-Methode. Wie das? Nun, Sie wissen vielleicht aus dem Mathematikunterricht (ich weiß, es langt bald, aber Grafik hat eine Menge mit Mathematik zu tun und für irgendwas muss ein Mathematikstudium taugen :-> ), dass man in ein Rechteck in bestimmten Fällen eindeutig eine Ellipse oder in jedem Fall in ein Quadrat eindeutig einen Kreis einschreiben kann. In der Mitte jeder Seite des Rechtecks bzw. Quadrats stö ß t das eingeschriebene runde Objekt in genau einem Punkt an die Linie des umgebenden rechteckigen Objekts. Sie geben also die Koordinaten der oberen linken Ecke dieses umschreibenden Rechtecks im ersten (x,y)-Tupel an die Methode public abstract void drawOval(int x, int y, int width, int height)
weiter. Das zweite (x,y)-Tupel legt die Breite und die Höhe des umschreibenden Rechtecks und damit auch des Ovalkörpers fest. Wenn Breite und Höhe gleich sind, haben Sie einen Kreis. import java.awt.Graphics; public class ZeichneOval extends java.applet.Applet { public void paint(Graphics g) { // Zeichne eine Ellipse g.drawOval(50, 100, 200, 120); // Zeichne einen Kreis g.drawOval(175, 175, 200, 200); } }
Java 2 Kompendium
Listing 9.11: Kreis und Oval
431
Kapitel 9
Grafik und Animation Das eindeutige Umschreiben eines rechteckigen Körpers mit einer ovalen Form ginge ebenso, aber in Java wird das Einschreiben genutzt.
Sie können auch die bereits beschriebene drawRoundRect()-Methode verwenden, um einen Kreis zu zeichnen, wenn Sie die Abrundung so definieren, dass die Winkel identisch sind und der Beginn der Abrundung in der Mitte des Rechtecks beginnt. Eine Ellipse funktioniert ähnlich, nur sind dann immer nur die beiden symmetrischen Winkelangaben identisch, nicht alle vier (sonst haben wir den Spezialfall Kreis). Abbildung 9.9: Eine Ellipse und ein Kreis
9.2.14
Zeichnen von gefüllten Kreisen und Ellipsen – fillOval()
Das Analogon für gefüllte Kreisen und Ellipsen ist die Methode public abstract void fillOval(int x, int y, int width, int height).
Die Syntax ist identisch mit der drawRect()-Methode. Listing 9.12: Kreis und Oval gefüllt
432
import java.awt.Graphics; public class ZeichneOvalFil extends java.applet.Applet { public void paint(Graphics g) { // Zeichne eine Ellipse g.fillOval(50, 15, 250, 50);
Java 2 Kompendium
Punkte, Linien, Kreise und Bögen
Kapitel 9
// Zeichne einen Kreis g.fillOval(250, 150, 50, 50); } } Abbildung 9.10: Gefüllte Ellipsen
9.2.15
Zeichnen von Bögen – drawArc()
Ein Bogen ist Teil einer Ellipse oder eines Kreis. Der Unterschied ist eigentlich nur, dass die Figur nicht geschlossen wird. Im Prinzip ist die Geschichte jedoch auch nicht viel schwieriger als das Zeichnen der vollständigen Figuren. Java stellt die folgende Methode dafür zur Verfügung: public abstract void drawArc(int x, int y, int width, int height, int startAngle, int arcAngle).
Die ersten vier Parameter sind identisch mit der drawOval()-Methode. Man muss der Methode nur zusätzlich mitteilen, ab wo und bis wo man die Figur gezeichnet haben möchte. Dazu muss man nur wissen, dass ein Kreis (oder auch Oval im allgemeinen Sinn) in 360 Grad unterteilt werden kann. Der fünfte Parameter der drawArc()-Methode bestimmt den Anfangswinkel von einer gedachten vertikalen Mittellinie aus gesehen, ab dem der Bogen gezeichnet werden soll. Der sechste Parameter legt den Winkel fest, wie weit der Bogen gehen soll. Dabei bedeutet der Gradwert, wie weit der Bogen ab dem Startpunkt gezeichnet wird und in welche Richtung er geht. Die positive Richtung in Java ist entgegen dem Uhrzeigersinn. Mit Angabe eines negativen Parameters wird in Richtung des Uhrzeigersinns gezeichnet. Es handelt sich also nicht um den Winkel von der gedachten vertikalen Mittellinie, wie bei Parameter 5. Ein Halbkreis wird also als Parameter 6 die Angabe 180 haben.
Java 2 Kompendium
433
Kapitel 9 Listing 9.13: Bögen
Grafik und Animation import java.awt.Graphics; public class ZeichneBogen extends java.applet.Applet { public void paint(Graphics g) { g.drawArc(50, 15, 50, 50, 90, 180); g.drawArc(50, 200, 150, 50, 120, -90); } }
Abbildung 9.11: Bögen
9.2.16
Zeichnen von gefüllten Bögen – fillArc()
Das Analogon zu der drawArc()-Methode für gefüllte Bögen ist die Methode public abstract void fillArc(int x, int y, int width, int height, int startAngle, int arcAngle).
Die Syntax ist identisch. Zwischen Anfangs- und Endpunkt wird eine gerade Linie gezogen und die Figur ausgefüllt. Listing 9.14: Gefüllte Bögen
import java.awt.Graphics; public class ZeichneBogenFil extends java.applet.Applet { public void paint(Graphics g) { g.fillArc(50, 15, 50, 50, 90, 90); g.fillArc(150, 150, 50, 50, 120, 180); } }
9.3
Farbangaben
Java setzt in seinem ursprünglichen Konzept (wir werden noch das neue Konzept kennen lernen) sämtliche Farben aus so genannten Primärfarben des Lichts zusammen. Dies sind die Farben Rot, Grün und Blau. Man nennt dieses Farbmodell das RGB-Modell (Red-Green-Blue). Das bedeutet, dass Farben aus der Summe von rotem, grünem und blauem Licht zusammengesetzt werden. 434
Java 2 Kompendium
Farbangaben
Kapitel 9 Abbildung 9.12: Gefüllte Bögen
Vielleicht werden Sie jetzt stutzen, weil Ihnen als Primärfarben Rot, Gelb und Blau einfallen. Dies ist nicht falsch, es handelt sich nur um ein anderes Farbmodell, das statt Grün Gelb verwendet. Man findet es bei pigmentbasierenden Farben (etwa Buntstiften). Das Lichtfarbmodell basiert jedoch auf der grünen Grundfarbe und dies wird von Java verwendet. Einige der üblichen Kombinationen sind: rot + grün = braun (oder gelb, je nach Helligkeit) grün + blau = cyan (hellblau) rot + blau = magenta (lila) Schwarz entsteht, wenn es kein Licht gibt, weiß wird mit der Kombination aller Primärfarben gebildet: rot + grün + blau (in gleicher Menge) = weiß. Dies ist genau umgekehrt zu dem Pigment-basierendem Modell. Sie definieren eine Farbe im RGB-Modell, indem Sie angeben, wie viel rotes, grünes und blaues Licht in der Farbe enthalten ist. Sie können dies beispielsweise mit einer Zahl zwischen 0 und 255 oder mit Gleitkommazahlen zwischen 0,0 und 1,0. Mehr Informationen zu diesem Thema finden Sie im Anhang. Kommen wir nun zu den Objekten und Methoden, mit denen unter Java die Farben verändert und für die diversen Grafik-Methoden gesetzt werden können.
9.3.1
Die Color-Klasse
Um ein Objekt in einer bestimmten Farbe zeichnen und darstellen zu können, können Sie eine Instanz der Color-Klasse erzeugen.
Java 2 Kompendium
435
Kapitel 9
Grafik und Animation Wir schauen uns drei Arten an, mit denen Sie eine Farbe können erzeugen können: 1.
public Color(int red, int green, int blue). Damit wird eine Farbe mit Rot-, Grün- und Blau-Werten zwischen 0 und 255 erzeugt.
2.
public Color(int rgb). Damit wird eine Farbe mit Rot-, Grün- und Blau-Werten zwischen 0 und 255 erzeugt, bei der jedoch alle Werte zu einem einzigen Integerwert kombiniert werden. Die Bits 16-23 enthalten den Rot-Wert, die Bits 8-15 enthalten den Grün-Wert und 0-7 enthalten den Blau-Wert. Diese Werte werden normalerweise in hexadezimaler Schreibweise notiert, damit Sie die Farbwerte ganz einfach sehen können. 0x123456 würde beispielsweise einen Rot-Wert von 0x12 (18 dezimal) ergeben, einen Grün-Wert von 34 (52 dezimal)) und einen Blau-Wert von 56 (96 dezimal). Denken Sie daran, dass jede Farbe in hexadezimaler Schreibweise genau 2 Zeichen einnimmt.
3.
public Color(float red, float green, float blue). Damit wird eine Farbe mit Rot-, Grün- und Blau-Werten von 0.0 und 1.0 erzeugt.
Eine Instanz einer Farbe erstellen Sie also beispielsweise so: Color pinkColor = new Color(255, 192, 192); Color MeineFarbe = new Color(0.0, 1.0, 0.25);
Eine gängige Farbe können Sie noch schneller zur Verfügung haben, denn die Color-Klasse definiert verschiedene in Klassenvariablen gespeicherte Standardfarbobjekte, auf die Sie per Punktnotation zugreifen können. Beispiele: Color.white Color.pink
Auch zu den Standardfarbobjekten finden Sie mehr Informationen im Anhang im Abschnitt über Farben.
9.3.2
Farben setzen – setColor()
Wenn Sie einmal eine Farbe erstellt haben oder ein Standardfarbobjekt verwenden wollen, können Sie die Zeichenfarbe mit der Methode public abstract void setColor(Color c)
verändern. Als Parameter geben Sie das gewünschte Farbobjekt an.
436
Java 2 Kompendium
Farbangaben import java.awt.*; public class Farbe extends java.applet.Applet { public void paint(Graphics g) { // Erzeugen von Farbobjekten Color pinkColor = new Color(255, 192, 192); Color irgendeineFarbe1 = new Color(255, 100, 92); Color irgendeineFarbe2 = new Color(155, 192, 192); Color irgendeineFarbe3 = new Color(55, 192, 192); Color irgendeineFarbe4 = new Color(5, 12, 120); // Zeichne eine Ellipse g.setColor(irgendeineFarbe2); g.drawOval(5, 5, 150, 250); // Zeichne eine Ellipse g.setColor(irgendeineFarbe1); g.fillOval(50, 15, 250, 150); // Zeichne einen Kreis g.setColor(pinkColor); g.fillOval(250, 150, 150, 150); // Zeichne einen gefüllten Bogen g.setColor(irgendeineFarbe3); g.fillArc(50, 15, 100, 100, 90, 90); // Zeichne einen weiteren gefüllten Bogen // in einer anderen Farbe g.setColor(irgendeineFarbe4); g.fillArc(150, 150, 50, 50, 120, 180); // direkte Verwendung einer Farbkonstanten g.setColor(Color.green); g.drawRect(40,40,100,200); } }
Kapitel 9 Listing 9.15: Verschiedene Farben
Abbildung 9.13: Verschiedene Farben
Java 2 Kompendium
437
Kapitel 9
Grafik und Animation
9.3.3
Hintergrundfarben und Vordergrundfarben pauschal setzen – setBackground() und setForeground()
Normalerweise ist die Hintergrundfarbe eines Applets weiß oder dunkelgrau (je nach Einstellungen im Container). Wenn Sie einmal eine Farbe erstellt haben oder ein Standardfarbobjekt verwenden wollen, können Sie damit ebenso die Hintergrundfarbe eines Applets individuell mittels der Methode public void setBackground(Color c)
setzen. Listing 9.16: Die paint()-Methode mit Setzen von Farbangaben
public void paint(Graphics g) { /* Hier wird auf einem rosa Hintergrund ein grünes Rechteck gezeichnet*/ Color pinkColor = new Color(255, 192, 192); setBackground(pinkColor); g.setColor(Color.green); g.drawRect(40,40,100,20); }
Wenn Sie die Farbe für alle Zeichenobjekte innerhalb eines Applets oder einer Applikation mit grafischer Oberfläche pauschal vorbelegen wollen, können Sie die Methode public void setForeground(Color c)
verwenden. Als Parameter geben Sie wieder das gewünschte Farbobjekt an. Listing 9.17: Hintergrund- und Vordergrundfarbe
import java.awt.*; public class BackFarbe extends java.applet.Applet { public void paint(Graphics g) { Color irgendeineFarbe1 = new Color(200, 100, 100); setBackground(irgendeineFarbe1); setForeground(Color.blue); g.fillRect(40,120,300,120); g.fillOval(5, 5,300, 150); } }
Die Angaben für Hinter- und Vordergrundfarben sollten in einer Klasse nur einmal gesetzt werden. Es sind globale Angaben, die bei Bedarf mit den individuellen Farbangaben verändert werden sollten, nicht durch erneutes Verändern der globalen Angaben. Vielleicht sind Sie von den bisherigen einfachen Beispielen mit den ziemlich einfachen Formen etwas enttäuscht. Dem kann man abhelfen. Wir werden jetzt ein berühmtes und optisch immer wieder eindrucksvolles Beispiel erstellen. 438
Java 2 Kompendium
Farbangaben
Kapitel 9 Abbildung 9.14: Global gesetzte Farben
Dabei verwenden wir nur die bereits besprochenen Grafikmethoden, die zu dem grundlegenden Grafikkonzept von Java gehören und die weitergehenden Techniken nicht bemühen (damit bleibt das Beispiel für mehr Plattformen lauffähig). Ergänzend wird deutlich, wie schön Mathematik sein kann :*). Es geht um das berühmte Apfelmännchen.
9.3.4
Ein Apfelmännchen als Java-Applet
Fast jeder, der sich intensiver mit Programmierung auseinandersetzt, wird irgendwann einmal auf Fraktale stoßen. Diese selbstähnlichen Gesellen bieten sich mit ihrer rekursiven Struktur geradezu zum grafischen Austesten von Rekursionen an. Unter einer Rekursion versteht man einen Programmieralgorithmus, der das Ergebnis eines Rechenschritts als Grundlage für den nächsten Schritt benötigt. Nur der erste Schritt (oder abzählbar viele) wird (werden) mittels Startwerten gesetzt. Es gibt Fraktal-Programme in fast allen Programmiersprachen und selbstverständlich kann man zur Programmierung von Fraktalen die innovative Sprache Java heranziehen. Das folgende Beispiel ist ein kleines Java-Programm zur Generierung des wohl berühmtesten Fraktals – dem Apfelmännchen. Die Entdeckung von Fraktalen geht wesentlich auf Arbeiten des amerikanischen Mathematikers und Physikers B.B. Mandelbrot zurück. Dieser beschäftigte sich u.a. in den 70er- und 80er-Jahren mit rekursiv definierten Formeln (besonders wichtig ist das 1982 erschienene Buch »The Fractal Geometry of Nature«). Die immer leistungsfähiger werdenden Computer ermöglichten bald ein grafisches Umsetzen von diesen rekursiv definierten Mustern. Die unzweifelhaften ästhetischen Resultate sorgten schnell für Aufmerksamkeit, aber vor allem die verblüffende Ähnlichkeit zu in der
Java 2 Kompendium
439
Kapitel 9
Grafik und Animation Natur vorkommenden Prozessen und Formen (das Prinzip der Selbstähnlichkeit in jeder Vergrö ß erungstiefe – denken Sie an einen Farn oder einen Blumenkohl) führte zu einer immer stärkeren Bedeutung der Fraktale (oder oft nach ihrem Entdecker Mandelbrotmenge genannt). Das aus den Fraktalen entstandene Forschungsgebiet ist übrigens heute als Chaostheorie bekannt. Fraktale bieten sich bei der Geschwindigkeit heutiger Computer für interessante grafische Experimente an, da die grafischen Effekte auf relativ einfach zu programmierenden Grundstrukturen basieren. Erst der rekursive Aufruf – etwa in Form von ebenfalls einfachen Schleifen – erzeugt die eigentlichen interessanten Effekte. Allerdings sollte für die Grundstrukturen neben Funktionen von reellen Zahlen auf jeden Fall die komplexe Ebene eingeschlossen werden (sonst wird es optisch ziemlich langweilig). Das Apfelmännchen ist wegen seiner relativ einfachen Grundstruktur das verbreitetste (und bekannteste) Fraktal und wird im folgenden Beispiel in Java umgesetzt. Trotz einer äußert komplexen grafischen Struktur bleibt der Source ziemlich klein und kompakt. Beachten sollten Sie bei einem Test, dass die Umsetzung rekursiver Formeln sehr stark von der Rechenleistung des Computers abhängt. Wenn Sie mit einem älteren Rechner arbeiten, kann die Berechnung und Darstellung des Apfelmännchens etwas dauern. Im Source sind die Stellen mit Kommentaren gekennzeichnet, wo Sie die Rechentiefe verändern können. Damit kann die Geschwindigkeit des Aufbaus (zeilenweise von oben nach unten) auf Kosten der Genauigkeit verbessert werden (und umgekehrt). Für die Bildschirmauflösung sollten mindestens 256 Farben gewählt sein, da die Berechnung der Farbe eines Bildpunktes sich aus dem Zusammensetzen von drei RGB-Werten- jeweils zwischen 0 und 255 – ergibt. Die Farbe wird im Beispiel nur durch Manipulation des Rot- und Grünwertes verändert, was aber willkürlich ist und natürlich ebenso für die anderen RGB-Werte funktioniert (für alle drei gleichzeitig oder nur einen). Bei komplexeren Versionen des Apfelmännchens kann man die drei RGB-Werte auch unabhängig voneinander in getrennten Rekursionen für jeden Parameter ermitteln. In dem Beispiel liegt die Grundfarbwahl im Rotbereich. Auch sie kann aber jederzeit in einen anderen Farbbereich verlegt werden (siehe Kommentare im Source und in den Screens). Durch die nicht-linearen komplexen Funktionen können Sie übrigens sicher sein, dass ein geringfügiges »Wackeln« an den Startwerten das Ergebnis extrem beeinflussen kann. Das wird in unserem Beispiel noch nicht so deutlich zu spüren. Aber wenn man einen zufälligen Ausschnitt unseres Ergebnisses vergrö ß ern würde, würde die Wahl des Ausschnittes nach spätestens 2 bis 3 Wiederholungen das Ergebnis massiv beeinflussen. Die Wahrscheinlichkeit, dass Sie eine exakte Kopie eines anderen Vergrö ß erungsvorgangs erstellt haben, ist kleiner, als 7(!) Richtige im Lotto zu haben (will heißen, kaum von 0 verschieden). Dies ist ebenfalls
440
Java 2 Kompendium
Farbangaben
Kapitel 9
wesentlicher Bestandteil der Chaostheorie. Vielleicht kennen Sie das Beispiel von dem Schmetterling, der am Äquator mit den Flügeln schlägt und damit in Europa das Wetter zum Kippen bringt. Das ist kein Witz, man kann es beweisen. Das Problem ist nur, dass es mehr als einen flügelwackelnden Schmetterling gibt (von grö ß eren Ereignissen wie startenden Flugzeugen auf Rheinmain oder Atombombentest in der Karibik ganz zu schweigen). Man nennt diese verschiedenen Effekte, die das Ergebnis beeinflussen können, Randbedingungen. Fraktal-Programme, die solche Ausschnittsbildungen mit sich ständig verändernden Randbedingungen zulassen, erzeugen immer wieder neue Ergebnisse. Nehmen wir uns jetzt endlich den Source vor. Wir werden ein paar Features nutzen, die erst bei der Diskussion des Java-AWT vorkommen. Nicht viele, aber es wird einfacher dadurch. Nehmen Sie diese erst einmal hin, die zentralen Methoden des Apfel-Applets haben wir schon durchgesprochen. // Einbinden der Packages mit dem import-Befehl import java.awt.*; import java.applet.*; import java.util.*; //Die Klasse Apfel wird als Applet definiert public class Apfel extends Applet { public void init() { // Einstiegspunkt für das Applet mit Layoutangaben setLayout(new BorderLayout()); helloPanel hp=new helloPanel(); add("Center",hp); } /* Unser Event-Handler in Java reagiert auf die entsprechenden Fensterereignisse, d.h. in unserem Fall bewirkt ein Schließen des Fensters ein Programmende. Mehr Funktionalität ist in dem Beispiel nicht integriert. */ public boolean handleEvent(Event e) { switch (e.id) { case Event.WINDOW_DESTROY: System.exit(0); return true; default: return false; } } } class helloPanel extends Panel { public helloPanel() { // Setzen der Hintergrundfarbe setBackground(Color.blue); } public int iteration(double realteil,double imaginaerteil) { /* Diese Methode enthält die eigentliche Iteration zur Berechnung der Farbe eines Bildpunktes des Apfelmännchens. Genau genommen wird hier zum einen die
Java 2 Kompendium
Listing 9.18: Das Apfelmännchen in Java
441
Kapitel 9
Grafik und Animation Entscheidung getroffen, welcher Zweig in der Zeichenmethode paint() zu verwenden ist. Zum anderen wird ein Modulo-Faktor für die tatsächliche Farbwahl berechnet. Dabei werden zwei Parameter benötigt – der Realteil und der Imaginärteil. */ double x,y,x2,y2,z; int k=0; x=0; y=0; x2=y; y2=0; z=0; for (k=0;k<1000;k++) { // Der komplexe Apfelmännchen-Grundalgorithmus y=2*x*y+imaginaerteil; x=x2-y2+realteil; x2=x*x; y2=y*y; z=x2+y2; if (z>4) return k; } return k ; } public void paint(Graphics g) { /* In der Zeichenmethode paint() wird der eigentliche Bildaufbau vorgenommen. */ /* Startwerte für das Apfelmännchen. Ein Verändern der Startwerte führt zu einer Veränderung der Grö ß e und/oder Position des Apfelmännchens. Die Grundfläche für den Aufbau des Apfelmännchens bleibt allerdings unberührt. */ double restart=-2; double reend=1; double imstart=-1; double imend=1; //Alternative Startwerte zum Experimentieren /* double restart=-3; double reend=2; double imstart=-2; double imend=2; */ double restep,imstep,imquad,repart,impart; int x,y,farbe; /* Veränderung der Schrittweiten bei der Berechnung beeinflusst ebenfalls Grö ß e und Position des Fraktals */ // Schrittweite für den Realteil restep=(reend-restart)/200; // Schrittweite für den Imaginärteil imstep=(imend-imstart)/200; y=0; // Zählvariable // Zuweisung eines Startwertes für den // Imaginärteil in der Rekursion impart=imstart;
442
Java 2 Kompendium
Farbangaben
Kapitel 9
/* Beginn der Rekursion. Zwei ineinander verschachtelte for-Schleifen. Die äußere Schleife berechnet den Realteil, die innere Schleife den Imaginärteil. */ // Jeder y-Wert entspricht einer Bildschirmzeile for (y=0;y<200;y++) { // Zuweisung eines Startwertes für den Realteil // in der Rekursion repart=restart; // Jeder x-Wert entspricht einer Spalte for (x=0;x<200;x++) { /* Berechnung der Entscheidungsvariable für die Farbe eines Bildpunktes*/ farbe=iteration(repart,impart); if(farbe==1000) { // Zeichne an der Position x,y einen schwarzen // Punkt g.setColor(Color.black); g.drawLine(x,y,x+1,y); } else { /* Hier wird die Farbe eines Bildpunktes vom eigentlichen Apfelmännchen explizit berechnet. Die 3 Angaben in der Color-Angabe sind RGB-Werte (RotGrün-Blau) und legen die jeweilige Intensität der Farbanteile fest. Nur der erste Parameter wird jeweils neu berechnet. Dabei ist bei Manipulationen des Rotbereichs darauf zu achten, dass das Resultat zwischen 0 und 255 bleibt. Hier im Beispiel liegt die Grundfarbwahl im Rotbereich. Sie kann aber jederzeit durch Veränderung der Parameter in einen anderen Farbbereich verlegt werden. */ Color jr = new Color(255-(farbe%52*5),255-(farbe%52*5),125); /* Alternative Grundfarbe. Dabei wird sowohl der Rotanteil, als auch der Grünanteil manipuliert. */ /*Color jr = new Color(255-(farbe%26*10), 120, 125); */ g.setColor(jr); // Zeichne an Position x,y einen Punkt mit // dem Farbwert jr g.drawLine(x,y,x+1,y);} /* Neue Werte für die Iteration*/ repart=repart+restep;} impart=impart+imstep;} } }
Mit den nicht auskommentierten Grundeinstellungen bekommen wir ein gelbes Apfelmännchen mit schwarzem Innenbereich und blauer Hintergrundfarbe (am rechten und unteren Rand zu sehen). Wenn wir ein bisschen mit den Anfangswerten und dem Hintergrund experimentieren, bekommen wir ein gänzlich verschiedenes Farbspektrum.
Java 2 Kompendium
443
Kapitel 9
Grafik und Animation
Abbildung 9.15: Das berühmte Apfelmännchen
Abbildung 9.16: Sämtliche Farben bewegen sich im helleren Farbspektrum.
9.3.5
Abrufen von Farbinformationen
Wenn Sie eine Farbe haben, können Sie sowohl die Rot-, Grün- und BlauWerte einzeln ermitteln, als auch die Farbangabe als Ganzes oder die Vordergrund- und Hintergrundfarben. Dazu dienen die folgenden Methoden: Die Methode public int getRed() liefert den Rotanteil einer Farbe als int-Wert zurück. Die Methode public int getGreen() liefert den Grünanteil einer Farbe als int-Wert zurück. Die public int getBlue()-Methode liefert den Blauanteil einer Farbe als int-Wert zurück. Die Methode public static Color getColor(String nm) ermittelt die aktuelle Grafikfarbe und gibt ein entsprechendes Farbobjekt zurück. Die Methode public Color getBackground() ermittelt die aktuelle Hintergrundfarbe und gibt ein entsprechendes Farbobjekt zurück. Die Methode public Color getForeground() ermittelt die aktuelle Vordergrundfarbe und gibt ein entsprechendes Farbobjekt zurück.
444
Java 2 Kompendium
Farbangaben
9.3.6
Kapitel 9
Textausgabe über den Zeichnen-Modus
Die Graphics-Klasse enthält diverse Methoden, um Textzeichen und Zeichenketten sowie allgemeine Zeichen zu zeichnen. Wir kennen ja bereits die drawString()-Methode. Um nun mit der Graphics-Klasse Text auszugeben, rufen Sie in der Regel public abstract void drawString(String str, int x, int y)
auf und übergeben die Zeichenkette, die Sie zeichnen wollen, samt der x- und y-Koordinaten für den Anfang der darzustellenden Zeichen. Die Koordinaten definieren genau genommen den Beginn einer gedachten Grundlinie, auf der der darzustellende Text aufsitzt. Diese Methode ist allerdings nur zur Ausgabe von Strings zu verwenden. Wenn primitive Datentypen ausgeben werden sollen, kann man damit nicht (direkt) arbeiten. Entweder, diese Datentypen werden in Strings konvertiert (beispielsweise durch Verknüpfung mit einem String über + oder Anwendung von Wrappern) oder man muss auf eine der Schwester-Methoden zurückgreifen, die dafür bereitgestellt werden: drawBytes(byte[], int, int, int, int) drawChars(char[], int, int, int, int)
Der erste Parameter bezeichnet dabei eine Reihe von Zeichen/Byte. Der zweite Parameter – der offset-Parameter – bezieht sich auf die Position des ersten Zeichens oder Bytes in dem zu zeichnenden Datenfeld. Diese wird oft Null sein, da Sie gewöhnlich vom Anfang des Datenbereiches an zeichnen wollen. Der dritte Parameter ist eine Ganzzahl für die Position des letzten zu zeichnenden Zeichens. Parameter 4 und 5 sind die bekannten Anfangskoordinaten. Zusätzlich spielen die Font-Klasse und die FontMetrics-Klasse beim Textzeichnen eine wichtige Rolle. Die Font-Klasse stellt bestimmte Fonts dar (Name, Stil, Punktgrö ß e), während FontMetrics Informationen über den Font enthält wie die tatsächliche Höhe und Breite eines bestimmten Zeichens.
9.3.7
Erstellen von Fontobjekten
Um Text auf dem Bildschirm ausgeben zu können, ist ein sinnvoller Weg, dass Sie zuerst eine Instanz der Klasse Font erstellen. Fontobjekte stellen einen einzelnen Font dar. Fontnamen sind Zeichenketten, die die Familie der Schrift bezeichnen (Arial, TimesRoman usw.). Fontstile sind Konstanten, Java 2 Kompendium
445
Kapitel 9
Grafik und Animation die in der Font-Klasse definiert sind und die Sie über die übliche Punktnotation ansprechen können. Die Punktgrö ß e ist die Grö ß e der betreffenden Schriftart entsprechend der im Schrifttyp enthaltenen Definition. Wenn Sie eine Schrift auswählen, müssen Sie die Punktgrö ß e der Schrift angeben. Die Punktgrö ß e ist ein Begriff aus dem Druckwesen, der sich auf die Grö ß e der Schrift bezieht. Es gibt 100 Punkte pro Inch (dots per inch = dpi) bei einem Drucker, diese Grö ß e gilt aber nicht zwangsläufig auch für den Bildschirm (Stichwort: verschiedene Bildschirmauflösungen). Ein typischer Punktgrößenwert für Drucktext ist 12 oder 14. Die Punktgrö ß e zeigt nicht die Anzahl der Pixel in der Höhe oder der Breite an. Es handelt sich vielmehr um einen vergleichenden Begriff. Eine Punktgrö ß e von 24 ist doppelt so groß wie die von 12. Um nun ein Fontobjekt zu erstellen, verwenden Sie die drei beschriebenen Fonteigenschaften als Argumente: den Schriftnamen die Schriftformatierung die Punktgrö ß e Es gilt folgende Syntax: Font f = new Font(,,)
Sie können in Java aus einer Vielzahl verschiedener Schriften auswählen. Es handelt sich um die auf den meisten Plattformen unterstützten Zeichensätzen. Sie können, wie wir schon gesehen haben, nicht nur zwischen verschiedenen Schriften auswählen, Sie können ebenso mehrere Schriftformatierungen auswählen. Beispielsweise Font.PLAIN (normale Schrift), Font.BOLD (fette Schrift) und Font.ITALIC (kursive Schrift). Diese Formatierungen können auch kombiniert werden, sodass Sie eine Schrift fett und kursiv formatieren können: Font.BOLD + Font.ITALIC. Die Konstanten repräsentieren so angeordnete int-Werte so, dass eine Addition einen neuen int-Wert ergibt, der eindeutig ist und sich nur aus dieser Kombination gegeben kann. Die folgende Deklaration erstellt eine Times-Roman-Schrift, die fett und kursiv ist und eine Punktgrö ß e von 12 hat: Font myFont = new Font("TimesRoman", Font.BOLD + Font.ITALIC, 12);
446
Java 2 Kompendium
Farbangaben
Kapitel 9
Das explizite Setzen eines Schrifttyps erfolgt mit der folgenden Methode: public abstract void setFont(Font font)
Hier ist ein etwas umfangreicheres Beispiel. import java.awt.Font; import java.awt.Graphics; public class FontTest extends java.applet.Applet { public void paint(Graphics g) { Font f = new Font("TimesRoman", Font.PLAIN, 18); Font fb = new Font("TimesRoman", Font.BOLD, 18); Font fi = new Font("TimesRoman", Font.ITALIC, 18); Font fbi = new Font("TimesRoman", Font.BOLD + Font.ITALIC, 18); Font f2 = new Font("Arial", Font.PLAIN, 10); Font f2b = new Font("Courier", Font.BOLD, 12); Font f2i = new Font("Arial", Font.ITALIC, 34); Font f22i = new Font("TimesRoman", Font.ITALIC, 20); g.setFont(f); g.drawString("Hier haben wir einen normalen (plain) Font – TimesRoman", 10, 25); g.setFont(fb); g.drawString("Hier haben wir einen fetten (bold) Font – TimesRoman", 10, 50); g.setFont(fi); g.drawString("Dies ist ein kursiver (italic) Font – TimesRoman", 10, 75); g.setFont(fbi); g.drawString("Hier haben wir einen fetten und kursiven (bold italic) Font – TimesRoman", 10, 100); g.setFont(f2); g.drawString("Hier haben wir einen wieder normalen (plain) Font in Schriftgrö ß e 10 – Arial", 10, 125); g.setFont(f2b); g.drawString("Hier haben wir einen bold Font in Schriftgrö ß e 12 – Courier", 10, 150); g.setFont(f2i); g.drawString("Groß und kursiv – Arial", 10, 200); g.setFont(f22i); g.drawString("Hier haben wir einen italic Font in Schriftgrö ß e 20 – TimesRoman", 10, 225); } }
Listing 9.19: Verschiedene Fonts
Bei Bedarf kann man mit der folgenden Methode das Font-Objekt abfragen: public abstract Font getFont()
Java 2 Kompendium
447
Kapitel 9
Grafik und Animation
Abbildung 9.17: Verschiedene Fonts
9.3.8
Abfragen der zur Verfügung stehenden Fonts
Mit einer speziellen Methode der Toolkit-Klasse können Sie die auf einem Computersystem zur Verfügung stehenden Fonts abfragen. Damit stellen Sie beispielsweise sicher, dass Sie keine Fonts verwenden, die auf der Zielplattform nicht vorhanden sind. Für einen solchen Fall können Sie einen DefaultFont setzen, der auf jeder Plattform zur Verfügung steht (etwa Courier). Die Methode public abstract String[] getFontList()
gibt ein Datenfeld zurück, das die Namen der verfügbaren Schriften enthält. Beispiel: fontList = getToolkit().getFontList();
Es kann allerdings sein, dass diese Methode nicht ganz zuverlässig arbeitet. Außerdem sollten Sie beachten, dass die folgenden Fontnamen mittlerweile als deprecated gelten und durch die zweitgenannten ersetzt werden sollten: TimesRoman (neu Serif) Helvetica (neu SansSerif) Courier (neu Monospaced)
9.3.9
Informationen über einen speziellen Font abfragen
Sie können bei Bedarf in Java über Fonts und Fontobjekte anhand einfacher Methoden Informationen abfragen, die auf alle Graphics- und Fontobjekte anzuwenden sind.
448
Java 2 Kompendium
Farbangaben
Kapitel 9
Einige Methoden sollen hier dem Namen nach aufgelistet werden. Sie sind mit einer Ausnahme alle aus der Font-Klasse. Nur die bereits erwähnte Methode getFont() gehört zur Graphics-Klasse. Methode
Beschreibung
getFont()
Ausgabe des gesetzten Fontobjekts.
getName()
Ausgabe des Namens von dem Font als Zeichenkette.
getSize()
Ausgabe der Größe des aktuellen Fonts als Ganzzahl.
getStyle()
Der Stil des aktuellen Fonts als Ganzzahl.
Tabelle 9.1: Abfrage von FontDetails
0 = normal 1 = fett 2 = kursiv 3 = fett + kursiv isPlain()
Rückgabe true oder false, wenn der Fontstil normal ist oder nicht.
isBold()
Rückgabe true oder false, wenn der Fontstil fett ist oder nicht.
isItalic()
Rückgabe true oder false, wenn der Fontstil kursiv ist oder nicht.
Wenn Sie mehr Informationen über den aktuellen Font wünschen, können Sie diverse Methoden in der Klasse FontMetrics nutzen. Dazu erstellen Sie am besten ein FontMetrics-Objekt und werten dieses dann mittels dieser FontMetrics-Methoden aus. Ihnen stehen dazu beispielsweise die folgenden Methoden zur Verfügung, die wir hier nur mit Namen angeben: Methode
Beschreibung
getAscent()
Ausgabe der Entfernung zwischen der Grundlinie und der oberen Grenze eines Buchstabens (des so genannten Aufstrichs).
getDescent()
Ausgabe der Entfernung zwischen der Grundlinie und der unteren Grenze eines Buchstabens (des so genannten Abstrichs). Etwa beim q oder y.
getLeading()
Ausgabe der Zeilenhöhe, d.h. des Abstands zwischen dem Abstrich der oberen und dem Aufstrich der darunter befindlichen Zeile.
Java 2 Kompendium
Tabelle 9.2: Weitergehende Informationen zu Fonts
449
Kapitel 9 Tabelle 9.2: Weitergehende Informationen zu Fonts (Forts.)
Grafik und Animation
Methode
Beschreibung
getHeight()
Ausgabe der Gesamthöhe der Schrift, also der Summe von Aufstrich, Zeilenabstand und Abstrich.
stringWidth()
Die volle Breite der Zeichenkette in Pixel.
charWidth()
Die Breite eines bestimmten Zeichens.
9.4
Die Java-Zeichenmodi
Die Graphics-Klasse verfügt über zwei verschiedene Modi, um Figuren zu zeichnen: den paint-Modus den XOR-Modus
9.4.1
Der Paint-Modus
Das Arbeiten im Paint-Modus bedeutet, dass eine neue Zeichnung alle Punkte einer bereits dort vorhandenen Abbildung überschreiben. Dies muss aber nicht zwingend so sein. Es gibt noch einen weiteren Zeichenmodus namens XOR, was für »eXclusive OR« steht.
9.4.2
Der XOR-Zeichenmodus
Den XOR-Zeichenmodus kombiniert das Pixel, das Sie zeichnen möchten, und das Pixel, das sich an der Stelle auf dem Bildschirm befindet, an der Sie zeichnen möchten. Wenn Sie ein weißes Pixel an einer Stelle zeichnen möchten, wo sich gegenwärtig ein schwarzes Pixel befindet, werden Sie dort ein weißes Pixel zeichnen. Wenn Sie ein weißes Pixel an einer Stelle zeichnen möchten, wo bereits ein weißes Pixel ist, werden Sie ein schwarzes Pixel zeichnen. Dies wird Sie vielleicht überraschen, aber denken Sie bitte einmal an die bitweisen Operatoren zurück. Auch dort hatten wir das Umkehren von Bits schon vorgefunden. Hier ist nun eine praktische Anwendung. In frühen Animationen (etwa auf Schwarz-Weiß-Basis) hat man diese Technik viel genutzt. Um den Zeichenmodus in den XOR-Modus zu verändern, rufen Sie den setXOR-Modus auf und geben Sie an ihn die Farbe weiter, die Sie als XORFarbe verwenden wollen. Dazu dient die Methode public abstract void setXORMode(Color c1).
450
Java 2 Kompendium
Zeichnen von Bildern
Kapitel 9
Beispiel: public void paint(Graphics g) { g.setXORMode(getBackground()); g.fillRect(40, 10, 40, 40); g.fillOval(0, 0, 30, 30); }
9.5
Listing 9.20: Einsatz des XORModus
Zeichnen von Bildern
Wir sind nun bei einem ganz spannenden Abschnitt der Java-Grafikfähigkeiten angelangt. Dabei besteht der Vorgang aus zwei getrennten Schritten: Laden von Bildern Ausgeben von Bildern
9.5.1
Laden von Bildern – getImage()
Um ein Bild anzeigen zu können, müssen Sie sich zuerst das Bild von irgendwo holen. Irgendwo kann ein lokaler Rechner, aber ebenso das Netz sein. Dies wird nicht von der Graphics-Klasse erledigt, sondern beispielsweise mit der Methode public Image getImage(URL url),
die zur Applet-Klasse gehört. Wir haben diese Methode auch schon im Rahmen eines Beispiels im Kapitel über die Applet-Erstellung verwendet. Die Methode lädt ein Bild und erstellt automatisch eine Instanz der ImageKlasse. Um sie zu verwenden, brauchen Sie nur der Methode die Adresse (URL) des Bildes übergeben. Von dieser Methode gibt es diverse Varianten – sogar in der Applet-Klasse. Einmal mit einem Argument (ein Objekt vom Typ URL) und einmal mit zwei Argumenten (ein Objekt vom Typ URL und eine Zeichenkette, die den Pfad oder Dateinamen des Bildes in Bezug zu einer Basis enthält). Die einfachere Variante mit einem Argument entspricht einer hartcodierten Pfadangabe auf das Bild und ist deshalb ziemlich unflexibel. Wenn beispielsweise das Projekt in ein anderes Verzeichnis kopiert wird oder sich das Projektverzeichnis sonst ändert, muss das Projekt neu kompiliert werden. Die zweite Variante mit den zwei Argumenten lässt sich in Verbindung mit zwei weiteren Methoden der Applet-Klasse äußerst anpassungsfähig einsetzen, die wir bereits kennen:
Java 2 Kompendium
451
Kapitel 9
Grafik und Animation Die Methode getDocumentBase() zur Rückgabe eines Objekts vom Typ URL, das das Verzeichnis der HTML-Datei enthält, die das Applet eingebunden hat. Die Methode getCodeBase() zur Rückgabe der URL des Applets. Wir können nun getImage() zum Holen von Bildern sehr flexibel einsetzen. Beispiele: Image bild = getImage(getDocumentBase(), "Bild1.gif") Image bild = getImage(getCodeBase(), "Bild2.gif")
Das erste Beispiel lädt das Bild aus dem Verzeichnis, wo sich die HTMLDatei befindet, das zweite Beispiel sucht das Bild in dem Verzeichnis, wo das Applet platziert ist. Kann Java eine angegebene Datei nicht finden, gibt getImage() den Wert null aus. Dies ist im Prinzip kein Problem, das Programm läuft weiter, es wird nur kein Bild angezeigt.
9.5.2
Anzeigen von Bildern – drawImage()
Die Graphics-Klasse ermöglicht es, Bilder mit der drawImage()-Methode darzustellen. Wir haben diese Methode schon verwendet. Auch drawImage() gibt es in mehreren Varianten: boolean drawImage(Image img, int x, int y, ImageObserver observer) boolean drawImage(Image img, int x, int y, int width, int height, ImageObserver observer)
Der erste Parameter in beiden Varianten ist das Bild selbst. Parameter 2 und 3 sind die Koordinatenangaben für die linke obere Ecke des Anzeigebereichs. Der observer-Parameter hat die Aufgabe, zu beobachten, wann das Bild gezeichnet werden kann. Wenn Sie drawImage() innerhalb eines Applets aufrufen, können Sie dieses selbst als Observer (mit dem this-Operator) übergeben, da die Applet-Klasse die ImageObserver-Schnittstelle eingebaut hat. Dazu gleich noch etwas mehr. Die Parameter width und height in der zweiten Variante dienen dazu, die Breite und die Höhe des Kastens festzulegen, in dem das Bild gezeichnet wird. Sind die Argumente für diese Grö ß enangaben der Bildbox kleiner oder grö ß er als das Bild selbst, wird das Bild automatisch skaliert, d.h. es wird an die Grö ß e der Bildbox angepasst. Man kann diese Parameter zu einer bewussten Verzerrung von Bildern nutzen, um damit Effekte zu erzielen. 452
Java 2 Kompendium
Zeichnen von Bildern
Kapitel 9
Beispiel: g.drawImage(Bild1 ,10 , 10, this);
Erstellen wir ein vollständiges Programmbeispiel. import java.awt.Image; import java.awt.Graphics; public class DrawImage2 extends java.applet.Applet { Image samImage; Image bild; public void init() { // Bild laden – ein jpg-File samImage = getImage(getDocumentBase(), "images/Safety.jpg"); } public void paint(Graphics g) { g.drawImage(samImage, 0, 0, this); } }
Listing 9.21: Ein Bild in einem Applet
Abbildung 9.18: Für die Darstellung eines Fotos ist das JPG/JPEG-Format besser als das GIFFormat.
Java 2 Kompendium
453
Kapitel 9
Grafik und Animation Wenn man Verzerrungen bei der Verwendung der zweiten Variante vermeiden möchte, stellt die Image-Klasse zwei Methoden zur Verfügung, die die tatsächliche Grö ß e des zu ladenden Bildes herausfinden können. Damit kann man beispielsweise über einen prozentualen Wert der Breite und Höhe das Bild verkleinern oder vergrö ß ern. Es handelt sich um die Methoden public abstract int getWidth(ImageObserver observer)
und public abstract int getHeight(ImageObserver observer),
die in dieser Variante als Argument eine Instanz von dem Imageobserver (meist this) besitzen. Bei animierten GIFs werden diese Methoden unter Umständen nicht zuverlässig sein, denn dort können sich einzelne Sequenzen in der Grö ß e unterscheiden. Listing 9.22: Größ enveränderungen, Verzerrungen und Überlagerungen
454
/* Laden und Zeichnen eines Bildes, das in mehrfacher Hinsicht verkleinert und vergö ß ert sowie verzerrt und überlagert wird */ import java.awt.Image; import java.awt.Graphics; public class DrawImage3 extends java.applet.Applet { Image bild; public void init() { // Bild laden bild = getImage(getCodeBase(), "images/kuerb.gif"); } public void paint(Graphics g) { int bildbreite = bild.getWidth(this); int bildhoehe = bild.getHeight(this); int xpos = 10; int ypos = 10; g.drawImage(bild, xpos, ypos, bildbreite , bildhoehe, this); xpos += bildbreite + 10; ypos += bildhoehe + 10; g.drawImage(bild, xpos , ypos, bildbreite / 2, bildhoehe / 2, this); xpos += bildbreite + 10; ypos += bildhoehe + 10; g.drawImage(bild, xpos , ypos, bildbreite * 3, bildhoehe * 3, this); xpos += (bildbreite * 3) + 10; g.drawImage(bild, xpos , ypos, bildbreite * 3, bildhoehe , this); xpos += bildbreite + 10; ypos += bildhoehe + 10; g.drawImage(bild, xpos , ypos, bildbreite, bildhoehe, this); g.drawImage(bild, xpos + 20 , ypos + 10, bildbreite, bildhoehe, this); g.drawImage(bild, xpos + 50 , ypos + 20, bildbreite, bildhoehe, this); }} Java 2 Kompendium
Zeichnen von Bildern
Kapitel 9 Abbildung 9.19: Verschiedene Darstellungen desselben Bilds
9.5.3
Der Imageobserver und der MediaTacker
Wollen wir uns noch ein bisschen näher mit dem Imageobserver beschäftigen. Dieser hat die Aufgabe, zu beobachten, wann das Bild gezeichnet werden kann. Aber was bedeutet das eigentlich? Es kann (und wird in der Internet-Praxis) häufig vorkommen, dass zu ladende Bilder über das Netzwerk sehr langsam transportiert werden. Da wir Multithreading zur Verfügung haben, kann es passieren, dass Sie anfangen, das Bild zu zeichnen, obwohl es möglicherweise noch nicht vollständig angekommen ist. Um zu sehen, ob ein Bild schon bereit ist, um angezeigt zu werden, steht Ihnen die Hilfsklasse MediaTracker zur Verfügung. Um einen solchen MediaTracker zu verwenden, müssen Sie zuerst einen für Ihr Applet erzeugen. Das ist mit der folgenden Syntax möglich: MediaTracker myTracker = new MediaTracker(this);
Wenn Sie im nächsten Schritt das Bild laden, verwenden Sie den getImage()Befehl. Beispielsweise: Image myImage = getImage("Image1.gif");
Um den MediaTracker nun anzuweisen, das Bild zu beobachten, übergeben Sie dem MediaTracker das Bild über eine numerische ID. Diese ID kann für mehrere Bilder verwendet werden. Sie können so mit einer einzigen ID sehen, ob eine ganze Gruppe von Bildern zur Anzeige bereit ist. Im einfachsten Fall können Sie einem Bild die ID Null geben: myTracker.addImage(myImage, 0);
Java 2 Kompendium
455
Kapitel 9
Grafik und Animation Damit ist das Bild dem MediaTracker zum Beobachten zugewiesen. Wenn Sie einmal begonnen haben, ein Bild zu suchen, können sie es laden und mit der Methode public void waitForID(int id) throws InterruptedException
warten, bis es fertig geladen ist: myTracker.waitForID(0);
Sie können auch mit der Methode public void waitForAll() throws InterruptedException
auf alle Bilder warten: myTracker.waitForAll();
Sie wollen vielleicht nicht die ganze Zeit warten, bis ein Bild geladen ist, bevor Ihr Applet startet. Sie können dann mit der Methode public int statusID(int id, boolean load)
bereits anfangen zu laden. Wenn Sie statusID() aufrufen, übergeben Sie die ID, für die Sie einen Status haben wollen, und einen booleschen Operator, der angibt, ob der Ladevorgang für das Bild starten soll oder nicht. Wenn Sie true übergeben, wird das Bild geladen: myTracker.statusID(0, true);
In Verbindung mit statusID() steht public int statusAll(boolean load),
das den Status aller Bilder im MediaTracker überprüft: myTracker.statusAll(true);
Die statusID()- und die statusAll()-Methoden geben einen int-Wert zurück, der aus den folgenden Flags erstellt wird: MediaTracker.ABORTED, wenn das Laden von Bildern abgebrochen
wurde. MediaTracker.COMPLETE, wenn Bilder komplett geladen wurden.
456
Java 2 Kompendium
Animationen
Kapitel 9
MediaTracker.LOADING, wenn sich Bilder noch im Prozess des Ladens
befinden. MediaTracker.ERRORED, wenn ein Fehler beim Laden der Bilder aufgetre-
ten ist. Sie können auch mittels checkID() und checkAll() überprüfen, ob ein Bild vollends geladen worden ist. Alle Variationen von checkAll() und checkID() geben einen booleschen Wert zurück, der true ist, wenn alle überprüften Bilder geladen worden sind. Folgende Varianten sind von Bedeutung: Die Version boolean checkID(int id) gibt den Wert true zurück, wenn das Bild mit einer spezifischen ID geladen worden ist. Sie startet den Ladevorgang nicht, wenn das Bild nicht schon geladen wird. Die Version boolean checkID(int id, boolean startLoading) wird true zurückgegeben, wenn das Bild mit einer spezifischen ID geladen worden ist. Wenn startLoading true ist, wird es den Ladevorgang für das Bild starten, das noch nicht geladen worden ist. Die Methode boolean checkAll() wird true zurückgeben, wenn alle Bilder, die der MediaTracker überprüft, geladen worden sind. Der Ladevorgang wird nicht gestartet, wenn ein Bild nicht schon geladen wird. In der Version boolean checkAll(boolean startLoading) wird true zurückgegeben, wenn alle Bilder, die der MediaTracker überprüft, geladen worden sind. Wenn startLoading den Wert true hat, wird es alle Bilder laden, deren Ladevorgang noch nicht gestartet worden ist. Wir werden in dem am Ende des Kapitels folgenden Applet einen MediaTracker verwenden, der einen Ladevorgang bei einer Animation überwacht. Vorher wollen wir noch einige Animationstechniken durchsprechen.
9.6
Animationen
Unter einer Animation versteht man erst einmal nur ein Aneinanderreihen von Bildern, die meist so schnell angezeigt werden, dass für das menschliche Auge der Eindruck von Bewegung entsteht. Eine Animation umfasst in Java zwei wesentliche Schritte: Aufbau eines Animationsrahmens Abspielen der Animation
Java 2 Kompendium
457
Kapitel 9
Grafik und Animation
9.6.1
Aufbau eines Animationsrahmens
Hierunter ist alles zu verstehen, was die Animation vorbereitet: Ermitteln der Grö ß e des Ausgabebereichs Farbeinstellungen Positionieren der Animation Erstellen oder Laden der einzelnen Animationsbilder Aufbau von einzelnen Animationssequenzen Außer dem letzten Punkt ist nichts dabei, was neu für uns ist. Und auch der letzte Punkt der Aufzählung lässt sich als das Zusammenfassen von einzelnen Bildern in Gruppen leicht erklären. Das Abspielen einer Animation ist jedoch wirklich neu und damit werden wir uns jetzt beschäftigen.
9.6.2
Abspielen einer Animation
Eine Animation kann ganz einfach aufgebaut sein. Wenn wir beispielsweise die Position eines Bildes in kleinen Schritten über den Ausgabebereich verschieben, haben wir bereits eine – zugegeben nicht sonderlich innovative – Animation. Wenn wir dabei zusätzlich die Grö ß e noch (geringfügig, sonst wird es zu viel ruckeln) verändern (grö ß er, wenn sich das Objekt auf den Betrachter hin bewegen soll und kleiner, wenn sich das Objekt von dem Betrachter weg bewegen soll), wird die Sache schon etwas spannender. Bauen wir ein Applet entsprechend um. Die Animationen verwenden Bilder, die sich innerhalb des Unterverzeichnisses images befinden.
Listing 9.23: Eine erste Animation
458
import java.awt.Image; import java.awt.Graphics; public class Animation1 extends java.applet.Applet { Image bild; public void init() { // Bild laden bild = getImage(getCodeBase(), "images/kuerb.gif"); resize(600, 200); } public void paint(Graphics g) { for (int i=0; i < 1000; i++) { int bildbreite = bild.getWidth(this); int bildhoehe = bild.getHeight(this);
Java 2 Kompendium
Animationen
Kapitel 9
int xpos = 10; // Startposition X int ypos = 10; // Startposition Y g.drawImage(bild, (int)(xpos + (i/2)) , (int)(ypos + (i/10)), (int)(bildbreite * (1 + (i/1000))),(int) (bildhoehe * (1 + (1/1000))), this); } } }
Wenn Sie das Applet laufen lassen, werden Sie sehen, dass sich der Kürbis von links nach rechts über den Bildschirm bewegt. Allerdings werden Sie gleich bemerken, dass sich dabei ein paar Effekte einstellen, die wahrscheinlich so nicht gewünscht sind. Das Flimmern werden wir später betrachten, zuerst kümmern wir uns darum, die jetzt noch gezogene Spur zu beseitigen. Wie das geht, wissen Sie bereits. Wir benötigen die repaint()-Methode. Abbildung 9.20: Immerhin bewegt sich was.
Allerdings werden Sie beim Umbau des Applets schnell bemerken, dass der Aufruf der repaint()-Methode nirgends so richtig reinpasst. Wir müssen offensichtlich noch mehr tun. Der erste Schritt könnte sein, die for-Schleife in die Startmethode zu verlegen. Zusätzlich müssen dann die Variablen bildbreite, bildhoehe, xpos und ypos aus der paint()-Methode an zentrale Stellen verlagert werden. Der zweite Denkansatz ist okay, der erste nicht. Zwar kann man das Applet tatsächlich so umkonstruieren. Der Aufruf der repaint()-Methode innerhalb der for-Schleife wird jedoch enttäuschend sein. Die Lösung liegt auf der Hand. Wir müssen zum einen die for-Schleife abarbeiten lassen, zum anderen während jedes Schleifendurchgangs über die repaint()-Methode eine Ausgabe aufrufen. Das sind zwei Vorgänge, die quasi gleichzeitig ablaufen. Und wie nennt man sowas? Multithreading. Wir haben hier eine klassische Anwendung von Multithreading. Im Prinzip ist jede Animation nur als Multithreading-Anwendung vernünftig zu konstruieren.
Java 2 Kompendium
459
Kapitel 9
Grafik und Animation Machen wir also unser Applet Multithreading-fähig und verlagern die forSchleife in die dann notwendige run()-Methode.
Listing 9.24: Schon besser, die Animation läuft sauber.
import java.awt.Image; import java.awt.Graphics; public class Animation2 extends java.applet.Applet implements Runnable { Image bild; int bildbreite; int bildhoehe; int xpos = 10; // Startposition X int ypos = 10; // Startposition Y Thread MeinThread; public void init() { // Bild laden bild = getImage(getCodeBase(), "images/kuerb.gif"); bildbreite = bild.getWidth(this); bildhoehe = bild.getHeight(this); } public void paint(Graphics g) { g.drawImage(bild, xpos, ypos ,bildbreite,bildhoehe, this); } public void run() { for (int i=0; i < 1000; i++) { xpos = (int)(xpos + (i/4)); ypos=(int)(ypos + (i/10)); bildbreite = (int)(bildbreite * (1 + (i/500))); bildhoehe= (int) (bildhoehe * (1 + (1/500))); repaint(); pause(100); } } public void start() { if (MeinThread == null) { MeinThread = new Thread(this); MeinThread.start(); } } public void stop() { if (MeinThread != null) { MeinThread.stop(); MeinThread = null; } } void pause(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { } } }
Sie werden sehen, dass hier schon eine recht vernünftige Bewegung zu erkennen ist. Das Bild wird übrigens zeilenweise verschoben, wie in der 460
Java 2 Kompendium
Animationen
Kapitel 9 Abbildung 9.21: Der Kürbis läuft von links oben nach rechts unten.
nächsten Abbildung einer Animation mit einer anderen Grafik recht gut zu erkennen ist. Abbildung 9.22: Die Momentaufnahme macht den Aufbau der Bildverlagerung gut deutlich.
Der Appletviewer des JDK ab der Version 1.2 macht bei der Darstellung der und den folgenden Animationen unter Umständen Schwierigkeiten. Sie müssen unter Umständen den Reload-Befehl auslösen, bevor Sie etwas sehen. Ansonsten läuft die Animation aber in allen anderen Referenzdarstellungsmedien. Sowohl in verschiedenen Browsern, als auch Appletviewern der Vorgängerversionen des JDK gibt es keine Probleme. Zwar verwenden wir mit der Methode stop() für die Threads eine als deprecated gekennzeichnete Methode, aber dies sollte keine Rolle spielen.
Java 2 Kompendium
461
Kapitel 9
Grafik und Animation Erweitern wir das Applet jetzt noch ein wenig und wechseln gleichzeitig die verwendete Grafik (eine Erdkugel). Wir lassen die Erdkugel vom rechten Rand wieder zurückkommen und platzieren sie dann zurück an die Orginalposition. Die ganze Aktion packen wir in eine Endlos-Schleife und haben damit eine permanent laufende Animation. Durch die MultithreadingFähigkeit des Applets brauchen wir uns keine Sorgen zu machen, die Endlosschleifen nicht unterbrechen zu können (eigentlich bietet die Appletklasse schon genug Abbruchmöglichkeiten, aber wir wollen sauber arbeiten).
Listing 9.25: Eine EndlosAnimation
462
import java.awt.*; import java.util.*; public class Animation3 extends java.applet.Applet implements Runnable Image bild; int bildbreite; int bildhoehe; int bildbreite_anfang; int bildhoehe_anfang; int xpos = 10; // Startposition X int ypos = 10; // Startposition Y Thread MeinThread; public void init() { // Bild laden bild = getImage(getCodeBase(), "images/img0001.gif"); bildbreite = bild.getWidth(this); bildhoehe = bild.getHeight(this); bildbreite_anfang=bildbreite; bildhoehe_anfang=bildhoehe; setBackground(Color.cyan); // Hintergrundfarbe resize(850, 450); } public void paint(Graphics g) { /* Wir verfolgen die Koordinaten der Erdkugel am Bildschirm über die Methode drawString mit. Dazu müssen wir diese vom Typ Interger in String konvertieren. Dies geschieht mit der toString-Methode. */ String stng = Integer.toString(xpos); // Die Ausgabe der X-Koordinate g.drawString("X-Koordinate: " + stng, 350,20); stng = Integer.toString(ypos); // Die Ausgabe der Y-Koordinate g.drawString("Y-Koordinate: " + stng, 500,20); // Bild-Ausgabe g.drawImage(bild, xpos, ypos ,bildbreite,bildhoehe, this); } public void run() { /* 4 lokale Variablen (eine davon als Konstante definiert), um den Rückweg der Erdkugel zu steuern. */ double schrittX=0; final double schrittY = 1.172413793103; int umkehrX=0;
Java 2 Kompendium
Animationen
Kapitel 9
int umkehrY=0; while (true) // Beginn Endlosschleife { for (int i=0; i < 80; i++) { // Laufe von links oben nach rechts unten xpos = (int)(xpos + (i/4)); ypos=(int)(ypos + (i/10)); bildbreite = (int)(bildbreite * (1 + (i/500))); bildhoehe= (int) (bildhoehe * (1 + (i/500))); repaint(); pause(100); } // Ende erste For-Schleife // Nun kehre die vertikale Richtung um und laufe // zurück nach links, aber immer noch nach unten. // Die Animationsgeschwindigkeit wird // beschleunigt durch kürzere Pausen. for (int i=0; i < 40; i++) { xpos = (int)(xpos – (i/4)); ypos=(int)(ypos + (i/10)); bildbreite = (int)(bildbreite * (1 – (i/500))); bildhoehe= (int) (bildhoehe * (1 – (i/500))); repaint(); pause(25); } // Ende zweite for-Schleife /* Nun kehre die horizontale Richtung auch um und laufe zurück nach links oben. Dazu nehmen wir für die X-Koordinate die aktuelle Position und die Anfangskoordinaten und berechnen daraus die Schrittgrö ß en für einen Rückweg in 290 Einzelsequenzen. Die Y-Schritte sind vorher berechnet und als Konstante gesetzt (Verwendung von final). Die Pausen werden nochmal verkürzt, aber durch die höhere Anzahl der Einzelsequenzen verlangsamt sich die Bewegung der Erdkugel.*/ umkehrX=xpos; umkehrY=ypos; schrittX = (xpos – 10) / 290 ; for (int i=0; i < 290; i++) { xpos = (int)(umkehrX – (i * schrittX)); ypos=(int)(umkehrY- (i * schrittY)); bildbreite = (int)(bildbreite * (1 – (i/500))); bildhoehe= (int) (bildhoehe * (1 – (i/500))); repaint(); pause(10); } // Ende dritte for-Schleife /* Kleinere Nachjustierungen, um Rechenungenauigkeiten auszugleichen. Sehen tun Sie nichts davon, das geht hamonisch über. */ xpos = 10; ypos = 10; bildbreite=bildbreite_anfang; bildhoehe=bildhoehe_anfang; repaint(); }//Ende der while-Schleife
Java 2 Kompendium
463
Kapitel 9
Grafik und Animation } public void start() { if (MeinThread == null) { MeinThread = new Thread(this); MeinThread.start(); } } public void stop() { if (MeinThread != null) { MeinThread.stop(); MeinThread = null; } } void pause(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { } } // Hier folgt die Mausaktion, mit der das Applet // gestoppt werden kann. public boolean mouseDown(Event evt, int x, int y) { stop(); return true; } }
Abbildung 9.23: Die Animation läuft solange, bis Sie die Maustaste klicken oder das Fenster schließen.
Die Erdkugel läuft zuerst von der linken oberen Ecke des Fensters an den rechten Rand (ungefähr die Mitte der Fensterhöhe), dann zurück nach links, jedoch immer noch nach unten, um dann wieder noch oben abzudrehen. Die Geschwindigkeit der Bewegung verändert sich, da wir nicht mit gleich großen Verschiebungen in der einzelnen Bewegungssequenzen arbeiten.
464
Java 2 Kompendium
Animationen
Kapitel 9
Wenden wir uns noch einer anderen Art der Animation zu. Wir werden dabei nicht nur ein Bild verwenden, das über den Bildschirm bewegt wird, sondern eine Sequenz von Bildern, die durch nacheinander folgende Einblendungen den Eindruck von Bewegung vermitteln. Dabei soll es auf Mausaktionen (stoppen) reagieren, Multithreading-fähig sein, einen MediaTracker verwenden und als Hauptfunktion eine Reihe von Bildern laden und diese dann in Form einer Endlosschleife als bewegte Animation ausgeben. Der Source wird vollständig mit Kommentaren dokumentiert, sodass er sicher verständlich bleibt. import java.awt.*; import java.util.*; public class Animation4 extends java.applet.Applet implements Runnable { Image bild; int bildbreite; int bildhoehe; int xpos; int ypos; // Nummer des aktuellen Bildes private int AktuellesBild; // Das Array wird die zu ladenden Bilder aufnehmen private Image m_Images[]; Thread MeinThread; // Anzahl der zu ladenden Bilder private final int ANZAHL_BILDER = 18; // Kontrollvariable, ob alle Bilder geladen wurden. private boolean m_fAllLoaded = false; // Eine Instanz von Graphics, die für diverse // Ausgabeaktionen genutzt wird. private Graphics ausgabe; public void init() { setBackground(Color.green); // Hintergrundfarbe resize(250, 250); } public void paint(Graphics g) { // Solange die Bilder noch geladen werden // (m_fAllLoaded ist in der Zeit false) // wird eine Meldung ausgegeben. Danach // (m_fAllLoaded ist dann true) // gibt paint das aktuelle Bild aus. //---------------------------------if (!m_fAllLoaded) { // Wenn die Testvariable, ob alle Bilder geladen // sind, false ist, wird abgebrochen g.drawString("Loading images...", 10, 20); return; } // Bild-Ausgabe g.drawImage(bild, xpos, ypos ,bildbreite,bildhoehe, this);
Java 2 Kompendium
Listing 9.26: Eine sich drehende Erdkugel
465
Kapitel 9
Grafik und Animation } public void run() { // Initialisierung der Anzahl der Bilder AktuellesBild = 0; /* Wenn die Webseite nach Verlassen (kein destroy, nur stop) wieder besucht wird, dann sind die Bilder immer noch geladen. Die Testvariable m_fAllLoaded ist also noch true. Andernfalls werden die Bilder geladen */ //-------------------------------------if (!m_fAllLoaded) { repaint(); ausgabe = getGraphics(); // Das Array wird 18 Elemente haben m_Images = new Image[ANZAHL_BILDER]; /* Wir verwenden hier einen MediaTracker, um den Ladevorgang der Bilder zu verfolgen. */ MediaTracker tracker = new MediaTracker(this); String strImage; /* Dieses Mal laden wir die Bilder nicht in der init()-Methode, sondern in der run()-Methode. Für jedes Bild in der Animation wird zuerst ein String mit dem Pfad zu dem Bild konstruiert; danach werden die Bilder in das m_ImagesArray geladen und damit dieses Array initialisiert. */ //-----------------------------------------for (int i = 1; i <= ANZAHL_BILDER; i++) { strImage = "images/img00" + ((i < 10) ? "0" : "") + i + ".gif"; m_Images[i-1] = getImage(getDocumentBase(), strImage); /* Das gerade geladene Bild wird dem MediaTracker als geladen gemeldet und steht für die Ausgabe damit bereit */ tracker.addImage(m_Images[i-1], 0); } // Exception-Behandlung bei Ladefehlern. try { tracker.waitForAll(); m_fAllLoaded = !tracker.isErrorAny(); } catch (InterruptedException e){ } // Fehlermeldung bei Ladefehler if (!m_fAllLoaded) { stop(); ausgabe.drawString("Error loading images!", 10, 40); return; } // Breite des aktuellen Bildes bildbreite = m_Images[0].getWidth(this); // Höhe des aktuellen Bildes bildhoehe = m_Images[0].getHeight(this); xpos=(size().width – bildbreite) / 2; ypos=(size().height – bildhoehe) / 2; } repaint();
466
Java 2 Kompendium
Animationen
Kapitel 9
while (true) // Beginn Endlosschleife { try // Exceptionhandling { bild=m_Images[AktuellesBild]; repaint(); // Zeige aktuelles Bild an AktuellesBild++; // Zähle Bildindex hoch // Wenn Bildindex das letzte Bild erreicht hat // setze den Zähler wieder auf 0 if (AktuellesBild == 18) AktuellesBild = 0; /* Unterbrechung von 50 Millisekunden. Ganz wichtig, denn in dieser Zeit können andere Aktionen laufen*/ Thread.sleep(50); } catch (InterruptedException e) { stop(); } }//Ende der while-Schleife } public void start() { if (MeinThread == null) { MeinThread = new Thread(this); MeinThread.start(); } } public void stop() { if (MeinThread != null) { MeinThread.stop(); MeinThread = null; } } void pause(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { } } // Hier folgt die Mausaktion, mit der das Applet // gestoppt werden kann. public boolean mouseDown(Event evt, int x, int y) { stop(); return true; }}
Die Animation zeigt eine sich drehende Erdkugel, die auf einer Position verbleibt. Ihnen werden sicher die Flimmereffekte aufgefallen sein, die die meisten der Animationen beeinflusst haben. Wir wollen deshalb unsere Animationstechnik ein wenig optimieren.
Java 2 Kompendium
467
Kapitel 9
Grafik und Animation
Abbildung 9.24: Ein komplexeres Applet mit der Animation einer drehenden Erdkugel
9.6.3
Flimmereffekte in Animationen reduzieren
Der Hauptgrund für das Flimmern bei Animationen liegt oft in der repaint()-Methode. Diese ruft vor der Ausführung der paint()-Methode die update()-Methode auf (die genau genommen erst die paint()-Methode anschließend aufruft) und da ist das Flimmerproblem häufig angesiedelt. Die update()-Methode leert in der Orginalform den ganzen Bildschirm (genauer – den Anzeigebereich des Applets). Die Teile des Bildschirms, die sich nicht ändern, werden danach schneller aufgebaut, als diejenigen, die neu gezeichnet werden müssen. Daraus resultiert der Flimmereffekt. Um die update()-Methode daran zu hindern, den Bildschirm jedes Mal zu leeren, können wir sie überschreiben und erreichen damit in vielen Fällen eine Reduzierung des Flimmereffekts. Überschreiben der update()-Methode Bei Animationen, wo es nicht notwendig ist, den Bildschirm vor einer neuen Ausgabe zu leeren (dies ist dann der Fall, wenn sich keine grundlegenden Angaben wie die Farbe verändern oder Bereiche geleert werden müssen), führt die folgende kleine Aktion zu einer erheblichen Reduzierung des Flimmerns. public void update(Graphics g) { paint(g); }
Mit dieser kleinen Veränderung wird der Bildschirm vor dem Aufruf der paint()-Methode überhaupt nicht mehr geleert. Wir werden nun eine Animation damit so optimieren, dass das – ohne die überschriebene update()-Methode vorhandene – Flimmern fast vollständig verschwindet.
468
Java 2 Kompendium
Animationen
Kapitel 9
Die Animation mit der sich drehenden Erdkugel ist ein perfekter Kandidat. Bauen Sie dort die überschriebene update()-Methode ein und das Flimmern verschwindet fast vollständig. Die Lademeldung (»Error loading images!«) müssen Sie jedoch – falls sie stört – von Hand beseitigen, da sie in dem Beispiel außerhalb des neu aufgebauten Bildschirmbereichs steht. Das Überschreiben der update()-Methode hat jedoch Grenzen, wie wir gleich sehen werden. Setzen wir nun zwei unserer letzten Animationen zusammen und ergänzen sie etwas. Unter anderem wird auch die nachfolgend noch beschriebene Clipping-Technik verwendet. import java.awt.*; import java.util.*; public class Animation5 extends java.applet.Applet implements Runnable { Image bild; int bildbreite; int bildhoehe; int bildbreite_anfang; int bildhoehe_anfang; int xpos = 10; // Startposition X int ypos = 10; // Startposition Y Thread MeinThread; // Nummer des aktuellen Bildes private int AktuellesBild; // Das Array wird die zu ladenden Bilder aufnehmen private Image m_Images[]; // Anzahl der zu ladenden Bilder private final int ANZAHL_BILDER = 18; // Kontrollvariable, ob alle Bilder geladen // wurden. private boolean m_fAllLoaded = false; // Eine Instanz von Graphics, die für diverse // Ausgabeaktionen genutzt wird. private Graphics ausgabe; public void init() { setBackground(Color.cyan); // Hintergrundfarbe resize(850, 450); } public void paint(Graphics g) { /* Solange die Bilder noch geladen werden (m_fAllLoaded ist in der Zeit false), wird eine Meldung ausgegeben. Danach (m_fAllLoaded ist dann true) gibt paint das aktuelle Bild aus. */ //------------------------------------------/* Wenn die Testvariable, ob alle Bilder geladen sind, false ist, wird abgebrochen */ if (!m_fAllLoaded) { g.drawString("Loading images...", 10, 20);
Java 2 Kompendium
Listing 9.27: Drehen und Verschieben
469
Kapitel 9
Grafik und Animation return; } g.clipRect(xpos ,ypos,bildbreite,bildhoehe); // Bild-Ausgabe g.drawImage(bild, xpos, ypos ,bildbreite,bildhoehe, this); /* Wir verfolgen die Koordinaten der Erdkugel am Bildschirm über die Methode drawString mit. Dazu müssen wir diese vom Typ Interger in String konvertieren. Dies geschieht mit der toString-Methode. */ g.clipRect(250 ,0,700,50); String stng = Integer.toString(xpos); // Die Ausgabe der X-Koordinate g.drawString("X-Koordinate: " + stng, 350,20); stng = Integer.toString(ypos); // Die Ausgabe der Y-Koordinate g.drawString("Y-Koordinate: " + stng, 500,20); stng = Integer.toString(AktuellesBild); // Die Ausgabe der Bild-Nr. g.drawString("Bild-Nr.: " + stng, 250,20); } public void run() { /* 4 lokale Variablen (eine davon als Konstante definiert), um den Rückweg der Erdkugel zu steuern. */ double schrittX=0; final double schrittY = 1.172413793103; int umkehrX=0; int umkehrY=0; // Initialisierung der Anzahl der Bilder AktuellesBild = 0; /* Wenn die Webseite nach Verlassen (kein destroy, nur stop) wieder besucht wird, dann sind die Bilder immer noch geladen. Die Testvariable m_fAllLoaded ist also noch true. Andernfalls werden die Bilder geladen */ //---------------------------------if (!m_fAllLoaded) { repaint(); ausgabe = getGraphics(); // Das Array wird 18 Elemente haben m_Images = new Image[ANZAHL_BILDER]; /* Wir verwenden hier einen MediaTracker, um den Ladevorgang der Bilder zu verfolgen. */ MediaTracker tracker = new MediaTracker(this); String strImage; /* Dieses Mal laden wir die Bilder nicht in der init()-Methode, sondern in der run()-Methode. Für jedes Bild in der Animation wird zuerst ein String mit dem Pfad zu dem Bild konstruiert; danach werden die Bilder in das m_ImagesArray geladen und damit dieses Array initialisiert. */ //---------------------------------for (int i = 1; i <= ANZAHL_BILDER; i++) { strImage = "images/img00" + ((i < 10) ? "0" : "") + i + ".gif"; m_Images[i-1] = getImage(getDocumentBase(), strImage); /* Das gerade geladene Bild wird dem MediaTracker als geladen gemeldet und
470
Java 2 Kompendium
Animationen
Kapitel 9
steht für die Ausgabe damit bereit */ tracker.addImage(m_Images[i-1], 0); } // Exception-Behandlung bei Ladefehlern. try { tracker.waitForAll(); m_fAllLoaded = !tracker.isErrorAny(); } catch (InterruptedException e) { } if (!m_fAllLoaded) // Fehlermeldung bei Ladefehler { stop(); ausgabe.drawString("Error loading images!", 10, 40); return; } // Breite des aktuellen Bildes bildbreite = m_Images[0].getWidth(this); // Höhe des aktuellen Bildes bildhoehe = m_Images[0].getHeight(this); bildbreite_anfang=bildbreite; bildhoehe_anfang=bildhoehe; } repaint(); while (true) // Beginn Endlosschleife { for (int i=0; i < 80; i++) { // Zuweisung des jeweiligen Bildes AktuellesBild = AktuellesBild + 1; // Wenn Bildindex das letzte Bild erreicht hat // setze Zähler wieder auf 0 if (AktuellesBild == 18) AktuellesBild = 0; bild=m_Images[AktuellesBild]; // Laufe von links oben nach rechts unten xpos = (int)(xpos + (i/4)); ypos=(int)(ypos + (i/10)); bildbreite = (int)(bildbreite * (1 + (i/500))); bildhoehe= (int) (bildhoehe * (1 + (i/500))); repaint(); pause(100); } // Ende erste For-Schleife /* Nun kehre die vertikale Richtung um und laufe zurück nach links, aber immer noch nach unten. Die Animationsgeschwindigkeit wird beschleunigt durch kürzere Pausen. */ for (int i=0; i < 40; i++) { /* Um in der Folge der Bilder keinen Sprung auftreten zu lassen, wird die Zählvariable beim letzten Stand weitergezählt. */ AktuellesBild = AktuellesBild + 1; // Wenn Bildindex das letzte Bild erreicht hat if (AktuellesBild == 18)
Java 2 Kompendium
471
Kapitel 9
Grafik und Animation AktuellesBild = 0; // setze Zähler wieder auf 0 bild=m_Images[AktuellesBild]; xpos = (int)(xpos – (i/4)); ypos=(int)(ypos + (i/10)); bildbreite = (int)(bildbreite * (1 – (i/500))); bildhoehe= (int) (bildhoehe * (1 – (i/500))); repaint(); pause(25); } // Ende zweite For-Schleife /* Nun kehre die horizontale Richtung auch um und laufe zurück nach links oben. Dazu nehmen wir für die X-Koordinate die aktuelle Position und die Anfangskoordinaten, und berechnen die daraus die Schrittgrö ß en für einen Rückweg in 290 Einzelsequenzen. Die Y-Schritte sind vorher berechnet und als Konstante gesetzt (Verwendung von final). Die Pausen werden nochmal verkürzt, aber durch die höhere Anzahl der Einzelsequenzen verlangsamt sich die Bewegung der Erdkugel.*/ umkehrX=xpos; umkehrY=ypos; schrittX = (xpos – 10) / 290 ; for (int i=0; i < 290; i++) { /* Um in der Folge der Bilder keinen Sprung auftreten zu lassen, wird die Zählvariable beim letzten Stand weitergezählt. */ AktuellesBild = AktuellesBild + 1; // Wenn Bildindex das letzte Bild erreicht hat if (AktuellesBild == 18) AktuellesBild = 0; // setze Zähler wieder auf 0 bild=m_Images[AktuellesBild]; xpos = (int)(umkehrX – (i * schrittX)); ypos=(int)(umkehrY- (i * schrittY)); bildbreite = (int)(bildbreite * (1 – (i/500))); bildhoehe= (int) (bildhoehe * (1 – (i/500))); repaint(); pause(10); } // Ende dritte For-Schleife /* Kleinere Nachjustierungen, um Rechenungenauigkeiten auszugleichen. Sehen tun Sie nichts davon, das geht harmonisch über. */ xpos = 10; ypos = 10; bildbreite=bildbreite_anfang; bildhoehe=bildhoehe_anfang; repaint(); }//Ende der while-Schleife } public void start() { if (MeinThread == null); { MeinThread = new Thread(this); MeinThread.start(); } } public void stop() { if (MeinThread != null) {
472
Java 2 Kompendium
Animationen
Kapitel 9
MeinThread.stop(); MeinThread = null; } } void pause(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { } } // Hier folgt die Mausaktion, mit der das Applet // gestoppt werden kann. public boolean mouseDown(Event evt, int x, int y){ stop(); return true; } }
Diese Animation wird sozusagen das Highlight unseres AnimationsAbschnitts. Zum einen wird die Erdkugel über den Bildschirm verschoben, zum anderen dreht sie sich dabei. Wir verfolgen die wichtigsten Positionsangaben und das jeweils angezeigte Bild am Bildschirm mit. Versuchen Sie jetzt einmal, die update()-Methode hier wie im letzten Beispiel zu überschreiben. Das Resultat wird nicht befriedigend sein (es sei denn, es soll ein spezieller Effekt erzielt werden). Abbildung 9.25: Ohne ein Neuzeichnen lassen sich manche Animationen nicht vernünftig darstellen.
Hier offenbart sich die Grenze des Überschreibens von der update()Methode. Ohne Überschreiben der update()-Methode flimmert der Bildschirm zwar, jedoch verschwinden zumindest die ungewollten Spuren der verschobenen Erdkugel. Es gibt jedoch noch eine andere Optimierungstechnik, um das Flimmerproblem anzugehen. Diese Lösung heißt Clipping.
Java 2 Kompendium
473
Kapitel 9
Grafik und Animation
9.6.4
Clipping
Clipping bezeichnet eine Technik bei grafischen Systemen, die beim Zeichnen von grafischen Objekten verhindern soll, dass ein Bereich den anderen überschreibt. Normalerweise wird der ganze Bildschirmbereich geleert oder der ganze Bereich ohne zu Leeren überschrieben. Sie können über die Clipping-Technik den Bereich, der geleert werden soll, begrenzen, um festzulegen, wo Sie innerhalb des Ausgabefensters etwas zeichnen wollen. Obwohl der ganze Bildschirm die Anweisung zum Nachzeichnen bekommt, wird nur der Teil innerhalb des Clipping-Bereichs neu gezeichnet. Bei einer sich in der Position nicht verändernden Animation – etwa der sich auf der Stelle drehenden Erdkugel – umrahmen Sie einfach die Erdkugel so knapp wie möglich. Aber was ist, wenn sich das Animationsobjekt über den Bildschirm bewegt? Sie müssen dabei den sich verändernden Bereich während der Animation verfolgen und den Clipping-Rahmen immer um das sich bewegende Objekt halten. Die Koordinaten des sich während der Animation verändernden Bereichs verwenden Sie dann in einer Methode, die der drawRect()-Methode zum Zeichen eines Rechtecks sehr ähnlich ist. Um die Grenzen für Ihren Clipping-Bereich zu setzen, verwenden Sie die Methode public abstract void clipRect(int x, int y, int width, int height).
Sie verwendet dazu wieder zwei Koordinaten-Tupel, die wie bei den drawRect()-Tupeln zu verstehen sind.
Das Tupel (x, y), das die linke obere Ecke eines Rechtecks bestimmt. Das Tupel (width, height), das die Breite und die Höhe des Rechtecks festlegt. Um beispielsweise einen Clipping-Bereich bei dem Punkt (150, 100) zu beginnen, der 200 Pixel breit und 120 Pixel hoch ist, würde der Methodenaufruf folgendermaßen aussehen: g.clipRect(150, 100, 200, 120);
Sie können die Clipping-Technik ebenso dazu verwenden, ein zu zeichnendes Objekt abzuschneiden, d.h., obwohl der eigentliche Zeichenbereich des Objekts grö ß er ist, wird auf dem Bildschirm nur innerhalb des ClippingRechtecks gezeichnet. Der restliche Bereich des Bildschirms bleibt unberührt.
474
Java 2 Kompendium
Animationen
Kapitel 9
Bei einer Animation müssen Sie also immer genau wissen, wo im Bildbereich ein Neuzeichnen Sinn macht. Wenn Sie dies im Griff haben, ergänzen Sie die paint()-Methode vor den eigentlichen Ausgaben. Sie sieht dann so aus: public void paint(Graphics g) { /* Solange die Bilder noch geladen werden (m_fAllLoaded ist in der Zeit false), wird eine Meldung ausgegeben. Danach (m_fAllLoaded ist dann true) gibt paint das aktuelle Bild aus. */ //---------------------------------------------if (!m_fAllLoaded) { /* Wenn die Testvariable, ob alle Bilder geladen sind, false ist, wird eine Meldung ausgegeben */ g.drawString("Loading images...", 10, 20); return; } g.clipRect(xpos ,ypos,bildbreite,bildhoehe); // Bild-Ausgabe g.drawImage(bild, xpos, ypos ,bildbreite,bildhoehe, this); /* Wir verfolgen die Koordinaten der Erdkugel am Bildschirm über die Methode drawString mit. Dazu müssen wir diese vom Typ Interger in String konvertieren. Dies geschieht mit der toString-Methode. */ g.clipRect(250 ,0,700,50); String stng = Integer.toString(xpos); // Die Ausgabe der X-Koordinate g.drawString("X-Koordinate: " + stng, 350,20); stng = Integer.toString(ypos); // Die Ausgabe der Y-Koordinate g.drawString("Y-Koordinate: " + stng, 500,20); stng = Integer.toString(AktuellesBild); // Die Ausgabe der Bild-Nr. g.drawString("Bild-Nr.: " + stng, 250,20); }
Listing 9.28: Die Clipping-Technik im Rahmen der paint()-Methode
Das Flimmern wird sich merklich verringern. Die Erdkugel ist von einem clipRect()-Kasten umgeben, der sie verfolgt und immer umgibt. Und nur der wird bei einem repaint()-Aufruf neu gezeichnet. Wenn Sie jetzt ein bisschen aufpassen, wird Ihnen auffallen, dass die Koordinatenangaben nicht mehr angezeigt werden. Warum das? Wir haben sie mit einem eigenen clipRect()-Kasten umgeben und dennoch sind sie weg. Dummerweise funktioniert die mehrfache Verwendung der clipRect()Methode nicht. Die Koordinatenausgaben liegen in dem nicht neu gezeichneten Bereich. Wir könnten zwar den clipRect()-Kasten erweitern, aber dann müssten wir fast den ganzen Bildschirm neu zeichnen lassen und die Reduzierung des Flimmerns – unser eigentliches Ziel – wäre verfehlt.
Java 2 Kompendium
475
Kapitel 9
Grafik und Animation Eine halbwegs befriedigende Lösung wäre eine exakte Berechnung des minimal notwendigen Bereichs zum Neuzeichnen vor jedem Aufruf der repaint()-Methode, um damit wenigstens zeitweise das Flimmern zu verringern. Eine andere Alternative wäre, mittels zwei – eventuell synchronisierter – Threads zu arbeiten, wobei jeder seinen eigenen Clipping-Bereich verfolgt. Dies ist sicher recht umständlich und dafür ist der Nutzen oft zu gering. Sie sehen, auch Clipping ist kein Allheilmittel, wenn Ausgaben in weit voneinander entfernten Bereichen des Bildschirms notwendig werden. Da muss man unter Umständen Flimmern in Kauf nehmen. Es gibt jedoch noch eine alternative Optimierungstechnik für Animationen, die wir uns noch anschauen wollen.
9.6.5
Double-Buffering
Double-Buffering (doppeltes Puffern) heißt, dass Sie bereits vor dem Zeichnen ein Bild erstellen oder es laden und in einem Off-Screen-Bereich (also einem nicht angezeigten Bildschirmbereich) zwischenspeichern (puffern), um es dann nur noch mit einem schnellen Kopierbefehl in den angezeigten Zeichenbereich zu verschieben. Sie erzeugen sozusagen eine zweite, nicht-sichtbare Oberfläche, in der bereits alles vorgezeichnet ist und dann auf einmal im sichtbaren Bereich angezeigt wird. Um Double-Buffering ausführen zu können, müssen wir fünf Schritte durchführen. 1. Erstellen einer Offline-Oberfläche und eines Graphics-Kontexts. Es müssen für eine Offline-Oberfläche und ein Graphics-Kontext Instanzvariablen erstellt werden, damit sie an die paint()-Methode übergeben werden können. Dies funktioniert so: Image OfflineOberflaeche; Graphics OffScreenBereich;
2. In der init()-Methode ein Image- und ein Graphics-Objekt erstellen. Nun müssen in der init()-Methode des Applets ein Image- und ein GraphicsObjekt erstellt werden, die diesen Instanzvariablen zugewiesen werden. Allerdings können Sie diese Objekte erst dann erstellen, wenn Sie ihre Grö ß e kennen. Das gesamte Verfahren funktioniert wie folgt mit der Methode
476
Java 2 Kompendium
Animationen
Kapitel 9
public abstract Image createImage(byte[] imagedata, int imageoffset, int imagelength),
die eine Instanz von Image liefert: OfflineOberflaeche = createImage(this.size().width,this.size().height);
Die so entstandene Instanz übergeben Sie dann der getGraphics()-Methode, um einen neuen Grafikkontext für das Bild zu erhalten: OffScreenBereich = OfflineOberflaeche.getGraphics();
Die als public abstract Graphics getGraphics()
definierte Methode der Klasse Image kann nur in diesem Zusammenhang aufgerufen werden, um einen neuen Grafikkontext für einen Off-ScreenBereich zu generieren. Sie ist nicht mit der in Component als public Graphics getGraphics() definierten Methode zu verwechseln, die den Graphics-Kontext von dieser Komponente zurückgibt bzw. Null, wenn die Komponente keinen aktuellen Grafikbezug hat. 3. Ausgaben in der Off-Screen-Bereich umleiten. Alle Ausgaben, die bisher über die paint()-Methode erfolgt sind, werden jetzt mittels der Instanz OffScreenBereich in die Off-Screen-Oberfläche geschrieben. Beispielsweise mit OffScreenBereich.drawLine(0,0,100,100);
4. Überschreiben der update()-Methode. Als Nächstes überschreiben Sie die update()-Methode wie folgt: public void update(Graphics g) { paint(g); }
5. Am Ende der paint()-Methode einen Kopierbefehl einfügen. Im letzten Schritt fügen Sie am Ende der paint()-Methode einen Kopierbefehl ein, der die Off-Screen-Oberfläche auf dem realen Bildschirm ausgibt: g.drawImage(OfflineOberflaeche,0,0,this);
Java 2 Kompendium
477
Kapitel 9
Grafik und Animation Diese Technik macht vor allem dann Sinn, wenn die Erstellung einer Sequenz zeitaufwändig ist. Wenn also die Zeit zum eigentlichen Zeichen länger ist, als auf Grund der Zeitintervalle zwischen den einzelnen Bildsequenzen zur Verfügung steht. Durch Multithreading kann eine Folgesequenz schon im Hintergrund erstellt werden, während die Vorgängersequenz noch auf dem Bildschirm ist. Beispiele sind aufwändige Zeichnenoperationen oder ein relativ langsames Laden von Bildern zur Laufzeit der Animation (etwa aus dem Netz). Wenn Sie diese Technik auf unsere letzte Animation mit der sich drehenden Erdkugel, die sich über den Bildschirm bewegt, anwenden, wird das Resultat ein erheblich verstärktes Flimmern sein. Außerdem wird die Spur der Erdkugel nicht gelöscht. Sie können es gerne ausprobieren (der Source liegt auf der CD als Animation7.java bei) und sich vom Resultat Augenschmerzen holen. Oft macht also auch diese Technik keinen Sinn und es kann sogar zu einer Verschlechterung des Ergebnisses kommen. Auch die anderen Techniken sind keine Wundermittel. Try and Error ist vielfach angesagt, um eine Animation zu optimieren. Die drei beschriebenen Techniken können Ihnen gleichwohl oftmals dabei helfen.
9.7
Das 2D-API
Wir wollen uns nun mit den Grafik-Erweiterungen von Java beschäftigen. 2D-Java ist Bestandteil der Java Foundation Classes (JFC) und ein Teil dessen, was Sun Swing nennt und sämtliche Techniken umfasst, die die GUIFähigkeit von Java ausmachen (insbesondere die AWT-Erweiterungen). Das Java-2D-API ist ein Satz von Klassen für erweiterte 2D-Grafik und die Bildbearbeitung. Es beinhaltet zahlreiche neue Stricharten (unterschiedliche Dicke, Endformen, Typen), Texte und Bilder. Das API unterstützt die allgemeine Bilderstellung und so genannte Alphachannel-Bilder, einen Satz von Klassen zur Unterstützung von exakten, einheitlichen Farbdefinitionen und -konvertierungen, sowie zahlreiche anzeigeorientierten Bildoperatoren. Mit dem Java-2D-API sind Sie in der Lage, das gleiche Bildmodell sowohl für die Bildschirmausgabe als auch für den Ausdruck zu verwenden. Dies ermöglicht nun auch unter Java WYSIWYG (What You See Is What You Get), d.h., die exakte Bildschirmanzeige dessen, was im Ausdruck zu sehen sein wird (Druckvorschau). Die zu dem Modell gehörenden Klassen werden als Ergänzung zu den bisherigen Klassen in den Paketen java.awt und java.awt.image gesehen.
478
Java 2 Kompendium
Das 2D-API
Kapitel 9
Beachten Sie, dass das Java-2D-API beim Wechsel von der JDK-1.2-Betaversion auf die Finalversion 1.2 stark verändert wurde. Leider oft ohne Hinweis darauf, was aus den bisher gültigen Elementen geworden ist und was als Ersatz fungiert. Im Fall von Applets werden die Java-2D-Techniken in Ihrem Browser wahrscheinlich nur unter Verwendung des Java-Plug-Ins funktionieren. Sie können aber auch auf den aktuellen Appletviewer zurückgreifen.
9.7.1
Java-2D-Grundlagen
Das Java-2D-API behandelt Formen, Text, und Bilder und unterstützt einen einheitlichen Mechanismus für Bildveränderungen (etwa Rotation und Skalierung). Neben der Font- und Farbunterstützung kann man über das Java2D-API so genannte Grafikprimitive (grafische Objekte wie ein Linienobjekt oder ein Kreisobjekt) kontrollieren, d.h., wie sie sich in einem 2D-Grafik-Kontext verhalten. Man kann spezifische Charakteristiken wie Höhe und Breite, Farbe, Füllmuster usw. angegeben, aber auch Überblendungen und ähnliche Effekte. Der Koordinatenraum Das Java-2D-API definiert zwei Koordinatensysteme: den Anwender-Koordinatenraum den Ausgabe-Koordinatenraum. Der Ursprung des Ausgabe-Koordinatenraums liegt in der oberen linken Ecke, wobei die x-Koordinate nach rechts zeigt und in diese Richtung gezählt wird. Die y-Koordinate wächst nach unten, hat also eine Orientierung von oben nach unten. Das ist analog zu dem bisherigen Koordinatensystem. Der Vorteil einer solchen von oben nach unten definierten Koordinatenangabe ist gerade bei Druckausgaben offensichtlich, denn das Blatt wird von oben nach unten bedruckt. Alle grafischen Objekte werden jedoch solange in einem Ausgabe-unabhängigen Anwender-Koordinatenraum beschrieben, bis sie auf einem Ausgabemedium wie dem Bildschirm oder dem Drucker ausgegeben werden. Das Übersetzungskonzept von einem Graphics2DObjekt beinhaltet Transformationsmöglichkeiten, um das Objekt dann in den Ausgabe-Koordinatenraum zu konvertieren. Normalerweise wird die Standardtransformation von einem Anwender-Koordinatenraum in einen Ausgabe-Koordinatenraum ohne Veränderung der Orientierung erfolgen.
Java 2 Kompendium
479
Kapitel 9
Grafik und Animation
9.7.2
Zeichnen unter dem Java-2D-API
Das Java-2D-API verwendet ein Zeichnenmodell, das von dem neuen java.awt-Package zum Zeichnen auf dem Bildschirm (AWT Drawing Model) definiert wird. In diesem Modell implementiert jedes Komponentenobjet eine paint()-Methode, die automatisch herangezogen wird, wenn irgendetwas zu Zeichnen ist. Wenn dies geschieht, wird ein Graphics-Objekt weitergegeben, das genau weiß, wie in der Komponente zu zeichnen ist. Zeichnen unter dem AWT Drawing Model Die Technik, die wir hier noch einmal rekapitulieren wollen, haben wir bisher in dem Kapitel bereits verwendet. Wir ziehen zur Veranschaulichung des grundsätzlichen Zeichnens unter dem AWT Drawing Model nochmals das nachfolgende Beispiel heran. Dabei werden wir – um auch dieses Vorgehen zu zeigen – kein Applet, sondern ein eigenständiges Java-Programm mit grafischer Oberfläche verwenden. Dessen Grundstruktur (inklusive Schließbefehl) haben wir in Kapitel 6 besprochen. Wir wollen ein rotes Rechteck zeichnen. Um dies nun unter der Verwendung von java.awt zu tun, implementieren Sie Component.paint() und gehen wie folgt vor: Listing 9.29: Ein eigenständiges Programm, das mit der paint()-Methode ein rotes Rechteck zeichnet.
import java.awt.*; import java.awt.event.*; class MaleRechtFill extends Frame { public MaleRechtFill() { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e){ dispose(); System.exit(0); } }); } public static void main(String args[]) { MaleRechtFill mainFrame = new MaleRechtFill(); mainFrame.setSize(400, 400); mainFrame.setTitle("Test"); mainFrame.setVisible(true); } public void paint(Graphics g) { g.setColor(Color.red); g.fillRect(50, 70, 200, 100); } }
Dieses Beispiel illustriert die grundsätzliche Vorgehensweise für jeden Zeichenvorgang:
480
Java 2 Kompendium
Das 2D-API
Kapitel 9 Abbildung 9.26: Ein rotes Rechteck in einer eigenständigen Applikation
1.
Spezifizieren Sie die notwendigen Attribute für die Form, die Sie zeichnen wollen, indem Sie eines (oder auch mehrere) der Grafikattribute über die passende Methode verwenden, etwa setColor().
2.
Definieren Sie die Form, die Sie zeichnen wollen – in unserem kleinen Beispiel ein Rechteck.
3.
Legen Sie das genaue Aussehen der Form fest, indem Sie eine passende Grafikmethode verwenden – etwa zum Füllen.
Schritt 2 und 3 werden in unserem Beispiel mit einer einzigen Grafikmethode erledigt, die Form und Aussehen festlegt – in unserem Fall fillRect(). Zeichnen mit dem Java-2D-API Schauen wir uns nun an, wie mit dem Java-2D-API gezeichnet wird. Sie werden erstaunt sein (oder auch nicht, wenn Sie aus den bisherigen Bemerkungen zu Java 2D, Swing & Co richtig geschlossen haben, dass die Erweiterungen oft nur ergänzen und nicht grundsätzlich verändern), aber die grundsätzlichen Zeichenprozesse sind identisch, wenn Sie die Java-2D-APIFeatures verwenden. Das Java-2D-API fügt einfach zusätzliche unterstützende Features zur Spezifizierung von Zeichenstilen, komplexen Formen und diverser Zeichnenprozesse hinzu. Um diese weitergehende Möglichkeiten zu nutzen, implementieren Sie die paint()-Methode wie gehabt, müssen aber zusätzlich den Graphics-Parameter in ein Graphics2D-Objekt casten. Dies geht beispielsweise so: Graphics2D g2d = (Graphics2D) g;
Java 2 Kompendium
481
Kapitel 9
Grafik und Animation Wir wollen unser rotes Rechteck von eben nun mittels Java-2D-API zeichnen. Dazu müssen Sie nur die paint()-Methode entsprechend modifizieren. Beispiel:
Listing 9.30: Ein rotes Rechteck in der 2D-Technik
import java.awt.*; import java.applet.*; import java.awt.image.*; import java.awt.geom.*; import java.awt.event.*; class MaleRechtFill2 extends Frame { public MaleRechtFill2() { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { dispose(); System.exit(0);}}); } public static void main(String args[]) { MaleRechtFill2 mainFrame = new MaleRechtFill2(); mainFrame.setSize(400, 400); mainFrame.setTitle("Test"); mainFrame.setVisible(true); } public void paint(Graphics g) { // Graphics-Parameter in ein Graphics2D-Objekt // casten, damit die Graphics2D-Funktionalitäten // genutzt werden können. Graphics2D g2d = (Graphics2D) g; // 1. Spezifizieren der Attribute g2d.setColor(Color.red); // 2. Definieren der Form. // (Verwende Even-Odd-Regel) GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); // untere linke Ecke path.moveTo(100.0f, 200.0f); // untere rechte Ecke path.lineTo(200.0f, 200.0f); // obere rechte Ecke path.lineTo(200.0f, 300.0f); // obere linke Ecke path.lineTo(100.0f, 300.0f); path.closePath(); // Schließen des Rechtecks // 3. Füllen der Form g2d.fill(path); } }
Der Basisprozess zum Zeichnen des Rechtecks mit den Java-2D-API-Klassen ist identisch mit dem java.awt, außer dass Sie die neue Java-2D-API-Klasse GeneralPath zum Definieren des Rechtecks verwenden. Sie ist ein Bestandteil des java.awt.geom-Pakets.
482
Java 2 Kompendium
Das 2D-API
Kapitel 9 Abbildung 9.27: Ein rotes Rechteck in der 2D-Technik
Das Objekt, mit dem wir agieren, ist dann aber nicht an die normale Rechteck-Form gebunden. Die Anzahl der Eckpunkte legt die Anzahl der Ecken der geometrischen Form fest, die auch in keiner Weise an rechte Winkel gebunden ist. Die Methode lineTo() ist ähnlich wie die bekannte drawLine()-Methode, nur können Sie über das GeneralPath-Objekt noch eine ganze Menge mehr machen. Und wenn es nur das Verschieben und Füllen ist, wie in unserem Beispiel. Erweitern wir das Beispiel ein wenig, damit das deutlich wird. import java.awt.*; import java.applet.*; import java.awt.image.*; import java.awt.geom.*; import java.awt.event.*; class MaleRechtFill3 extends Frame { public MaleRechtFill3() { addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { dispose(); System.exit(0); } }); } public static void main(String args[]) { MaleRechtFill3 mainFrame = new MaleRechtFill3(); mainFrame.setSize(450, 520); mainFrame.setTitle("Test"); mainFrame.setVisible(true); } public void paint(Graphics g) { // Graphics-Parameter in ein Graphics2D-Objekt // casten, damit die Graphics2D-Funktionalitäten
Java 2 Kompendium
Listing 9.31: Ein Viereck und ein Fünfeck
483
Kapitel 9
Grafik und Animation // genutzt werden können. Graphics2D g2d = (Graphics2D) g; // 1. Spezifizieren der Attribute g2d.setColor(Color.red); // 2. Definieren der Form. (Even-Odd-Regel) GeneralPath path1 = new GeneralPath(GeneralPath.WIND_EVEN_ODD); GeneralPath path2 = new GeneralPath(GeneralPath.WIND_EVEN_ODD); path1.moveTo(10.0f, 50.0f); // untere linke Ecke path1.lineTo(200.0f, 80.0f);//untere rechte Ecke path1.lineTo(200.0f, 350.0f);//obere rechte Ecke path1.lineTo(100.0f, 400.0f);//obere linke Ecke path1.closePath(); // Schließen des Rechtecks // 3. Füllen der Form g2d.fill(path1); g2d.setColor(Color.green); path2.moveTo(50.0f, 150.0f);//untere linke Ecke path2.lineTo(80.0f, 120.0f);//untere rechte Ecke path2.lineTo(300.0f, 380.0f);//obere rechte Ecke path2.lineTo(100.0f, 480.0f);// obere linke Ecke path2.lineTo(120.0f, 200.0f);// obere linke Ecke path2.closePath(); // Schließen des Rechtecks // 3. Füllen der Form g2d.fill(path2); } }
Abbildung 9.28: Überlagerte gefüllte Formen in der 2D-Technik
484
Java 2 Kompendium
Das 2D-API
Kapitel 9
Das Java-2D-API versetzt Sie in die Lage, die gleichen Mechanismen wie beim Zeichnen einer einfachen Form für viel komplexere Zeichnenoperationen zu verwenden. Wie auch immer, um die Erstellung von Standardformen wie Rechtecken, Ellipsen, Bögen und Kurven zu vereinfachen, stellt das Java-2D-API diverse Subklassen der Klasse Shape in Verbindung mit GeneralPath zur Verfügung. So können Sie beispielsweise statt GeneralPath Rectangle2D.Double zum Definieren des Rechtecks in unserem Beispiel verwenden: Rectangle2D.Double rect = new Rectangle2D.Double(300, 300, 200, 100);
Dieses Rechteck ist nicht identisch mit dem Rechteck, das über GeneralPath definiert wird. Der Ursprung des GeneralPath-Objekts, das wir in dem ersten Beispiel konstruiert haben, hat als Startpunkt die untere linke Ecke des Rechtecks. Der Ursprung von dem über das Rectangle2D.Double-Objekt konstruierte Rechteck ist (300, 300), die obere linke Ecke des Rechtecks. Wenn Sie ein Rectangle2D.Double-Objekt konstruieren, das mit einer negativen Dimension arbeitet, wird ein leeres Rechteck mit der Grö ß e (0,0) generiert. Im Allgemeinen läuft das Zeichnen mit den Java-2D-API-Klassen folgendermaßen ab: 1. Spezifizieren der notwendigen beschreibenden Attribute. Zusätzlich zu den Ihnen schon aus dem »klassischen« Fall bekannten Attributen wie feste Farbfüllungen stellen die Java-2D-Klassen Möglichkeiten für komplexere Füllungen (etwa Verläufe und Muster) zur Verfügung. Für diese komplexere Füllungen können Sie die setPaint()-Methode verwenden (wir kommen in Kürze noch einmal genauer darauf zurück). 2. Definieren einer Form, eines Textstrings oder eines Bildes. Das Java-2D-API behandelt Pfade bzw. Positionsangaben, 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. Das Java-2D-API unterstützt eine Vielzahl von Implementationen von Shape, die gemeinsame Formen wie Rechtecke, Bögen und Ellipsen definieren. GeneralPath ist eine Implementation von des Shape-Interfaces, das Sie zur Definition von beliebigen, komplexen Formen verwenden können, indem Sie eine Kombination von Linien und quadratischen sowie kubischen Beziér-Kurven nutzen. Der GeneralPath-Konstruktor bekommt einen Parameter, der die so genannte Winding Rule für das jeweilige Objekt spezifiziert. Dabei handelt es sich um Regeln zum Bestimmen, ob ein Zeichenobjekt innerhalb einer
Java 2 Kompendium
485
Kapitel 9
Grafik und Animation Form liegt oder nicht, wenn ein Path-Segment die Form kreuzt. Zwei verschiedene winding rules können für ein GeneralPath-Objekt spezifiziert werden: even-odd winding rule nonzero winding rule Die erste Regel spezifiziert, wie das Innere eines Path-Segments behandelt wird, wenn ein solches Gebilde zwischen Innen und äusseren Gebieten alterniert, d.h. er durchquert den Rand. Die 2. Regel basiert auf der gedachten Zeichnung von Strahlen von einem gegebenen Punkt aus bis Unendlich in jede Richtung und dann durch Prüfen aller Orte, wo die Strahlen und das Path-Segment sich schneiden. Die even-odd-Regel wurde in unseren Beispielen verwendet. Die Felder aus der Klasse java.awt.geom.GeneralPath für die Winding Rules haben sich verändert. Java 2D war bereits in den Betaversionen des JDK 1.2 vorhanden. Dort hießen die Felder static byte EVEN_ODD und static byte NON_ZERO. In der Finalversion des JDK 1.2 wurden diese Feldnamen entfernt! Sie heißen dort static int WIND_EVEN_ODD und static int WIND_NON_ZERO (beachten Sie auch die Veränderung des Datentyps). Leider wurde diese Veränderung kaum dokumentiert. Es kommt bei Verwendung der alten Felder in einem aktuellen JDK nicht einmal eine Meldung beim Komplilieren, dass diese Felder als deprecated gelten. Es wird nur die Fehlermeldung ausgegeben, dass diese Felder nicht in der Klasse vorhanden sind. Umgekehrt können Sie im JDK einer Vorgängerversion natürlich die neuen Feldnamen nicht verwenden. Positiver Effekt des offensichtlich stark veränderten Konzeptes mit den neuen Felder ist eine erheblich bessere Unterstützung der Grafikausgabe auf einigen Plattformen. Eine bessere Dokumentation bezüglich der Felder wäre jedoch wirklich wünschenswert gewesen. 3. Festlegen des Aussehens der Form. Dritter Schritt ist das Festlegen des Aussehens der Form, des Textstrings oder des Bildes. Dazu können Sie eine der passenden Graphics2D-Methoden verwenden. Bevor ein Java-2D-API-Objekt gezeichnet wird, wird es meist über eine zu dem Graphics2D-Objekt gehörende Transformation manipuliert. Eine solche Transformation nimmt einen Punkt oder Pfad und transformiert ihn in einen neuen Punkt oder Pfad. Das Default-Transformationsobjekt kreiert eine einfache Skala von Ausgabekoordinaten, wenn das Graphics2D-Objekt konstruiert wird. Um dann solche Effekte wie Rotation, Translation oder
486
Java 2 Kompendium
Das 2D-API
Kapitel 9
benutzerdefinierte Skalierung zu erhalten, kreieren Sie ein Transform-Objekt und verwenden es mit dem Graphics2D-Objekt. Die Standardtransformation, die in das Java-2D-API implementiert ist, ist die so genannte affine Transformation, die lineare Transformationen wie Translation, Rotation, Skalierung, und Verzerrungen unterstützt (auch darauf werden wir noch eingehen).
9.7.3
Komplexere Zeichnenoperationen im 2D-API
Die Technik für komplexere Zeichnenoperationen im 2D-API ist nahezu identisch mit dem bisherigen einfachen Beispiel. Dies ist eine der großen Stärken des 2D-API. Nehmen wir eine kleine Veränderung unserer bisherigen Beispiele vor und zeichnen zweite Rechtecke, wobei das zweite einen Teil des ersten Rechtecks überdeckt, sowie um 45° gedreht ist. Wir füllen das zweite Rechteck mit einer Farbe (nehmen wir blau) und verwenden zusätzlich eine der neuen 2D-Techniken – die Transparenz. Das neue Rechteck soll zusätzlich zu 50 % transparent sein, sodass das erste Rechteck im überdeckten Bereich immer noch sichtbar ist. Was bisher unter Java ein erhebliches Problem darstellte, kann mit dem Java-2D-API leicht realisiert werden. Um auch bei der Verwendung des 2D-API nicht auf Applets zu verzichten, wird das Beispiel in ein Applet umgebaut (was es sogar kürzer macht – die paint()-Methode funktioniert aber identisch in der eigenständigen Applikation). Dies soll aber noch einen weiteren Zweck verfolgen. Wenn Sie das Applet ohne das Java-Plug-In im Browser laden, wird es nicht funktionieren. Das Beispiel sieht dann aus wie folgt: import java.awt.*; import java.applet.*; import java.awt.image.*; import java.awt.geom.*; public class MaleRechtFill4 extends Applet { public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.setColor(Color.red); GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); path.moveTo(0.0f, 0.0f); // untere linke Ecke path.lineTo(200.0f, 0.0f); // untere rechte Ecke // obere rechte Ecke path.lineTo(200.0f, -100.0f); path.lineTo(0.0f, -100.0f); // obere linke Ecke path.closePath(); // Schließen des Rechtecks AffineTransform at = new AffineTransform();
Java 2 Kompendium
Listing 9.32: 2D-Java in einem Applet
487
Kapitel 9
Grafik und Animation at.setToTranslation(300.0, 400.0); g2d.transform(at); g2d.fill(path); // Hinzufügen eines 2. Rechtecks // Definieren der Farbe g2d.setColor(Color.blue); AlphaComposite comp =AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f); g2d.setComposite(comp); //setzen composite-Modus // Rotatation um 45 Grad gegen den Uhrzeigersinn at.setToRotation(-Math.PI/4.0); g2d.transform(at); g2d.fill(path); } }
Wenn Sie das Applet mit einer normalen <APPLET>-Referenz in einen Browser laden, werden Sie Probleme bekommen. Ohne das Java-Plug-In wird es nicht laufen. Abbildung 9.29: Der Navigator macht ohne Plug-In Probleme.
Mit dem Java-Plug-In und der Referenz über bzw. <EMBED> geht es dann. Sollten Sie die mittels des im ersten Kapitel beschriebenen HTML-Konverter erstellte HTML-Datei mit dem und dem <EMBED>-Tag als Basis nehmen, wird der Appletviewer das Applet zweimal öffnen. Analysieren wir das Beispiel: Der Zeichenprozess ist für beide Rechtecke identisch. Das Rechteck wird über die Verwendung eines GeneralPath-Objekts definiert. Die Attribute zur Festlegung des Aussehens werden gesetzt, indem die Methode setColor() für die Farbe und die Methode setComposite() zur Spezifizierung der 50 %-Transparenz für das blaue Rechteck angewendet wird.
488
Java 2 Kompendium
Das 2D-API
Kapitel 9 Abbildung 9.30: Zwei sich teilweise überdeckende Rechtecke
Die Transformationen werden angewandt, bevor die Rechtecke erstellt werden (Graphics2D.transform() wird verwendet, um beide Rechtecke am Punkt (300, 400) zu positionieren und das blaue Rechteck um 45º gegen den Uhrzeigersinn zu rotieren.) Die Rechtecke werden endgültig durch den jeweiligen Aufruf von fill() dargestellt.
Wir haben in dem Beispiel auf einige Java-Techniken vorgegriffen, die gleich noch näher beleuchtet werden. Wichtig ist, dass das eben durchgesprochene Beispiel die grundsätzliche Vorgehensweise zur Definition von neuen Farbattributen, Transformationsprozessen und der Darstellung des Objekts zeigt. Definition der beschreibenden Attribute: Definition der Farbe. In unserem Beispiel muss zur Zeichnung des zweiten Rechtecks mit einer zu 50 % transparenten, blauen Farbe zuerst die Farbe gesetzt werden: g2d.setColor(Color.blue);
Java 2 Kompendium
489
Kapitel 9
Grafik und Animation Definition der Gestaltungsoperation. Dabei müssen Sie festlegen, wie die neuen Farben bei Überlagerungen mit bereits existierenden Farben zusammenspielen sollen. Dazu kreieren Sie ein so genanntes AlphaComposite-Objekt. Ein solches AlphaComposite-Objekt definiert eine Kompositionsoperation, die spezifiziert, wie Farben ineinander übergehen, d.h. verschmelzen. In unserem Fall haben wir ein AlphaCompositeObjekt kreiert, das die Transparenz auf 50 % setzt und die neue Farbe über die alte Farbe legt. Hierfür spezifizieren Sie die SRC_OVER-Operation und einen Alpha-Wert von 0.5 bei der Erstellung eines AlphaComposite-Objekts. Um das neue Composite-Objekt aufzurufen, verwenden Sie folgenden Source: Graphics2D.setComposite.AlphaComposite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f); g2d.setComposite(comp);
Definition der Rotation. Die Rotation wird über die Erstellung einer neuen Instanz der Klasse AffineTransform und Aufruf der Methode setToRotation() zum Spezifizieren der Rotation um 45 Grad entgegen des Uhrzeigersinns bewerkstelligt. Die Transformation wird dann mit der vorangehenden Transformation zusammengefügt, indem die Transformation des Graphics2D-Objekts (Translation auf (300,400)) wie folgt über die Methode transform() aufgerufen wird: at.setToRotation(-Math.PI/4.0); g2d.transform(at);
Die Effekte von aufeinander folgenden Transformationsaufrufen sind kumulativ – (an)häufend, anwachsend. Dies bedeutet, dass durch jeden des aufeinander folgenden Transformationsaufrufe zu der aktuellen Position eine weitere Drehung entgegen des Uhrzeigersinns fortgeführt wird.
9.7.4
Text unter Java 2D
Auch im Bereich der Textbehandlung hat das neue Java-2D-API einige Erweiterungen gebracht. Diese reichen von der einfachen Verwendung von Schriftarten bis zum professionellen Management von Zeichenlayouts und Schriftarten-Features. Im Wesentlichen wird über die neue Fontklasse des Java-2D-API gegenüber der existierenden Klasse zur Verwendung von verschiedenen Schriftarten eine bessere Kontrolle über Schriftarten gewährleistet. Es ist nun möglich, mehr Informationen über eine Schriftart, etwa den Beziér-Pfad von individuellen Zeichenformen, mit den Schriftarten zu verwalten. Die Java-2DAPI-Fontklasse löst die alte Klasse vollständig ab, was bedeutet, dass sie natürlich all das kann, was diese auch konnte.
490
Java 2 Kompendium
Das 2D-API
Kapitel 9
Konkretes Zeichnen von Text Die Technik zum Zeichnen von Text ist unter dem 2D-API identisch mit der Verwendung eines GeneralPath-Objekts zum Definieren einer Form. Sie können unsere bisherigen Ausführungen zum Zeichnen eines Textes nahezu unverändert übernehmen, nur kreieren Sie ein Font-Objekt und verwenden es durch den Aufruf von Graphics2D.drawString(). Wir werden ein Beispiel durchsprechen, das auch auf Text zusätzlich noch eine der neuen 2D-Erweiterungen verwendet – die Rotation eines Texts, was nur als Rotation eines beliebigen Graphics2D-Objekts verstanden wird. Ein String soll gegen den Uhrzeigersinn gedreht (bis er fast auf dem Kopf steht) und außerdem in der Grö ß e von 60 Punkten dargestellt werden. Danach wird er über zwei Rechtecke platziert, die sich um einen Winkel gedreht überdecken. import java.awt.*; import java.applet.*; import java.awt.image.*; import java.awt.geom.*;import java.awt.font.*; public class MaleRechtFill5 extends Applet { public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; g2d.setColor(Color.red); GeneralPath path = new GeneralPath(GeneralPath.WIND_EVEN_ODD); path.moveTo(0.0f, 0.0f); // untere linke Ecke path.lineTo(200.0f, 0.0f); // untere rechte Ecke path.lineTo(200.0f, -100.0f);// oben rechts path.lineTo(0.0f, -100.0f); // oben links path.closePath(); // Schließen des Rechtecks AffineTransform at = new AffineTransform(); at.setToTranslation(300.0, 400.0); g2d.transform(at); g2d.fill(path); // Hinzufügen eines 2. Rechtecks g2d.setColor(Color.blue); // Definieren Farbe AlphaComposite comp = AlphaComposite.getInstance(AlphaComposite.SRC_OVER, 0.5f); //setzen des composite-Modus g2d.setComposite(comp); // Rotatation um 45 Grad gegen den Uhrzeigersinn at.setToRotation(-Math.PI/4.0); g2d.transform(at); g2d.fill(path); // eine 80 point-Version von Helvetica-BoldOblique Font myFont = new Font("Helvetica-BoldOblique", Font.PLAIN, 60); // Anzeige von String in gelb g2d.setColor(Color.yellow); g2d.setFont(myFont); // Setzen der Schriftart
Java 2 Kompendium
Listing 9.33: Ein gedrehter Text inmitten der Rechtecke
491
Kapitel 9
Grafik und Animation // String ist undurchsichtig gezeichnet, daher // wird es hier leicht transparent gesetzt g2d.setComposite(AlphaComposite. getInstance(AlphaComposite.SRC_OVER, 0.7f)); at.setToRotation(-Math.PI/2.0); g2d.transform(at); // Text zeichnen g2d.drawString("Das ist der Hammer", -100f, 50f); } }
Abbildung 9.31: Ein gedrehter Text in mitten der Rechtecke
492
Java 2 Kompendium
Bilder unter Java 2D
9.8
Kapitel 9
Bilder unter Java 2D
Zur Bildverarbeitung stehen Ihnen unter dem Java-2D-API alle Möglichkeiten der bisherigen Klassen in java.awt und java.awt.image zur Verfügung. Zusätzlich gibt es eine Vielzahl von neuen Klassen, inklusive BufferedImage, ComponentColorModel und ColorSpace. Damit erhalten Sie eine erweiterte Kontrolle über Bilder. So können Sie in gleicher Weise Bilder kreieren, die über das RGB-Modell hinausgehen und absolut genaue Farbdefinitionen zur Reproduktion beinhalten. Darüber hinaus lassen sich nun Pixel direkt in Speicher verwalten. Wie alle anderen grafischen Elemente werden Bilder mit einer Transformation bearbeitet, die dem Graphics2D-Objekt zugeordnet ist. Dies bedeutet, auch Bilder können skaliert, rotiert, verzerrt oder transformiert werden. Darüber hinaus beinhalten Bilder ihre eigenen Farbinformationen.
9.8.1
Anzeige von Bildern unter Java 2D
Die Anzeige eines Bildes unter Java 2D ist unkompliziert und wie fast immer eine einfache Übertragung der bisherigen Technik. Wenn Sie ein Bild haben, rufen Sie einfach Graphics2D.drawImage() mit einer gewünschten Transformation auf. Dabei können Sie – je nach verwendetem Konstruktor – das Bild über das Netz mit einer URL laden (Image meinBild = applet.getImage(url);) oder auch lokal mit einer Pfadangabe als String wie in dem folgenden Beispiel: public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; Image image = getImage(getDocumentBase(),"images/Img1.jpg"); AffineTransform at = new AffineTransform(); at.setToTranslation(100.0, 100.0); g2d.drawImage(image, at, this); }
Listing 9.34: Die paint()-Methode zeichnet ein rotiertes Bild
Sie können ein Bild ganz einfach rotieren. Dazu müssen Sie vor der Ausgabe mit g2d.drawImage(image, at, this); nur Folgendes einfügen: at.rotate(Math.PI/4.0);
9.8.2
Transparenz und Bilder
Wir haben eben angedeutet, dass Bilder unter Java 2D weitergehende Informationen zu jedem Pixel verwalten können. Dazu zählen auch Informationen über die Transparenz für jedes Pixel in einem Bild.
Java 2 Kompendium
493
Kapitel 9
Grafik und Animation
Abbildung 9.32: Ein Bild wird wie jedes andere Grafikobjekt behandelt.
Diese Informationen werden Alphachannel genannt und werden in Verbindung mit der aktuellen Komposition mit einem anderen Objekt verwendet, um Überblendeffekte mit der aktuellen Zeichnung zu realisieren. Daraus resultieren viele interessante Effekte, wie man sie mittlerweile oft in Webseiten sieht. Wir kennen bereits die notwendige Technik, um diese Transparenz bzw. Übergangeffekte zu realisieren – das AlphaComposite-Objekt, das SRC_OVER für die Kompositionsoperation verwendet. Erstellen wir zuerst ein Beispiel, das aus unseren bisherigen Beispielen eine kleine Animation zusammenfügt. Dabei arbeiten wir mit Transparenzmodi, Positionierungen, Rotationen und Verschiebungen. Das Beispiel in der ersten Version mischt Java 2D mit alten Verfahren. Listing 9.35: 2D-Animation in der gemischten Version
494
import import import import public Image
java.awt.*; java.applet.*; java.awt.image.*; java.awt.geom.*; class Animation2D extends java.applet.Applet implements Runnable { bild, image;
Java 2 Kompendium
Bilder unter Java 2D
Kapitel 9
int bildbreite; int bildhoehe; int xpos = 10; // Startposition X int ypos = 10; // Startposition Y Thread MeinThread; public void init() { // Bild laden bild = getImage(getCodeBase(), "images/kuerb.gif"); bildbreite = bild.getWidth(this); bildhoehe = bild.getHeight(this); image = getImage(getDocumentBase(),"images/Img1.jpg"); resize(600, 600); } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; Graphics2D g2d2 = (Graphics2D) g; AffineTransform at1 = new AffineTransform(); at1.setToTranslation(250.0, 30.0); at1.rotate(Math.PI/4.0); g2d.setComposite(AlphaComposite. getInstance(AlphaComposite.SRC_OVER, 0.7f)); g2d2.setComposite(AlphaComposite. getInstance(AlphaComposite.SRC_OVER, 0.7f)); /* g2d.setComposite(AlphaComposite. getInstance(AlphaComposite.SRC_OVER, 0.5f)); g2d2.setComposite(AlphaComposite. getInstance(AlphaComposite.SRC_OVER, 0.3f)); */ g2d.drawImage(image, at1, this); g2d2.drawImage(bild, xpos, ypos ,bildbreite,bildhoehe, this); } public void run() { for (int i=0; i < 32; i++) { xpos = (int)(xpos + (i/4)); ypos=(int)(ypos + (i/5)); bildbreite = (int)(bildbreite * (1 + (i/30))); bildhoehe= (int) (bildhoehe * (1 + (i/30))); repaint(); pause(500); } } public void start() { if (MeinThread == null) { MeinThread = new Thread(this); MeinThread.start(); } } public void stop() { if (MeinThread != null) { MeinThread.stop(); MeinThread = null; } }
Java 2 Kompendium
495
Kapitel 9
Grafik und Animation void pause(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { } } }
Das Beispiel nutzt eine Animation von vorher, in der ein Bild (ohne Einschränkung ein GIF) verschoben und vergrö ß ert wird. Dabei überstreicht es den Bereich eines Bilds, das per Java 2D positioniert, gedreht und transparent gesetzt wurde. Das verschobene GIF wird zwar mit einer drawImage()Methode aus der alten Technik angezeigt und positioniert (also auch verschoben), aber diese wird auf ein Graphics2D-Objekts angewandt (die Methode wurde aus Graphics vererbt). Dementsprechend kann auch dieses GIF transparent gesetzt werden. Sie sehen die Effekte, wenn sich die Bilder überlagern. Wenn Sie die Transparenzmodi ändern (dazu gibt es bereits eine auskommentierte Variante im Source), wird man die Effekte gut erkennen können. Abbildung 9.33: Ein Bild und ein GIF transparent überlagert
496
Java 2 Kompendium
Bilder unter Java 2D
Kapitel 9 Abbildung 9.34: Die Überlagerung in einem geänderten Transparenzmodus – das Bild scheint stärker durch
Im Appletviewer müssen Sie unter Umständen den Reload-Befehl auslösen, bevor Sie den Kürbis sehen. Diese Animation wird gewaltig flimmern. Natürlich könnten wir jetzt die oben beschriebenen Techniken zur Optimierung anwenden, aber darum geht es hier nicht. Wenn wir vollständig auf Java 2D umsteigen, werden auch diverse Optimierungen automatisiert. Das wollen wir nicht komplett machen, sondern nun auch das GIF vollständig als Graphics2D-Objekt behandeln und entsprechend rotieren, positionieren und ausgeben. Die nachfolgende Animation läuft auch im Appletviewer ohne den ReloadBefehl.
import import import import public Image
java.awt.*; java.applet.*; java.awt.image.*; java.awt.geom.*; class Animation2D2 extends java.applet.Applet implements Runnable { bild, image;
Java 2 Kompendium
Listing 9.36: Auch der Kürbis wird ganz als Graphics2D-Objekt behandelt.
497
Kapitel 9
Grafik und Animation Thread MeinThread; double xpos = 100.0; double ypos = 100.0; AffineTransform at2; public void init() { // Bild laden bild = getImage(getCodeBase(), "images/kuerb.gif"); image = getImage(getDocumentBase(),"images/Img1.jpg"); resize(600, 600); at2 = new AffineTransform(); } public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; Graphics2D g2d2 = (Graphics2D) g; AffineTransform at1 = new AffineTransform(); at1.setToTranslation(250.0, 30.0); at1.rotate(Math.PI/4.0); at2.setToTranslation(xpos, ypos); at2.rotate(Math.PI/1.8); g2d.setComposite(AlphaComposite. getInstance(AlphaComposite.SRC_OVER, 0.5f)); g2d2.setComposite(AlphaComposite. getInstance(AlphaComposite.SRC_OVER, 0.3f)); g2d.drawImage(image, at1, this); g2d2.drawImage(bild, at2, this); } public void run() { for (int i=0; i < 40; i++) { xpos = (double)(xpos + (i/4)); ypos=(double)(ypos + (i/5)); repaint(); pause(500); } } public void start() { if (MeinThread == null) { MeinThread = new Thread(this); MeinThread.start(); } } public void stop() { if (MeinThread != null) { MeinThread.stop(); MeinThread = null; } } void pause(int time) { try { Thread.sleep(time); } catch (InterruptedException e) { } } }
498
Java 2 Kompendium
Bilder unter Java 2D
Kapitel 9 Abbildung 9.35: Der Kürbis bewegt sich auf sein Opfer zu.
9.8.3
Rendering als Bestandteil von Java 2D
Eine neue Technik in Java 2D nennt sich Rendering, was Übersetzung oder Übertragung bedeutet. Darunter versteht man einen Prozess, um ein grafisches Objekt auf einem (beliebigen) Ausgabegerät wiederzugeben. Dazu ist eine Serie von Schritten notwendig, die in manchen Literaturquellen als Rendering Pipeline benannt wird. Wenn sie das 2D-API zur Ausgabe verwenden, wird der Rendering-Prozess über ein Graphics2D-Objekt und seine Statusattribute kontrolliert. Mit diesen Statusattributen können Sie einen Clippingpfad zur Limitierung des Bereichs, das ausgegeben werden soll, setzen, oder Farben und Füllungen definieren. Diese Attribute werden bei der Steuerung des Ausgabeproszesses dann angewandt. Der konkrete Rendering-Prozess Der konkrete Rendering-Prozess kann in vier Grundschritte aufgeteilt werden. Das grafische Objekt wird in grafische Primitive konvertiert und in den Ausgaberaum transformiert – unter Verwendung der Transformation von dem Graphics2D-Objekt. Dies legt fest, wo das grafische Objekt ausgegeben
Java 2 Kompendium
499
Kapitel 9
Grafik und Animation werden sollte. Wie dies dann aber exakt geschieht, hängt von dem Typ des auszugebenen grafischen Objekts ab: Wenn eine Form auszugeben ist, wird der PathIterator zum Holen der Elemente in dem Pfad der Form verwendet. Wenn der Pfad gezeichnet wird, wird das Stroke-Objekt in dem Graphics2D-Objekt verwendet, um den Pfad in einen gezeichneten Pfad zu konvertieren. Die Geometrie der Form wird in die Ausgabekoordinaten transformiert, die mit Graphics2D assoziiert sind. Wenn ein Text auszugeben ist, wird das Layout von den Figuren (Glyphs genannt), die dem auszugegebenden Textstring enthalten, über die Information in dem String-Font festgelegt. Die Glyphs werden in Umrisse konvertiert, die von dem Shape-Objekt festgelegt werden. Diese Shape-Objekte, die die Glyphs repräsentieren, werden wie normale Formen ausgegeben. Wenn ein Bild auszugeben ist, wird die begrenzende Box (in AnwenderKoordinaten) in Ausgabe-Koordinaten transformiert, indem die mit Graphics2D assoziierte Transformation verwendet wird. Bei der Ausgabe wird der aktuelle Clipping-Pfad zur Begrenzung der Ausgabeoperation verwendet. Die auszugebende Farbe wird nach folgenden Kriterien festgelegt: Für Bilder wird die Farbe von den Imagedaten selbst genommen. Für Text und Pfade wird das aktuelle Paint- oder Color-Objekt in Graphics2D bezüglich der Farbe abgefragt. Wenn das Objekt konkret auf dem Ziel ausgegeben werden soll, wird das aktuelle Composite-Objekt verwendet.
9.8.4
Kontrolle der Ausgabequalität
Java 2D gibt Ihnen vielfältige Möglichkeiten zur Kontrolle der Ausgabequalität eines beliebigen grafischen Objekts auf einem gerasterten grafischen Ausgabegerät. Dabei muss jedoch immer bedacht werden, dass einige Kurvenformen und diagonale Linien immer approximiert (berechnet) werden müssen und dabei Stufeneffekte entstehen. Dies werden Sie vor allem dann bemerken, wenn die Auflösung des Ausgabegeräts nur sehr niedrig ist. Um diese Effekte zu reduzieren, stellt Java nun eine Technik zur Verfügung, die Antialiasing genannt wird und zum Glätten von Ecken führt (auf Kosten der Geschwindigkeit der Ausgabe). Sie können in der Java-2D-API festlegen, ob Objekte bei der Ausgabe so schnell wie möglich oder möglichst glatt ausgegeben werden sollen (soweit die Plattform dies unterstützt).
500
Java 2 Kompendium
Bilder unter Java 2D
Kapitel 9
Sie können die Ausgabepräferenz für ein Graphics2D-Objekt spezifizieren, indem Sie die Methode setRenderingHints() aufrufen. Es gibt zwei Typen von Ausprägungen: 1.
Die erste indiziert, ob ein Objekt bei der Ausgabe antialiasiert werden soll oder nicht.
2.
Der zweite dient zum Festlegen einer Präferenz zwischen Geschwindigkeit und Qualität.
9.8.5
Transformation von 2D-Objekten
Das Java-2D-API unterstützt eine spezielle Transformationsklasse – AffineTransform. Wir haben sie bereits verwendet. Diese unterstützt sämtliche klassischen Grafikoperationen wie Skalierung, Rotation, und Zusammensetzen. Affine Transformationen Der Begrif »Affine Transformation« stammt aus der Mathematik und bedeutet eine lineare Transformation auf einem Satz von grafischen Primitiven. Es werden immer gerade Linien in gerade Linien transformiert und parallele Linien gehen wieder in parallele Linien über. Verändert werden jedoch die Abstände zwischen den einzelnen Punkten und die Winkel zwischen nicht-parallelen Linien. Dies mag vielleicht sehr abstrakt klingen, ist aber genau das, was wir auch bisher schon in unseren Beispielen getan haben. Außerdem ist es nur die abstrakte Beschreibung dessen, was Sie bei einer solchen Aktion auch sicher erwarten. Beispielsweise erwarten Sie sicher, dass bei einer reinen Rotation (einer typischen affinen Transformation) das grafische Objekt nur gedreht und nicht deformiert wird. Falls dies zusätzlich gewünscht wird, muss man mehrere affine Transformationen hintereinander ausführen. Eine einzelne affine Transformation wird immer nur eine lineare Aktion durchführen. Eine affine Transformation basiert immer auf einer 2-dimensionalen Matrix, die in der AffineTransform-Klasse enthalten ist und die Sie niemals direkt aufrufen müssen (eine erhebliche Erleichterung gegenüber vielen anderen Programmiersprachen und auch dem bisherigen Java-Konzept). Sie müssen bloß die gewünscht Sequenz von Rotationen, Translationen oder anderen Transformation auswählen und anwenden. Die mit dem Graphics2D-Objekt assoziierten Transformationen stehen in allen Formen, Texten und Bildern zur Verfügung, die Sie von dem Anwenderraum in den Ausgaberaum zeichnen (die meist verwendete Aktion). Graphics2D implementiert eine Version von drawImage(), die eine Instanz von AffineTransform als Parameter verwendet (wir haben die Methode in den letzten Beispielen verwendet). Wenn Sie diese Version von drawImage() verJava 2 Kompendium
501
Kapitel 9
Grafik und Animation wenden, wird das Bild genauso gezeichnet, wie Sie es mit der Transformation von dem Graphics2D-Objekt verkettet haben.
9.8.6
Neue Formen kreieren
Mittels der Shape-Schnittstelle können Sie in Java 2D eine Klasse kreieren, die einen neuen Typ von Form definiert (beispielsweise ein spezielles Polygon). Dazu müssen Sie bloß die folgenden Methoden der Shape-Schnittstelle implementieren: contains() getBounds() getBounds2D() getPathIterator() intersects()
9.8.7
Stroking Paths
Zeichnen (Stroking) einer Form wie ein GeneralPath-Objekt ist äquivalent dazu, als würde ein logischer Stift über die Segmente des GeneralPath geführt werden. Das Stroke-Objekt schließt die Eigenschaften des verwendeten Strichs durch diesen Stift mit ein. Das Java-2D-API unterstützt diverse Basis-Stroke-Klassen für die verschiedensten Basisstrichformen und -stile.
9.8.8
Füllen von Formen
Das Java-2D-API beinhaltet eine Vielzahl von einfachen Füllmechanismen, aber auch komplexe Mechanismen zum Ausfüllen von Formen (etwa Farbverläufe). Zur Vereinfachung dieser komplexen Vorgänge definiert das 2DAPI eine neue Graphics2D-Methode, die setPaint() heißt, sowie ein Interface mit Namen Paint. Zwei Implementationen von dem Paint-Interface werden unterstützt: GradientPaint TexturePaint
Sämtliche Zeichenvorgänge erfolgen mit einem Paint-Objekt. Ein ColorObjekt ist beispielsweise ein sehr einfacher Typ von eine Paint-Objekts, und die setColor()-Methode ist ein Spezialfall von setPaint(). Daraus folgt, dass setColor() ein Paint-Objekt mit nur einer Farbe installiert.
9.8.9
Komposition von Bildern
Für die komplexere Komposition verwendet Java 2D die AlphaCompositeKlasse, eine Implementation vom dem Composite-Interface. Diese Klasse
502
Java 2 Kompendium
Bilder unter Java 2D
Kapitel 9
unterstützt eine ganze Anzahl von verschiedenen Stilen zur Zusammenfügung von Bildern. Instanzen von dieser Klasse beinhalten Kompositionsregeln, die exakt beschreiben, wie eine neue Farbe in eine bereits bestehende Farbe geblendet werden soll. Der alpha-Wert wird von einem Color-, Paintoder Image-Objekt genommen und mit Pixelinformationen kombiniert. Benutzerdefinierte Kompositionsregeln Indem Sie die Composite- und CompositeContext-Interfaces verwenden, können Sie einen neuen Typ von Kompositionsregeln erstellen. Ein CompositeObjekt unterstützt dazu ein CompositeContext-Objekt.
9.8.10
Transparenz
Eine der wichtigsten und am meisten verwendeten Regeln in der AlphaComposite-Klasse ist SRC_OVER – die Angabe, wie Objekte durchscheinend angezeigt werden können. Das Verfahren haben wir in den letzten Beispielen mehrfach angewandt. Wenn AlphaComposite.SRC_OVER verwendet wird, wird indiziert, wie die neue Farbe (source color) in die existierende Farbe (destination color) eingeblendet wird. AlphaComposite-Objekte können mittels extra alpha-Werten angegeben werden, die die Tranparenz von Objekten beim Zeichnen festlegen. Diese alphaWerte werden mit den alpha-Werten des Grafikobjekts kombiniert. Diese kombinierten alpha-Werte legen fest, wie viel von der existierenden Farbe durch die neue Farbe hindurch angezeigt werden soll. Man kann das Durchscheinen vollständig unterbinden (alpha=1.0) oder auch die neue Farbe vollständig durchscheinen lassen (alpha=0.0). Werte dazwischen entsprechen den verschiedenen Abstufungen.
9.8.11
Text und Fonts unter Java 2D
Jegliche Transformations- und Zeichnentechnik von der Java-2D-API kann auf Textstrings angewendet werden. Dazu erhalten Sie zahlreiche Klassen für das Textlayout und feinabgestimmte Schriftkontrolle. Text-Management Mit dem Java-2D-API erhalten Sie eine erweiterte Font-Klasse, die erheblich weitergehende Möglichkeiten zur Kontrolle von Schrifarten bereitstellt als die originale Klasse (der Vorgängerversionen) zum Verwalten der Schriftart. Dazu zählen die Spezifikation von detailierten Fontinformationen sowie deren Zugriff. Ein String wird durch die enthaltenen Zeichen festgelegt, aber es gibt natürlich noch andere Informationen über ihn – etwa die ausgewählte Schriftart.
Java 2 Kompendium
503
Kapitel 9
Grafik und Animation Diese Vereinigung des Zeichens selbst und seinem exakten Aussehen wird Glyph genannt. Wie auch immer, ein String lässt sich auf Ebene der einzelnen Zeichen auf vielfältige Art und Weise manipulieren: verschiedene Formatierungen Ausrichtung Position Schriftart Metrik Grö ß e Umrisse Jedes Font-Objekt beinhaltet diese Attribute, die man direkt über die Methoden ansprechen kann, die von der Font-Klasse zur Verfügung gestellt werden. Die Font-Klasse erlaubt zusätzlich den Zugriff auf Metrik- und Umrissinformationen über die getGlyphMetrics() und getGlyphOutline()Methoden. Die Form, die durch getGlyphOutline() zurückgegeben wird, ist skaliert, indem die Fontgrö ß e und -transformation verwendet wird, aber reflektiert nicht die Transformation, die einem Graphics2D-Objekt zugeordnet ist. Zugriff auf Textpfade Über die Font.getGlyphOutline()-Methode können Sie beliebig auf die Form von jedem Glyph in einem Font zugreifen. Die StyledString-Klasse stellt Ihnen darüber hinaus die getStringOutline()-Methode zur Verfügung, die Konversion von einem Block von Text in einen Umriss vereinfacht. Text-Transformation Über die Font.deriveFont()-Methode können Sie ein neues Font-Objekt mit veränderten Attributen erstellen. Wir werden als Beispiel einen Text um einen zentralen Punkt rotieren lassen.
Listing 9.37: Ein String mehrfach um einen zentralen Punkt gedreht
504
import java.awt.*; import java.applet.*; import java.awt.image.*; import java.awt.geom.*; import java.awt.font.*; public class DrehSchrift extends Applet { public void paint(Graphics g) { Graphics2D g2d = (Graphics2D) g; // Transformation für den font. AffineTransform fontAT = new AffineTransform(); fontAT.setToScale(72.0, 72.0);
Java 2 Kompendium
Bilder unter Java 2D
Kapitel 9
// Beschreibung des font und instanziere ihn Font theFont = new Font("Helvetica", Font.PLAIN, 50); g2d.setColor(Color.blue); // Setzen der Farbe g2d.setFont(theFont); // Setzen der Schriftart Font theDerivedFont = theFont.deriveFont(fontAT); // Definieren der Renderingtransformation AffineTransform at = new AffineTransform(); at.setToTranslation(300.0, 300.0); g2d.transform(at); at.setToRotation(Math.PI / 4.0); // Kreiere ein String-Objekt zur Spezifizierung // von Text und transformiertem font. String ss = new String("Ei gude wie"); // Zeichnen von 8 Kopien des strings for (int i = 0; i < 8; i++) { g2d.drawString(ss, 0.0f, 0.0f); g2d.transform(at); } } } Abbildung 9.36: Drehschrift
Java 2 Kompendium
505
Kapitel 9
Grafik und Animation
9.8.12
Farbmanagement unter Java 2D
Zur Verwaltung von Farben finden Sie in dem Java-2D-API viele Erweiterungen und vor allem Erleichterungen für die Anwendung. Spezifizieren von Farben Zur Anzeige einer Form in einer bestimmten Farbe benötigen Sie einen Weg, um diese Farbe beschreiben zu können. Dazu gibt es eine Vielzahl von Wegen. Wir kennen bereits das RGB-Modell (Standard). Eine Alternative ist die Verwendung der Farben Cyan, Magenta, Gelb (yellow) und Schwarz (black) in dem CMYK-Konzept. Beide Modelle – RGB und CMYK – haben spezifische Vor- und Nachteile, auf die wir nicht genauer eingehen wollen, sind aber als wesentlichste Eigenschaft weitgehend Ausgabe-unabhängig. Die verschiedenen Techniken zur Spezifizierung von Farben werden Farbraum genannt. Das Java-2D-API referenziert auf das RGB- und das CMYK-Modell als Farbraum-Typen. Dies ist eine Erweiterung des bisherigen Modells, das nur das RGB-Modell nutzt. Die Farbklassen von Java 2D Die Schlüssel-Farbmanagementklasse in dem Java-2D-API ist Color. Die Color-Klasse beschreibt eine Farbe als Zusammensetzung der beteiligten Farbkomponenenten in dem speziellen Farbraum. Die Color-Klasse hat eine Vielzahl von Methoden und Konstruktoren, die den Standard-RGB-Farbraum (sRGB – Informationen dazu gibt es unter http://www.w3.org/pub/WWW/ Graphics/Color/sRGB.html) unterstützen. sRGB ist der Default-Farbraum von Java 2D. In dem Java-2D-API werden auch viele Funktionalitäten, die bisher in anderen Klassen untergebracht wurden, in diese Klasse verlagert. Eine wichtige Subklasse von Color ist die ColorSpace-Klasse. Sie besitzt u.a. Methoden zur Konvertierung von Farbkomponenenten in einen Farbraum.
9.8.13
2D-Bildbearbeitung
Bildbearbeitung beinhaltet die Manipulation von Rasterbildern. Fast jeder der bekannten Effekte von Foto-editierenden Programmen können über die Java-2D-API-Bildbearbeitungsklassen oder deren Erweiterungsklassen realisiert werden. Das Java-2D-API beinhaltet einen Satz von Klassen, die nahezu beliebige Operationen mit Bildobjekten durchführen können. So erlaubt beispielsweise BufferedImage extends java.awt.Image, auf jedes Pixel in einem Bild zuzugreifen.
506
Java 2 Kompendium
Bilder unter Java 2D
Kapitel 9
Für affine Transformationen sind diverse Klassen vorhanden. Im Wesentlichen sind es AffineTransformOp und Subklassen, BilinearAffineTransformOp und NearestNeighborAffineTransformOp, BandCombineOp, ColorConvertOp, ConvolveOp, LookupOp, RescaleOp und ThresholdOp. Diese Klassen können für die verschiedensten geometrischen Transformationen verwendet werden.
9.8.14
Offscreen-Puffer unter Java 2D
Wir haben uns im Laufe unserer Grafikbehandlung in Java bereits mit der Technik des Offscreen-Puffers auseinander gesetzt. Es handelt sich um eine Technik, mit der ein grafisches Element außerhalb des sichtbaren Bereichs eines Bildschirms (offscreen) erstellt und dann auf den Bildschirm kopiert wird. Dies ist bei sehr zeitintensiven Erstellungsvorgängen von Grafiken sehr nützlich und konnte auch schon in früheren Java-Versionen durchgeführt werden. Das java.awt-Package erleichert die Verwendung von Offscreen-Puffern, indem es das Zeichnen in einem solchen Puffer vollkommen identisch macht mit dem Zeichnen in einem Fenster. Alle Java-2D-API-Features zum Zeichnen können wie gewohnt verwendet werden. Zum Kopieren eines Offscreen-Puffers auf den Bildschirm wird einfach drawImage() aufgerufen. Erstellen eines Offscreen-Puffers Der einfachste Weg zur Erstellung eines Offscreen-Puffers unter Java 2D ist die Verwendung der Component.createImage()-Methode. Diese Methode kreiert ein Offscreen-zeichenbares Bild mit den angegebenen Dimensionen. Das Bild ist undurchsichtig und hat die Vordergrund- und Hintergrundfarben der Komponente. Die Transparenz lässt sich nicht verändern. Manipulation von Offscreen-Bilder Die BufferedImage-Klasse erlaubt die direkte Manipulation von Pixeln in einem Bild. Diese Klasse unterstützt eine absolute Kontrolle über ein Bild, indem sowohl das Farbmodell, die Bilddaten allgemein und das Datenlayout zu beeinflussen sind. Dies funktioniert auch ohne Einschränkung mit einem BufferedImage-Objekt, denn dieses enthält wie jedes andere Bild einen Alphachannel.
9.8.15
2D-Graphics Devices
Alle grafischen Ausgabemedien (Graphics Devices) – Monitore und Drucker – werden durch ein GraphicsDevice-Objekt repräsentiert, das alle notwendigen Informationen einschließt. Das Java-2D-API erlaubt die Abfrage sämtli-
Java 2 Kompendium
507
Kapitel 9
Grafik und Animation cher Attribute von der Umgebung, wo eine Applikation läuft. Dazu dient die GraphicsEnvironment-Klasse sowie GraphicsDevice und ihre zugehörigen GraphicsConfiguration-Objekte. Über die GraphicsEnvironment-Klasse haben Sie Zugriff auf alle notwendigen Informationen der Laufzeitumgebung, die eine Liste von GraphicsDeviceObjekten beinhaltet, die Ausgabegeräte repräsentieren. Diese kann man zwar abfragen, aber das ist so gut wie nie notwendig. Die GraphicsDevice-Klasse wird vom Java-2D-API zur Repräsentation eines aktuellen Ausgabegeräts verwendet. Normalerweise werden Sie auf diese Klasse nie zugreifen müssen. GraphicsDevice-Objekte werden standardmäß ig mit dem aktuellen Ausgabegerät verbunden. Jedes GraphicsDevice-Objekt hat eine Liste von GraphicsConfiguration-Objekten zugeordnet. Sie können eine Referenz auf ein GraphicsDevice-Objekt entweder von GraphicsEnvironment oder GraphicsConfiguration bekommen, indem Sie die Methode getDeviceConfiguration() aufrufen. Um eine Liste von den GraphicsConfiguration-Objekten zu bekommen, die den jeweiligen GraphicsDevice zugeordnet sind, können Sie analog GraphicsDevice.getConfigurations() verwenden.
9.9
Die Java-2D-API-Referenz
Alle Klassen in den folgenden Paketen sind Teile des Java-2D-API: java.awt.color java.awt.font java.awt.geom java.awt.print com.sun.image.codec.jpeg java.awt.image.renderable
Zusätzlich werden einige Klassen in den java.awt und java.awt.imagePackages dazugezählt: java.awt: java.awt.AlphaComposite java.awt.BasicStroke java.awt.Color java.awt.Composite java.awt.CompositeContext java.awt.Font java.awt.GradientPaint java.awt.Graphics2D
508
Java 2 Kompendium
Die Java-2D-API-Referenz
Kapitel 9
java.awt.GraphicsConfiguration java.awt.GraphicsDevice java.awt.GraphicsEnvironment java.awt.Paint java.awt.PaintContext java.awt.Rectangle java.awt.Shape java.awt.Stroke java.awt.TexturePaint java.awt.Toolkit java.awt.Transparency java.awt.image: java.awt.image.AffineTransformOp java.awt.image.BandCombineOp java.awt.image.BandedSampleModel java.awt.image.BufferedImage java.awt.image.BufferedImageFilter java.awt.image.BufferedImageOp java.awt.image.ByteLookupTable java.awt.image.ColorConvertOp java.awt.image.ColorModel java.awt.image.ComponentColorModel java.awt.image.ComponentSampleModel java.awt.image.ConvolveOp java.awt.image.DataBuffer java.awt.image.DataBufferByte java.awt.image.DataBufferInt java.awt.image.DataBufferShort java.awt.image.DirectColorModel java.awt.image.IndexColorModel java.awt.image.Kernel java.awt.image.LookupOp java.awt.image.LookupTable java.awt.image.MultiPixelPackedSampleModel java.awt.image.PackedColorModel java.awt.image.Raster java.awt.image.RasterformatException java.awt.image.RasterOp java.awt.image.RenderedImage java.awt.image.RescaleOp java.awt.image.SampleModel java.awt.image.ShortLookupTable java.awt.image.SinglePixelPackedSampleModel java.awt.image.WritableRaster
Java 2 Kompendium
509
Kapitel 9
Grafik und Animation
9.10
Sound und Töne in Java
Zum Abschluss wollen wir uns noch mit den akustischen Möglichkeiten von Java beschäftigen. Diese sind meist als Spezialfall einer allgemeinen Multimedia-Animation zu verstehen. Java gibt Ihnen jedenfalls die Mittel zum Einsatz. Unterstützt wurde ursprünglich nur das AU-Format. Dies ist ein extrem hochkomprimiertes Klangformat, das leider nicht allzu gute Tonqualität liefert. Allerdings konnten Sie schon länger bei Applets über Verknüpfungen zu externen Dateien auch andere Klangformate einbinden. Unter der aktuellen Java-Version wird die Audio-Unterstützung erweitert. Sowohl in Applikationen als auch in Applets lassen sich nun MIDI-Dateien (Typ 0 und Typ 1) sowie RMF-, WAVE-, AIFF-, und AU-Dateien in hoher Tonqualität abspielen und mischen. Das Abspielen von Sound wird mit Mitteln bewerkstelligt, die wie die Unterstützung von Bildern in den Applet- und awt-Klassen integriert sind und wie gesagt als Spezialfall einer Multimedia-Animation anzusehen sind. Die Methoden und deren Syntax ähneln sehr stark der Situation bei Bildern und Animationen. Wir schauen uns nur den Applet-Fall an. Sie können einem Applet jederzeit Klang hinzufügen, indem Sie die getAudioClip()-Methode verwenden, die zur Applet-Klasse gehört. Es gibt zwei Varianten: public AudioClip getAudioClip(URL url): public AudioClip getAudioClip(URL url, String name)
Dies ist wieder die gleiche Situation wie bei der getImage()-Methode. Mit einem Argument (ein Objekt vom Typ URL) ist es die einfachere Variante mit einer hartcodierten Pfadangabe. Die zweite Variante mit den zwei Argumenten (ein Objekt vom Typ URL und eine Zeichenkette) lässt sich in Verbindung mit den zwei weiteren – uns bekannten – Methoden getDocumentBase() und getCodeBase() – Bestandteile der Applet-Klasse – äußerst anpassungsfähig einsetzen. Beispiel: Audioclip clip = getAudioClip(getDocumentBase(), "audio/history.au");
Kann Java eine angegebene Datei nicht finden, gibt die Methode getAudioClip() Null aus.
510
Java 2 Kompendium
Sound und Töne in Java
9.10.1
Kapitel 9
Das Abspielen und Stoppen eines Audio-Clips – play()
Sie können einen Audio-Clip mit der play()-Methode abspielen. Auch hier schauen wir uns die Methode der Applet-Klasse (Applet.play()) an. Es gibt wieder zwei Varianten: public void play(URL url) public void play(URL url, String name)
Wenn eine Klangdatei an der angegebenen URL nicht zu finden ist, passiert einfach nichts. Das Anhalten eines Audio-Clips erfolgt über die stop()-Methode. Das permanente Wiederholen eines Audio-Clips erfolgt über die loop()-Methode. Tonwiedergabe ist ein ideales Einsatzgebiet für Multithreading. Besonders, wenn die Tondatei in einer Endlosschleife wiedergegeben wird.
9.10.2
Die Sound-Möglichkeiten der JDK-Demos
Mit dem JDK 1.3 werden wunderbare Demo-Applikationen mitgeliefert, die sich nach einer vollständigen Installation in dem Verzeichnis demo befinden. In dessen Unterverzeichnis sound befindet sich zu den wirklich sehr umfangreichen Multimediamöglichkeiten von Java ein exzellentes Demo, das man mit java -jar JavaSound.jar starten kann und das eine Basis für interessante Experimente bieten sollte. Besonders wichtig – der Quellcode des Demos liegt im Unterverzeichnis src bei und ist eine wahre Fundgrube, die für eigene Zwecke ausgeschlachtet werden kann. Abbildung 9.37: Die DemoQuelltexte
Java 2 Kompendium
511
Kapitel 9
Grafik und Animation
Abbildung 9.38: Die Quelltexte bieten sich als ideale Studientexte für eigene Soundexperimente an.
Aber auch das reine Experimentieren mit dem Programm ist eine äußerst kurzweilige Angelegenheit, die die Motivation für eigene Programmierexperimente mit diesen genialen Techniken sicher steigert1. Das Demo zeigt in vier Registerblättern, wie Java mit Sound umgehen kann. Da ist einmal die Juke Box, die Musikdateien unterschiedlichster (oben beschriebener) Formate abspielen kann. Abbildung 9.39: Die Juke Box
1
512
Und Freizeitmusiker wie den Autor von der eigentlichen Arbeit abhält ;-).
Java 2 Kompendium
Sound und Töne in Java
Kapitel 9
Das zweite Registerblatt (und natürlich der dazu vorhandene Quelltext) zeigt die Möglichkeit der Aufnahme von Sounds. Abbildung 9.40: Die Aufnahme von Sounds
Das dritte Registerblatt demonstriert den Umgang mit MIDI über ein virtuelles Keyboard, das sogar mit reinem MouseOver bedient werden kann. Dazu kann man verschiedene Instrumente und Kanäle auswählen und all das machen, was mit einem MIDI-Keyboard möglich sein sollte. Abbildung 9.41: Das mit der Maus zu bedienende virtuelle MIDIKeyboard
Java 2 Kompendium
513
Kapitel 9
Grafik und Animation Ein Drumsynthesizer fehlt auch nicht.
Abbildung 9.42: Der Drumsynthesizer
9.10.3
Das Java Media Framework
Die Soundmöglichkeiten von Java sind aber in der Java-2-Plattform nicht nur auf solche Basismöglichkeiten des JDK beschränkt. Multimedia allgemein (also auch wieder über reine Sounds hinausgehend) nimmt in der Java2-Plattform großen Raum ein. Ein wesentlicher Bestandteil der Multimediafähigkeit von Java steckt in eine eignem API der Java-2-Plattform – dem Java Media Framework (JMF), das eine sehr komfortable Verbindung von verschiedenen Mediatypen mit Java-Applets und -Applikationen erlaubt. Das JMF-API unterstützt eine einfache und vereinheitlichte Synchronisation, Kontrolle, Verarbeitung und Präsentation von komprimierten, Zeitbasierenden Mediadaten. Dies beinhaltet sowohl Javastreams als auch MIDI, Audio und Video auf allen Java-Plattformen. Das erste Framework 1.0 wurde von Sun, Silicon Graphics und Intel entwickelt und unter dem Namen Java Media Player bekannt. JMF 1.0 wurde durch die Folgeversion 1.1 u.a. um Screenshotfähigkeiten, Dateispeichern, RTP-Broadcast und der Manipulation von Mediadaten erweitert. Das aktuelle JMF 2 unterstützt MPEG-1 Layer 3 und bietet eine pluggable CodecArchitektur. Codec ist die Abkürzung für compressed und decompressed (co/dec), also die Kompression und Dekompression von Mediadaten, die für eine zeitkritische Verwendung im Internet unabdingbar sind. Das JMF 2.0 ist unter http://java.sun.com/products/java-media/jmf/ zu laden.
514
Java 2 Kompendium
Zusammenfassung
Kapitel 9 Abbildung 9.43: Download des Java Media Frameworks
9.11
Zusammenfassung
Bereits in der ersten Version stellte Java eine Vielzahl von Techniken zur Verfügung. Diese basieren im Wesentlichen auf java.awt.Graphics bzw. sind Bestandteil der Image-Klasse, die auch zum Paket java.awt gehört. Darunter sind alle grundlegenden Grafikvorgänge und Fundamente für Animationen zu finden, die auch die Basis für die neuen Grafiktechniken sind. Die neuen Grafikmöglichkeiten werden noch einige Zeit nicht in allen Darstellungplattformen – insbesondere den Browsern – wiedergegeben werden können. Festhalten kann man aber, dass es unter der neuen JFC für sämtliche der Methoden, die java.awt.Graphics bzw. die Image-Klasse nutzen, einen Ersatz gibt und dessen Anwendung sich meist direkt ableiten lässt. Das Java-2D-API erweitert die Grafikmöglichkeiten von Java um zahlreiche Möglichkeiten zur Behandlung von komplexen Formen, Texten und Bildern. Mit den Java-2D-API-Klassen, können Sie 2D-Grafiken in hoher Qualität, Text und Bilder in Ihren Applikationen und Applets vereinigen. Das Java-2D-API erlaubt die Ausgabe von Grafiken in hoher Qualität und Auflösung – unabhängig von dem physikalischen Ausgabegerät, stellt erweiterte Schriftarten- und Textunterstützung zur Verfügung und unterstützt ein einfaches und dennoch umfassendes Übersetzungsmodell.
Java 2 Kompendium
515
10
Das AWT
Kommen wir nun zu dem für viele Programmierer wahrscheinlich wichtigsten Thema einer Programmiersprache – der Kommunikation mit dem Anwender. In Java wird das hauptsächlich über ein eigenes Konzept realisiert – das AWT (Abstract Window Toolkit – in einigen Quellen wird es auch Abstract Windowing Toolkit genannt). Obwohl für viele weitergehende Aufgaben Dinge wie Netzwerkzugriffe, Datenbankzugriffe und -management oder Datenträgeroperationen unabdingbar sind, wird für viele Programmierer die Kommunikation mit dem Programmanwender das zentrale Thema sein. Besonders die Programmierer, die keine Großprojekte bearbeiten, sondern alleine oder in einem kleinen Team kleinere Anwendungen (wichtiges Stichwort in Java: Applets) erstellen, werden großen Wert auf die Benutzerkommunikation legen.
10.1
Was ist das Abstract Window Toolkit?
Das AWT beinhaltet zur Kommunikation mit dem Anwender im Wesentlichen ein API (Application Programming Interface), über das allgemeine Komponenten der Anwenderschnittstelle wie Buttons oder Menüs plattformunabhängig genutzt werden können. Um es nochmals zu betonen: plattformunabhängig! Damit hat Java ein erhebliches Problem zu lösen. Eine Benutzerschnittstelle kann ziemlich unterschiedlich aussehen und zu bedienen sein. Es kann sich um grundsätzlich verschiedene Konzepte handeln. Man unterscheidet erst einmal, ob es sich um eine befehlzeilenorientierte oder grafische Schnittstelle zum Anwender handelt. Aber nehmen Sie diesen Ansatz nicht zu ernst. Ich werde nicht ernsthaft befehlzeilenorientierte Benutzerschnittstellen wie DOS oder alte UNIX-Varianten als Basis heranziehen :-). Selbstverständlich hat Java von Anfang an eine grafische Verbindung zum Anwender geplant (wobei eine Kommunikation auf Befehlszeilenebene natürlich auch möglich ist – denken Sie nur an System.out zur Ausgabe oder zugehörigem System.in zur Eingabe). Damit ist das Problem, eine portierbare und plattformunabhängige grafische API zu entwickeln, jedoch noch lange nicht gelöst. Die wichtigsten grafischen Benutzerschnittstellen der EDV-Welt unterschieden und unterscheiden sich massiv. Da gibt es als Vertreter der großen Anwendermasse die Windows-Oberfläche. Oder besser – die Windows-Oberflächen, denn es Java 2 Kompendium
517
Kapitel 10
Das AWT gibt mehrere Varianten, die sich zum Teil massiv unterscheiden. Natürlich kocht(e) IBM mit dem API des OS/2-Präsentationsmanagers ein eigenes Süppchen und Apple will (wollte – wenn man neuere Entwicklungen beobachtet?) bei der Mac-Oberfläche überhaupt keinen Verdacht aufkommen lassen, große Ähnlichkeiten zu den unausgereiften Produkten der WintelKonkurrenz zu haben. Wie sollte nun ein gemeinsames API aussehen, auf dem sich Anwender aller Welten zurechtfinden können? Es gibt im Grunde vier Lösungsansätze: Man kann sich alle Plattformen, die unterstützt werden sollen, ansehen, die gemeinsamen Komponenten identifizieren und dann einen kleinsten gemeinsamen Nenner schaffen. Ansatz zwei untersucht wieder alle relevanten Oberflächen und schafft dann ein API, das sämtliche Möglichkeiten der Basis-Oberflächen umfasst (so etwas wie die Vereinigungsmenge aller Möglichkeiten der Basis-Oberflächen). Die dritte Idee könnte sein, je nach relevanter Plattform die dort gültige Oberfläche zu verwenden. Der Anwender bekäme das gleiche Look and Feel wie bei Anwendungen, die das systemeigene bzw. native API verwenden. Alle drei Ansätze haben Vor- und Nachteile. Die erste Idee ist relativ einfach und kann leicht auf allen Plattformen unterstützt werden, wird jedoch in der Leistungsfähigkeit erhebliche Einschränkungen gegenüber den jeweiligen Basis-Oberflächen der Plattformen zur Folge haben. Die Vereinigungsmenge aller Möglichkeiten der Basis-Oberflächen in einem einzigen API (wie in Variante zwei) beinhaltet zwar keinerlei Einschränkungen der Leistungsfähigkeit, aber es kann zu Konflikten bei der zu wählenden Realisierung kommen (z.B. so wie in Windows oder in OS/2), das System wird sehr kompliziert und evtl. nicht auf allen Plattformen einwandfrei zu realisieren sein. Zudem wird es für den Anwender schwer durchschaubar (was sonst geht, funktioniert zwar, aber es gibt noch weitergehende, unbekannte Aktionen). Die dritte Idee bringt für Anwender die wenigsten Umstellungen. Ein Anwender hätte in der Java-Oberfläche das gleiche Look and Feel wie in den gewohnten Anwendungen, die das systemeigene bzw. native API verwenden. Der Nachteil dieser Variante ist (und eigentlich ebenso der ersten beiden), dass sie nicht wirklich plattformunabhängig ist. Man kann nicht alle Oberflächen der EDV-Welt berücksichtigen und wie sollen Java-Programme auf Exoten-Plattformen aussehen? Wer wirklich plattformunabhängig sein will, muss ebenso dafür eine Lösung bereitstellen. Schlimmer noch ist, dass der dritte Ansatz gegenüber Neuerungen sehr unflexibel ist.
518
Java 2 Kompendium
Was ist das Abstract Window Toolkit?
Kapitel 10
Außerdem wird man abhängig von API-Herstellern. Wenn beispielsweise Microsoft wieder einmal beschließt, eine Windows-Version abzulösen und dort wieder einmal zig Veränderungen der API notwendig sind, muss das Java-API diese Veränderungen umgehend nachvollziehen. Es fehlt bisher noch der vierte Denkansatz. Wie wäre es, sich alle Plattformen, die unterstützt werden sollen, anzusehen, die besten Komponenten zu identifizieren und dann diejenigen zu realisieren, die auf allen Plattformen Sinn machen und ein vollständiges API gewährleisten? Natürlich würde man sich bei einem solchen Ansatz nicht 100 % für ein bestehendes Look and Feel entscheiden, sondern ein eigenes Look and Feel erzeugen, das die besten Ideen aus den zugrunde liegenden Plattformen beinhaltet. Genau diesen Ansatz hatte Sun bei Java ursprünglich gewählt – ein allgemeingültiges API, mit dem es Ihnen ermöglicht werden soll, Java-Anwendungen weitgehend übergangslos in die unterschiedlichsten Umgebungen einzubetten – das AWT. Diese vierte Lösung hat Sun jedoch nicht nur Lob eingebracht. Da das AWT von Java in der Version 1.0 relativ klein und allgemein gehalten wurde, konnten Java-Applikationen am Anfang nur eine Auswahl der unter Windows bekannten Elemente bieten. Hauptargument der Kritiker war, dass damit über 80 % der angeschlossenen Rechner (basierend auf der WINTELPlattform) zu Gunsten von weniger als 20 % der Internet-Teilnehmer auf die vollständige Windowsfunktionalität und deren Aussehen bei Java-Applikationen verzichten mussten. Dem AWT 1.0 wird gerne unterstellt, dass es recht rustikal aussieht. Ob dieser Kritikpunkt nun berechtigt war oder nicht (nicht immer ist der Massengeschmack ein Garant für Qualität – ich verzichte auf Beispiele) – das AWT hat in der Version 1.1 sehr weitreichende Erweiterungen erfahren. Darunter fallen im Wesentlichen eine einheitliche Druckerschnittstelle, damit Applikationen darüber plattformunabhängig drucken können (auch dies war in der Version 1.0 noch nicht gegeben), schnelleres und einfacheres Scrolling (bzw. die prinzipiell schnellere Reaktion auf Ereignisse), bessere Grafikmöglichkeiten sowie flexiblere Unterstützung von Schrift. Die Unterstützung von Pop-up-Menüs und der Zwischenablage waren weitere wichtige Erweiterungen der ersten AWT-Erweiterung. Die folgenden APIs haben Schritt für Schritt zugelegt und viele weitere neue Funktionalitäten und Erweiterungen folgen lassen. Die Java Foundation Classes (JFC) beinhalten nun Java 2D, diverse UI-Components (UI steht für User Interface – Benutzerschnittstelle), Zugriffsmöglichkeiten auf Fremdtechnologien (Accessibility), Drag&Drop, ein Input Method Framework zur Eingabe und Verwendung von Text in beliebigen Sprachen (etwa Japanisch, Chinesisch oder Koreanisch) über verschiedenste Eingabeelement und Application Services. Im Bereich der Performance und Stabilität wurde das AWT im Laufe
Java 2 Kompendium
519
Kapitel 10
Das AWT der Zeit erheblich verbessert. Nicht zuletzt hat sich Sun der Kritik angenommen, dass eine überall einheitlich aussehende Oberfläche nicht unbedingt sinnvoll ist und mit dem Swing-Konzept (eingeführt in Java 1.1, aber erst im JDK 1.2 in der nun aktuellen Form vollständig implementiert) eine Erweiterung des bisherigen Aussehens der Oberfläche geschaffen. Dieses Konzept implementiert einen Satz von GUI-Komponenten mit anpassungsfähigem Look and Feel. Man hat also im Nachhinein – trotz der Bedenken – den dritten Denkansatz als Alternative zum AWT realisiert. Swing ist vollständig in 100 % purem Java implementiert und basierte ursprünglich auf dem JDK 1.1 Lightweight UI Framework. Das Aussehen und die Reaktionen von GUI Komponenten passen sich automatisch an jede unterstützte Betriebssystemplattform (Windows, Solaris, Macintosh) an und erweitern die AWT-Möglichkeiten um einem Satz von Oberflächen-Interaktionselementen (Baumansichten, Listboxen, usw.). Swing ist keine Ablösung des AWTs. Beide Welten existieren parallel und können sogar vermischt werden (was aber nicht zu empfehlen ist). Bei der Entwicklung einer Oberfläche können Sie entscheiden, ob Sie sich der ursprünglichen Idee von Sun anschließen (also eine Oberfläche ohne Swing erstellen) oder sich der automatisch anpassungsfähigen Oberfläche widmen. Wir werden uns Swing im nächsten Kapitel widmen und uns hier auf das reine AWT beschränken. Der Begriff Framework umfasst einen ganzen Satz von Elementen für eine grafische Oberfläche. Neben den Elementen, die die eigentliche Kommunikation mit dem Anwender regeln (UI-Elemente), wird ein vollständiger Rahmen (Bibliotheken) für eine Applikation bereitgestellt. Ein wichtiger und berechtigter Kritikpunkt am dem AWT 1.0 war das sehr starr ausgelegte Eventhandling (also die Reaktion auf Ereignisse). Es wurde ab der Version 1.1 völlig überarbeitet und damit wesentlich flexibler. Das 1.1-Eventmodell arbeitet mit so genannten Delegates, d.h. Objekte, die die »Delegierten« von anderen Objekten sind. Ein Fenster erhält zum Beispiel ein Delegate-Objekt, an das eine Menge von Methoden geschickt wird, die das Delegate dann abarbeiten kann. Je nach dem Rückgabewert der Methode in dem Delegate wird das Fensterobjekt dann unterschiedlich reagieren. Das Delegate-Konzept an sich ist nicht neu und bereits ziemlich ausgereift, denn es wird schon seit Jahren unter dem Betriebsystem Nextstep, aber auch unter Windows NT und in der Entwicklungsumgebung OpenStep verwendet. Ein jedoch ebenfalls nicht zu unterschätzendes Problem des neueren Eventhandlings ist der gänzlich unterschiedliche Aufbau zu dem Modell 1.0. Insbesondere in der Vergangenheit gab es diverse komplexe Entwicklungen, die bereits nach dem alten Verfahren erstellt wurden und nur mit viel Auf-
520
Java 2 Kompendium
Woraus besteht das AWT?
Kapitel 10
wand auf das neue Konzept umgestellt werden konnten. Des Weiteren unterstützen selbst 2001 die meisten Browser das neue Eventmodell und die damit assoziierten Methoden bzw. neuen Varianten von bereits vorhandenen Methoden noch nicht direkt (die Referenzierung über das -Tag außen vor gelassen). Wer eine maximale Verbreitung anvisiert und bei Applets auf Nummer sicher gehen will, muss noch einige Zeit bei dem alten Eventmodell bleiben. Es lässt sich also nicht leugnen, dass es derzeit noch für beide Eventmodelle Argumente gibt, sodass wir uns mit beiden Modellen beschäftigen werden und sie gegenüberstellen. Wir werden – wie bei dem Kapitel über Grafik und Animation – im ersten Abschnitt des Kapitels das AWT und vor allem das Eventhandling der ersten Generation als Basis verwenden. Zum einen hat diese Technik – wie wir gerade gesehen haben – noch eine massive Existenzberechtigung, zum anderen sind diese die Grundlage für die neuen Techniken und die Erweiterungen werden sich ganz einfach ableiten lassen. Bestehende Methoden werden oft unverändert übernommen oder einfach durch eine neue Variante erweitert. Andere Methoden erhalten (etwa für das Swing-Konzept) eine direkte Partnermethode für das neue Konzept. In diesem Fall werden die »alten« Methoden als deprecated bezeichnet (in der Dokumentation erhalten Sie immer einen Verweis auf die neue Lösung). In Swing wird beispielsweise oft einfach eine neue Klasse geschaffen, die bis auf ein vorangestelltes J mit dem Namen der Klasse identisch ist, die im bisherigen Konzept die Aufgaben erledigte. Die direkten Partnermethoden für das neue Konzept müssen dann aus dieser neuen Klasse verwendet und evtl. etwas veränderte Argumente genommen werden.
10.2
Woraus besteht das AWT?
Das AWT besteht vorwiegend aus Komponenten, die der Endanwender (also der Entwickler einer Java-Applikation oder eines Applets) benutzt, indem er daraus seine Applikation baut1. Daneben gibt es Container, in denen die Komponenten integriert werden müssen, damit eine vollständige und sinnvolle Anwenderschnittstelle erstellt werden kann. Ein dritter bedeutender Bestandteil des AWT ist die Technik der Layoutmanager. Und »last but not least« muss das AWT einen Mechanismus zur Verfügung stellen, um auf Ereignisse reagieren zu können, die von dem Anwender über Komponenten ausgelöst werden. Das AWT basiert auf dem Grundkonzept, dass jedes Java-Fenster mehrere verschachtelte Ebenen besitzt, die aus Komponenten aufgebaut sind. Dies beginnt mit dem äußersten Fenster und endet mit dem kleinsten eigenständigen Bestandteil der Benutzeroberfläche. Diese Verschachtelung bildet eine 1
Natürlich benutzt damit auch der User über das fertige Produkt dann das AWT mit seinen Komponenten.
Java 2 Kompendium
521
Kapitel 10
Das AWT Hierarchiestruktur. Die Klassen des AWT-Pakets sind so entwickelt worden, dass ihre Struktur eine vollständige Benutzeroberfläche abbilden können. Die Wurzel von fast allen AWT-Komponenten ist die Klasse Component. Darin enthalten sind die grundlegenden Anzeige- und Eventhandling-Funktionalitäten.
10.2.1
Was sind AWT-Komponenten?
Die Komponenten sind die Elemente, über die eine Interaktion mit dem Endanwender konkret realisiert wird. Sie kennen sie aus den unterschiedlichen grafischen Benutzerschnittstellen. Die von Anfang an vorhandenen Komponenten des AWT sind folgende: Schaltflächen (Buttons) Label Kontrollkästchen (Checkbuttons) Optionsfelder (Radiobuttons) Listen Auswahlfelder Textfelder Textbereiche Menüs Zeichenbereiche Bildlaufleisten Die hier aufgeführten Komponenten kann man nach ihrer Funktion in drei logische Bereiche unterteilen.
522
1.
Komponenten der Benutzeroberfläche: Dazu zählen alle typischen Elemente einer Benutzeroberfläche (etwa Schaltflächen, Kontrollfelder usw.).
2.
Zeichenbereiche: Dabei handelt es sich um die Fläche auf dem Bildschirm, auf der in der Regel Zeichnungen ausgegeben werden.
3.
Fensterkomponenten: Dies sind alle Komponenten, die sich um den Aufbau und die Struktur der Fenster kümmern. Also Bildlaufleisten, Rahmen, Menüleisten und Dialogfelder. Diese Komponenten trennt man logisch von den Komponenten der Benutzeroberfläche, da sie logisch unabhängig von der integrierten Anwendung betätigt werden (das Verschieben von Fensterinhalt über Bildlaufleisten beispielsweise löst noch keine Aktion in der Applikation aus).
Java 2 Kompendium
Woraus besteht das AWT?
10.2.2
Kapitel 10
Was versteht man unter AWT-Container?
Die Container sind eine Ordnungsstruktur für Gruppen von Komponenten, die gemeinsam verwaltet werden. Das mag jetzt erst einmal sehr abstrakt klingen, ist aber eigentlich ganz logisch. Eine Schaltfläche ohne ein Fenster, in der sie integriert ist, macht wenig Sinn. Genauso wenig hat die Komponente »Bildlaufleiste« ohne ein Fenster, das über sie gescrollt werden kann, eine nützlich Funktion. Die Komponenten müssen mittels Container in verwaltbaren Gruppen organisiert werden. In Java können nur diejenigen Komponenten im AWT verwendet werden, die auch in einem Container enthalten sind. Streng genommen enthalten Container auch keine Komponenten, sie sind selbst die allgemeinste Form von Komponenten. Das bedeutet, dass ein Container neben »normalen« Komponenten durchaus auch andere Container enthalten kann. Die Container im AWT sind: Fenster (Windows) Panels Frames Dialoge
10.2.3
Container und Applets
Es mag in einem Applet so aussehen, dass man dort keinen Container verwendet. Der Eindruck trügt, den die Applet-Klasse ist eine Unterklasse der Panel-Klasse. Insbesondere stellt bei Applets der Browser das Hauptfenster und die Menüleiste (also die Fensterkomponenten) bereit.
10.2.4
Der Aufbau der AWT-Klassenhierarchie
Am deutlichsten wird der Zusammenhang der wichtigsten Bestandteile der AWT-Klassenhierarchie, wenn man die Klassen in einem Hierarchiediagramm ansieht. In der Klassenhierarchie-Anzeige der API-Dokumentation von Sun wird es sehr schön angezeigt.
10.2.5
Was ist ein AWT-Layoutmanager?
Wir hatten bisher festgehalten, dass man Komponenten verwendet, um die Interaktion mit dem Anwender zu realisieren, und Container als spezielle Komponenten, um diese dann gemeinsam zu verwalten. Unter dieser Verwaltung ist grö ß tenteils die gemeinsame Abspeicherung der Komponenten gemeint. Was nicht darunter fällt, ist die Anordnung der Komponenten innerhalb eines Containers. Sie müssen sich selbstverständlich darum kümmern, wo die Komponenten innerhalb eines Containers dargestellt werden.
Java 2 Kompendium
523
Kapitel 10
Das AWT
Abbildung 10.1: Button ist eine Subklasse von Component
Wer bisher mit anderen Sprachen programmiert hat, kennt wahrscheinlich die übliche Technik. Über Koordinatenangaben werden die einzelnen Elemente einer Struktur (etwa eines Dialogfensters) platziert, evtl. auch visuell. In Java werden Sie anders vorgehen. Unter Java werden Sie nicht mehr genau angeben, wo eine hinzugefügte Komponente in einem Container platziert werden soll. Die genaue Platzierung regeln die Layoutmanager. Sie weisen das AWT (und auch Swing) darüber nur noch an, wo Ihre Komponenten im Verhältnis zu den anderen Komponenten stehen sollen. Der Layoutmanager findet – angepasst an die jeweilige Situation – automatisch heraus, an welche Stelle die Komponenten am besten passen. Wie dieses sicher sehr abstrakt klingende Verfahren genau funktioniert, werden wir uns etwas später in der Praxis ansehen. Vereinfacht gesagt, bedeuten Layoutmanager für einen Programmierer die Abgabe von Kontrolle über Grö ß e und Position von Komponenten. Dieses Wegnehmen der absoluten Kontrolle vom Programmierer und die Übergabe an das System mag bei vielen Entwicklern erst einmal eine Aversion auslösen, hat jedoch ganz massive Vorteile. Es erleichtert die Erstellung von plattformunabhängiger Software erheblich. Wenn Sie Komponenten mit absoluten Koordinaten positionieren, kann in unterschiedlichen Auflösungen ein völliges Chaos entstehen. Eine Applikation, die mit absoluten Koordinaten unter einer 1280 x 1024 Auflösung perfekt aussieht, wird unter einer 640 x 480 Auflösung wichtige Bestandteile nicht anzeigen oder zumindest verunstaltet. Layoutmanager fangen dies genial und einfach ab.
524
Java 2 Kompendium
Container
Kapitel 10
Damit kein Missverständnis entsteht: Layoutmanager kümmern sich nur um Komponenten und nicht um Grafikelemente. Diese werden auch unter Java vom Programmierer absolut positioniert. Das AWT stellt erst einmal fünf verschiedene Typen von Layoutmanagern zur Verfügung: Flow Border Grid Card GridBag Mehr dazu folgt etwas weiter unten.
10.3
Container
Wir beschränken uns bei der Abhandlung des AWTs meist darauf, AWTKomponenten in Applets einzufügen oder abstrakt von Containern zu sprechen. Aber was bedeuten Container grundsätzlich? Die Beschränkung auf Applets hat in vielen Beispielen nur rein didaktische Gründe oder dient der einfacheren Beschreibung. Ein Container ist im weitesten Sinn ein Objekt, das darin enthaltenen Komponenten eine gewisse Grundfunkionalität zur Verfügung stellt und die in ihm enthaltenen Komponenten gemeinsam verwaltet. Wir haben den Begriff im Zusammenhang mit Applets/Browsern schon früher kennen gelernt. Im AWT sind Container zwar etwas anders zu verstehen, erfüllen aber im Wesentlichen den gleichen Zweck. Ein Applet ist im Sinn des AWTs auf jeden Fall ein Container. Das AWT verfügt neben dem Applet über drei weitere Container. Panels Frames Dialoge Schauen wir uns diese drei Container genauer an.
Java 2 Kompendium
525
Kapitel 10
Das AWT
10.3.1
Panels
Bei einem Panel handelt es sich um einen reinen Container, der keine sichtbaren Bestandteile besitzt. Es handelt sich um kein eigenes Fenster. Einziger Zweck eines Panels ist es, Komponenten in einem Fenster anzuordnen. Es gibt also nur sehr wenig, was Sie mit einem Panel machen können, ohne es in einem anderen Container (etwa einem Applet) einzusetzen. Dennoch sind Panels äußerst wichtig, wie wir gleich sehen werden. Sie können verschachtelt werden. Erzeugen von Panels Sie erzeugen ein Panel mit folgender Syntax: Panel [Panelname] = new Panel();
Beispiel: Panel meinPanel = new Panel();
Hinzufügen von Panels in Container Da Panels wie gesagt kein eigenes Fenster besitzen, machen sie hauptsächlich Sinn innerhalb von anderen Containern, die selbst eine sichtbare Oberflächenstruktur aufweisen (zumindest in der letzten Ebene). Sie können ein Panel einem anderen Container mit der public Component add(Component comp)-Methode hinzufügen. Beispiel: add(meinPanel);
Uns begegnet hier zum ersten Mal die add()-Methode, mit der Komponenten einem Container hinzugefügt werden. Von dieser werden Sie eine Vielzahl von Varianten finden. Beachten Sie, dass Sie die korrekte Variante aufrufen, wenn wir sie verwenden (besonders, wenn nicht explizit die Variante angegeben ist). Verschachteln von Panels Panels können anderen Panels hinzugefügt werden. Das heißt, Sie können Panels verschachteln. Dies ist besonders deshalb wichtig, weil damit Programmierer eine (relative) Kontrolle über die Position und Grö ß e von Komponenten zurück erlangen. Allgemein ist üblich, dass die Layoutmanager das Layout eines Haupt-Panels festlegen und in dessen Teilbereichen weitere Panels eingefügt werden, die dann die konkreten Komponenten (Buttons, Labels usw.) enthalten. Verschachtelt werden Panels einfach, indem die add()-Methode des übergeordneten Panels aufgerufen wird.
526
Java 2 Kompendium
Container
Kapitel 10
Beispiel: Panel hauptPanel, subPanel1, subPanel2; subPanel1 = new Panel(); // 1. verschachteltes Panel subPanel2 = new Panel(); // 2. verschachteltes Panel hauptPanel = new Panel(); // Hauptpanel hauptPanel.add(subPanel1); // Kind vom Hauptpanel hauptPanel.add(subPanel2); // 2. Kind vom Hauptpanel
Panels lassen sich in beliebig viele Ebenen verschachteln, solange es Sinn macht. In verschiedenen Beispielen im weiteren Verlaufs des AWT-Kapitels werden wir Panels in der Praxis einsetzen und auch verschachtelte Panels verwenden.
10.3.2
Frames
Frames ist ein Begriff, der vielen Lesern aus dem Zusammenhang mit dem World Wide Web bekannt sein dürfte. Seit dem HTML-Standard 4.0 gehören Frames zum offiziellen HTML-Dialekt. Um den Begriff zu klären, wollen wir uns von HTML aus nähern. Vor der Einführung der Frame-Technologie in HTML gab es nur die Möglichkeit, eine HTML-Datei in einen Browser zu laden und diese dann im vollständigen Anzeigefenster des Browsers darzustellen. Mit der ursprünglich von der Firma Netscape entwickelten Frame-Technologie ist es dagegen möglich, den Anzeigebereich des Browsers in verschiedene, frei definierbare Segmente aufzuteilen. Diese frei definierbaren Segmente werden Frames genannt und so lässt sich auch die Definition auf unseren Fall übertragen. Ein Frame ist ein voll funktionsfähiges Fenstersegment bzw. Fenster mit eigenem Titel und Icon. Frames können Pulldown-Menüs haben und verschieden gestaltete Mauszeiger verwenden. Jeder Frame kann eigene Inhalte enthalten. Die Situation lässt sich gut mit einer üblichen grafischen Oberfläche wie Windows und der dortigen Fenstertechnik vergleichen. Frames im Java-Sinn sind also Fenster, wie Sie sie aus nahezu allen grafischen Benutzerschnittstellen gewohnt sein dürften. Erzeugen von Frames Allgemein gilt, dass Sie mit der Window-Klasse des AWT grafische Fenster für Java-Applikationen erstellen können. Die Window-Klasse enthält grundlegende Eigenschaften für Fenster. In der Praxis wird jedoch selten die WindowKlasse direkt verwendet, sondern deren Subklassen Frame und Dialog. Da es sich um Subklassen handelt, können natürlich auch mit diesen Klassen voll funktionstüchtige Fenster erstellt werden. darüber hinaus haben sie noch weitergehende Eigenschaften.
Java 2 Kompendium
527
Kapitel 10
Das AWT Zum Erzeugen von Frames stehen Ihnen wieder verschiedene Konstruktoren zur Verfügung. Die einfachste Variante sieht so aus: public Frame()
Sie können damit einen Frame erzeugen, der anfangs nicht sichtbar ist und keinen Titel hat. Beispiel: Frame mFrame = new Frame(); Eine etwas kompliziertere Version vergibt für den Rahmen des Frames einen Titel bei der Erzeugung, jedoch der Frame ist anfänglich immer noch unsichtbar: public Frame(String title) Beispiel: Frame mFrame = new Frame("Titel des Frames"); Beide Konstruktoren zum Erzeugen von Frames haben diese unsichtbar gelassen. Damit sind die Frames bisher noch nicht zu gebrauchen. Frames müssen vor dem Gebrauch noch sichtbar gemacht werden. Frames dimensionieren, anzeigen, ausblenden und löschen Der erste sinnvolle, aber nicht unbedingt notwendige Schritt besteht darin, einem Frame eine Grö ß e zu geben. Dazu diente ursprünglich die Methode public void resize(int width, int height).
Das erste Argument gibt die Breite des Frames in Pixel an, das zweite Argument die Höhe in Pixel . Beispiel: mFrame.resize(400, 200); // 400 Pixel breit und 200 hoch
Diese Methode gibt es zwar immer noch, sie gilt aber schon lange als deprecated. Statt dessen verwendet man zur Dimensionierung von Frames die Methode setSize(), die es in zahlreichen Varianten gibt. Zwei der wichtigsten Varianten (Bestandteil der Klasse Component) sind folgende: public void setSize(Dimension d) public void setSize(int width, int height)
Im ersten Fall übergibt man ein Dimensions-Objekt als Parameter, der zweite Fall ist analog der resize()-Methode.
528
Java 2 Kompendium
Container
Kapitel 10
Grundsätzlich wurden mit der Zeit Methoden zum Setzen von Eigenschaften allesamt neu eingeführt, sodass sie mit set beginnen. Passend dazu beginnen Methoden zum Abfragen von Eigenschaften mit get. Dimensionieren legt zwar die Grö ß e eines Frames fest, sichtbar ist der Frame aber immer noch nicht. Das erfolgt nun beispielsweise mit der uns auch schon bekannten Methode public void show(). Beispiel: meinFrame.show(); Diese Methode gilt als deprecated und soll im Prinzip durch public void setVisible(boolean b)ersetzt werden. Der Parameter ist ein boolean Wert. Ist er true, wird das Fenster angezeigt, ist er false, wird es versteckt. Damit benötigen Sie dann auch nur eine Methode, mit der Fenster angezeigt und wieder unsichtbar gemacht werden können. Die vorher zum Ausblenden von Fenstern verwendete Methode public void hide() kann also auch als deprecated abgelegt werden. Wenn ein Fenster-Objekt angezeigt wird, ohne dass Sie vorher eine Grö ß e festgelegt haben, wird nur ein Fenster mit Titelzeile, aber ohne einen Anzeigebereich geöffnet. Sie können das Fenster aber dann mit der Maus vergrößern. Verstecken bzw. unsichtbar machen bedeutet nicht, dass ein Frame-Objekt damit zerstört worden ist. Wenn Sie ein Frame-Objekt endgültig beseitigen wollen, können Sie es mit der public void dispose()-Methode zerstören (die Methode gehört zu Window). Damit werden dann alle vom Frame belegten Ressourcen freigegeben. Beispiel: meinFrame.dispose(); Sofern ein Programm beendet werden soll, ist der Aufruf von System.exit(int status); sinnvoller. Überprüfen, ob bereits angezeigt Wenn Sie einen Frame anzeigen oder schließen wollen, ist es sinnvoll, vorher zu überprüfen, ob das Fenster bereits angezeigt wird. Dies können Sie mit der Methode public boolean isShowing() kontrollieren. Diese gibt einen booleschen Wert zurück, den Sie wie üblich auswerten können. Beispiel: if (!meinFrame.isShowing()) { // tue etwas sinnvolles }
Java 2 Kompendium
529
Kapitel 10
Das AWT Frames beeinflussen und überprüfen Selbstverständlich lassen sich Frames vielfältig beeinflussen bzw. Eigenschaften von Frames abfragen: Die Methode public void setTitle(String title) ändert den Titel des Frames, der oben in einem Rahmen angezeigt wird. Beispiel: meinFrame.setTitle("Neuer Titel"); Mit der Schwester-Methode public String getTitle() können Sie den Titel eines Frames einlesen. Beispiel: String frameTitel = meinFrame.getTitle(); Die Frame-Klasse verfügt über unterschiedliche Mauszeigerformen, die Sie mit der Methode public void setCursor(int cursorType) dazu benutzen können, den Mauszeiger innerhalb eines Frames zu ändern. Dabei kann das Argument der Methode durch in der Klasse definierte, meist sprechende Konstanten ersetzt werden. Beispiel: meinFrame.setCursor(Frame.HAND_CURSOR); Folgende Mauszeiger sind unter dem alten Java-Modell verfügbar: Frame.DEFAULT_CURSOR, Frame.CROSSHAIR_CURSOR, Frame.TEXT_CURSOR, Frame.WAIT_CURSOR, Frame.HAND_CURSOR, Frame.MOVE_CURSOR, Frame.N_RESIZE_CURSOR, Frame.NE_RESIZE_CURSOR, Frame.E_RESIZE_CURSOR, Frame.SE_RESIZE_CURSOR, Frame.S_RESIZE_CURSOR, Frame.SW_RESIZE_CURSOR, Frame.W_RESIZE_CURSOR und Frame.NW_RESIZE_CURSOR.
Unter dem neuen Java-Modell stehen Ihnen die Mauszeiger immer noch zur Verfügung. Sie müssen sie nur über Cursor.[Konstante] adressieren, also beispielsweise Cursor.CROSSHAIR_CURSOR. Natürlich lassen sich diese Werte abfragen. Dafür gibt es die Methode public int getCursorType(). Sie wird einen dieser Werte oder einen Integerwert zurückgeben, der dann den aktuellen Mauszeigertyp anzeigt. Damit kann man dann etwas Sinnvolles anfangen. Beispiel: if (mFrame.getCursorType() == Frame.W_RESIZE_CURSOR) { // tue was sinnvolles } else { // tue was anderes sinnvolles } 530
Java 2 Kompendium
Container
Kapitel 10
Sie können die potenzielle Grö ß enanpassung eines Frames durch den Anwender an- oder abstellen. Dazu gibt es die Methode public void setResizable(boolean resizable). Das Argument true lässt den Frame in der Grö ß e veränderbar, false deaktiviert diese Möglichkeit. Beispiel: meinFrame.setResizable(false); Wir werden jetzt zwei Beispiele anführen, die die gerade besprochenen Elemente in der Praxis zeigen. Wir werden zwar ein wenig vorgreifen, da wir dort u.a. Schaltflächen in einen Container einfügen und diesen Vorgang erst im Folgenden genauer behandeln. Außerdem werden die action()-Methode aus dem Eventhandling 1.0 bzw. Bestandteile des Eventhandling 1.1 verwendet, wobei wir grundsätzlich mit so genannten anonymem Adaptern arbeiten. Die Beispiele dürften dennoch leicht zu verstehen sein. Probieren Sie sie einfach mal aus. Wenn Sie bezüglich der Hintergründe der Ereignisbehandlung vorgreifen wollen, können Sie die Details auf Seite 605 finden. Das erste Beispiel verwendet einige als deprecated gekennzeichnete Methoden, wird aber einwandfrei funktionieren. Für die meisten besprochenen AWT-Situationen werden wir eine Ereignisbehandlung nach dem Modell 1.0 und (!) dem Modell 1.1 durchspielen.
import java.awt.*; public class AppletMitFolgeFenster extends java.applet.Applet { Frame meinFrame; public void init() { // Füge zwei Schaltflächen dem Container hinzu add(new Button("Öffne Fenster")); add(new Button("Schließe Fenster")); // Erstelle Frame meinFrame = new Frame("Sub-Fenster"); // Lege Framegröße fest meinFrame.resize(250,150); } // Reaktionen auf Schaltflächen public boolean action(Event evt, Object arg) { if (evt.target instanceof Button) { String label = (String)arg; // Wenn Schaltfläche "Öffne Fenster" if (label.equals("Öffne Fenster")) { // Überprüfe, ob Frame noch nicht angezeigt if (!meinFrame.isShowing()) meinFrame.show(); // Zeige Frame } // Wenn Schaltfläche "Schließe Fenster" else if (label.equals("Schließe Fenster")) { // Überprüfe, ob Frame angezeigt
Java 2 Kompendium
Listing 10.1: Ein Applet, das auf Klick ein Folgefenster öffnet und wieder schließt – Eventhandling 1.0
531
Kapitel 10
Das AWT if (meinFrame.isShowing()) meinFrame.hide(); // Verstecke Frame } return true; } else return false; }}
Abbildung 10.2: Ein Folgefenster, das von einem Applet geöffnet wird
Das zweite Beispiel arbeitet mit dem Eventmodell 1.1 und einer eigenständigen Java-Applikation mit grafischer Oberfläche, auf der eine Schaltfläche platziert wird. Bei einem Klick auf die Schaltfläche wird das Programm wieder beendet. Beachten Sie, dass wir hier den Titel und die Grö ß e des Fensters erst nach der Erzeugung festlegen, ebenso die Beschriftung des Buttons. Listing 10.2: Ein Java-Programm mit grafischer Oberfläche
532
import java.awt.*; import java.awt.event.*; public class AWTTe extends Frame { Button b = new Button(); public static void main(String[] args) { new AWTTe(); } public AWTTe() { initial(); } private void initial() { b.setLabel("Ende");
Java 2 Kompendium
Container
Kapitel 10
b.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { b_actionPerformed(e); } }); setLayout(new FlowLayout()); setTitle("Mein Fenster"); add(b); setSize(200,100); setVisible(true); } void b_actionPerformed(ActionEvent e) { System.exit(0); }} Abbildung 10.3: Ein Fenster mit Schließ-Button
10.3.3
Dialoge
Eng verwandt mit der Klasse Frame ist die Klasse Dialog. Unter einem Dialog versteht man in fast allen grafischen Benutzeroberflächen ein Popup-Fenster. So auch unter Java. Ein Dialogfenster verfügt über nicht so viele Funktionalitäten wie ein Frame. Dialoge werden im Wesentlichen für standardisierte Eingaben oder Meldungen eingesetzt. Dialoge werden entweder modal oder non-modal erzeugt: Der Begriff »modal« bedeutet, dass das Dialogfeld andere Fenster blockiert, während es angezeigt wird. Erzeugen eines allgemeinen Dialogfeldes Um ein allgemeines Dialogfeld zu erzeugen, stehen Ihnen im Wesentlichen zwei Konstruktoren zur Verfügung. Beide geben an, ob der Dialog zum Zeitpunkt der Erstellung modal oder non-modal ist. Dies kann nach der Erstellung nicht mehr geändert werden. Der Konstruktor Dialog(Frame, boolean) erzeugt ein Dialogfeld ohne Titel, das mit dem angegebenen Frame (das erste Argument) verbunden ist. Das zweite Argument gibt an, ob der Dialog modal (Wert true) oder non-modal (Wert false) ist. Beispiel: Dialog meinDlg = new Dialog(meinFrame, true);
Java 2 Kompendium
533
Kapitel 10
Das AWT Der Konstruktor Dialog(Frame, String, boolean) erzeugt ein Dialogfeld mit einem zusätzlichen Titel. Beispiel: Dialog meinDlg = new Dialog(meinFr, "Stop"; false); Grundsätzlich unterscheidet sich die Verwendung eines Dialogfensters nicht sonderlich von der eines Frames. Nur muss immer zuerst ein Rahmen vorhanden sein, um einen Dialog daran aufzuhängen. Ein Dialog kann als Folge nicht direkt zu einem Applet gehören (der Konstruktor benötigt jedes Mal ein Frame-Objekt als Argument). Ein Applet kann jedoch einen Rahmen erstellen, zu dem der Dialog gehören kann. Da ein Dialog nicht direkt zu einem Applet gehören kann, kann er nur dann an einem Rahmen »aufgehängt« werden, wenn Sie in dem Applet einen »Dummy«-Rahmen als Elternteil des Dialogs einsetzen. Damit können allerdings keine modalen Dialoge erzeugt werden. Ansonsten verhält sich ein Dialog wie ein gewöhnlicher Frame, was z.B. die Methoden zum Anzeigen und Verstecken betrifft. Auch die Methoden void setResizable(boolean), boolean isResizable(), void setTitle(String) und String getTitle() hat die Klasse Dialog mit der Frame-Klasse gemeinsam. Zusätzlich gibt die public boolean isModal()-Methode den Wert true aus, wenn der Dialog modal ist (andernfalls false). Spielen wir ein Beispiel mit einem Dialog durch. Das Beispiel basiert auf dem vorherigen Frame-Beispiel mit dem Eventmodell 1.1. Dieses Mal verwenden wir nur zwei Java-Dateien, die sich im selben Verzeichnis befinden sollen (also innerhalb desselben anonymen Pakets).
Listing 10.3: Ein Dialogfenster mit Schließfunktionalität
534
import java.awt.*; import java.awt.event.*; public class Dialog1 extends Dialog { Button d = new Button(); public Dialog1( Frame frame, String title, boolean modal) { super(frame, title, modal); enableEvents(AWTEvent.WINDOW_EVENT_MASK); initial(); } void initial() { d.setLabel("Schliessen"); d.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { d_actionPerformed(e); } }); setLayout(new FlowLayout()); add(d);
Java 2 Kompendium
Container
Kapitel 10
} protected void processWindowEvent(WindowEvent e) { if (e.getID() == WindowEvent.WINDOW_CLOSING) { cancel(); } super.processWindowEvent(e); } void cancel() { dispose(); } void d_actionPerformed(ActionEvent e) { dispose(); } }
import java.awt.*; import java.awt.event.*; public class AWTTe2 extends Frame { Button b = new Button(); Button c = new Button(); Dialog1 dl = new Dialog1( this,"Mein Dialog",true); public static void main(String[] args) { new AWTTe2(); } public AWTTe2() { initial(); } private void initial() { b.setLabel("Ende"); b.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { b_actionPerformed(e); } }); setLayout(new FlowLayout()); setTitle("Mein Fenster"); add(b); c.setLabel("Dialog öffnen"); c.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { c_actionPerformed(e); } }); add(c); setSize(300,400); setVisible(true); }
Java 2 Kompendium
Listing 10.4: Ein Java-Programm, das das Dialogfenster öffnet
535
Kapitel 10
Das AWT void b_actionPerformed(ActionEvent e) { System.exit(0); } void c_actionPerformed(ActionEvent e) { dl.setSize(200,100); dl.show(); }}
Abbildung 10.4: Ein innerhalb eines Fensters aufgerufenes Dialogfenster
Das Beispiel stellt ein Fenster mit zwei Buttons bereit. Der eine beendet wieder das Programm, der andere öffnet ein Dialogfenster. Dieses beinhaltet einen weiteren Button, der die dispose()-Methode aufruft und das DialogObjekt damit zerstört (ohne das Programm zu beenden). Dateidialoge Neben den allgemeinen Dialogfenstern gibt es unter Java spezielle Dialoge, die für besondere Aufgaben vorbereitet sind. Sie stellen also eine Art Schablonen dar. Es gibt beispielsweise für den Zugriff auf das Dateisystem eines Rechners ein Dateidialogfenster. Dieses ist im Prinzip systemunabhängig, kann sich jedoch optisch von Plattform zu Plattform unterscheiden. Dateidialogfelder machen meist nur bei eigenständigen Java-Applikationen Sinn. Bei Applets behindert die Sicherheitsbeschränkung des Java-Sandkastens in der Regel die Funktionalität. Zwar lässt sich das Dateidialogfeld anzeigen, die konkreten Dateizugriffe werden jedoch abgefangen. Dies gilt aber nicht immer, denn wenn das Applet maximale Rechte hat (sollte nicht oft vorkommen), kann man auch mit Applets Dateioperationen durchführen.
536
Java 2 Kompendium
Container
Kapitel 10
Um ein Dateidialogfeld zu erzeugen, stehen Ihnen u.a. zwei Konstruktoren zur Verfügung. Der Konstruktor FileDialog(Frame, String) erzeugt ein Dialogfeld vom Typ »Datei öffnen« mit dem durch String bezeichneten Titel, das mit dem aktuellen Rahmen (das erste Argument) verbunden ist. Es wird also ein Dateidialogfeld zum Laden einer Datei erzeugt. Beispiel: FileDialog mDlg = new FileDialog(meinFr, "Datei öffnen");
Der Konstruktor FileDialog(Frame, String, int) erzeugt ebenfalls ein Dateidialogfeld mit einem Titel, das mit dem aktuellen Rahmen (das erste Argument) verbunden ist. Der wesentliche Unterschied ist, dass das dritte Argument benutzt wird, um zu bestimmen, ob das Dateifeld zum Laden oder Speichern einer Datei dient. Die möglichen Ausprägungen für das dritte Argument sind FileDialog.LOAD und FileDialog.SAVE. Dies sind Konstanten, die Integerwerten entsprechen. Beispiel: FileDialog meinDlg = new FileDialog( meinFr, "Datei öffnen", FileDialog.LOAD);
Um es ganz deutlich zu betonen – das Dateidialogfeld besitzt nicht die Funktionalität, dass bei Auswahl einer Datei und Betätigen der entsprechenden Schaltfläche eine Datei geöffnet oder gespeichert wird. Statt dessen passiert Folgendes: Wenn eine Datei ausgewählt wird, kann die ausgewählte Datei mit den Methoden getDirectory() und getFile() bestimmt werden. Beide Methoden haben als Rückgabewerte Zeichenketten, die die ausgewählte Datei angeben. Konkret öffnen oder speichern müssen Sie die Dateien dann mit den Methoden, die wir im Kapitel über die Ein- und Ausgabe in Java behandeln. Fensterereignisse Für diverse standardisierte Fensterereignisse gibt es in der Event-Klasse einige Konstanten, die im Rahmen des Eventhandlings verwendet werden können. Schauen wir uns einmal einige dieser Standard-Ereignisse an. Konstante
Beschreibung
WINDOW_DESTROY
Das Fenster wird geschlossen, z.B. mit dem Schließbutton oder durch Beenden des Containers.
WINDOW_EXPOSE
Das Fenster wird in den Vordergrund geholt.
Java 2 Kompendium
Tabelle 10.1: Fensterereignisse
537
Kapitel 10 Tabelle 10.1: Fensterereignisse (Forts.)
Das AWT
Konstante
Beschreibung
WINDOW_MOVED
Das Fenster wird verschoben.
WINDOW_ICONIFY
Das Fenster wird zum Symbol verkleinert.
WINDOW_DEICONIFY
Das Fenster wird auf Normalgrö ß e wiederhergestellt.
10.4
Schaltflächen
Schaltflächen oder Buttons sind die elementarsten Komponenten für die Interaktion zwischen Anwender und Anwendung. Ob ein Befehl bestätigt werden soll, ob eine Aktion abgebrochen oder ob Hilfe geholt werden soll – Buttons regeln in grafischen Benutzerschnittstellen das Prozedere. Es gibt keine ernst zu nehmende interaktive Anwendung, die auf Buttons verzichten kann (und wenn es nur die Bestätigung des Beenden-Vorgangs ist). Schaltflächen werden in Java ganz einfach erzeugt. Sie benötigen nur folgende Syntax: Button [Buttonname] = new Button(); Beispiel: Button meinButton = new Button(); Mit dieser Syntax wird eine Schaltfläche ohne Label (d.h. ohne eine Beschriftung innerhalb der Schaltfläche – etwa »OK«) erzeugt. Normalerweise benötigen Sie jedoch eine Schaltfläche, die ein Label besitzt. Dies funktioniert ähnlich einfach. Wir haben einmal die bereits in obigen Beispielen verwendete Methode setLabel() zur Verfügung, um nachträglich eine Beschriftung zu setzen. Es gibt aber auch noch einen Konstruktor, der die Beschriftung bereits bei der Erzeugung setzt. Sie müssen nur als Argument in Hochkommata eingeschlossen die Beschriftung mit angeben: Button [Buttonname] = new Button([String]);
Beispiel: Button meinButton = new Button("Alles klar!"); Das war es! Mehr ist nicht notwendig, um eine Schaltfläche in Java zu erzeugen. Es gibt keine weiteren Optionen. Fertig sind Sie jedoch noch nicht. Die so erzeugte Schaltfläche muss noch in einen Container platziert werden und sollte normalerweise eine gewisse Funktionalität haben (Selbstzweck ist für einen Button meist nicht ausreichend). Platzieren wir zuerst den Button in einem Container. Die einfache Syntax add(meinButton); reicht im einfachsten Fall aus, um die eben erzeugte Schaltfläche dem Container hinzuzufügen.
538
Java 2 Kompendium
Schaltflächen
Kapitel 10
Sie können also entweder zwei Schritte nacheinander ausführen: Button meinButton = new Button("Alles klar!"); add(meinButton);
Alternativ funktioniert es ebenso in einer Zeile: add(new Button("Alles klar!"));
Im zweiten Fall arbeiten wir mit einem anonymen Objekt. Bei der expliziten Benennung einer Variablen (zwei Schritte), kann das Objekt später noch über den Namen der Variable beeinflusst werden. Die bisherigen Beispiele zum AWT haben ja bereits mit Buttons gearbeitet, sodass wir hier auf ein einfaches Beispiel verzichten können. Grundsätzlich kann zur Laufzeit eines Programms oder Applets die Bezeichnung eines Buttons (oder anderer verwandter Komponenten) geändert oder abgefragt werden. Gerade wenn Sie eine Schaltfläche ohne Beschriftung erzeugen, bietet es sich an, die Bezeichnung eines Buttons zu ändern. Dazu dient (wie wir bereits wissen) die Methode public void setLabel(String label). Beispiel: meinButton.setLabel("Doch nicht OK!"); Vollkommen analog können Sie die Bezeichnung für einen Button einlesen. Dazu gibt es die Methode public String getLabel(). Beispiel: String LabelvomButton = meinButton.getLabel(); Schaltflächen können (wie die meisten anderen Komponenten) zu Laufzeit aktiviert und deaktiviert werden. Dazu gibt es beispielsweise die Methode disable() zum Deaktivieren des Buttons oder sogar noch allgemeiner einer Komponente. Beispiel: meinButton.disable(); Die Methode enable() macht einen gesperrten Button (bzw. allgemein eine Komponente) wieder auswählbar. Beispiel: meinButton.enable(); Beide Methoden gelten als deprecated. Statt dessen steht die Methode public void setEnabled(boolean b) zur Verfügung. Wenn der Parameter true ist, ist eine Komponente aktiviert, sonst deaktiviert (also wieder nur eine Methode zum Ein- und Ausschalten eines Zustands). In diesem Zusam-
Java 2 Kompendium
539
Kapitel 10
Das AWT menhang ist auch die Methode public boolean isEnabled() zu nennen, mit der getestet werden kann, ob eine Komponente aktiviert ist oder nicht. Um noch mal vorzuwarnen – ein etwas umfangreicheres Thema ist die Reaktion der Applikation auf Ereignisse von Komponenten. Unsere bisherigen Beispiele haben dieses Verfahren schon gezeigt, aber das muss natürlich noch genauer besprochen werden. Wir haben an dieser Stelle das Dilemma, dass Schaltflächen (und natürlich ebenso die anderen Komponenten) in der Regel erst dann Sinn machen, wenn deren Betätigung etwas bewirkt. Dies soll im Rahmen des Abschnitts über die beiden Ereignismodelle (siehe Seite 605) noch detaillierter erfolgen. Wir wollen dennoch mit einigen Erklärungen vorarbeiten. Die meisten Komponenten innerhalb einer AWT-Oberfläche der 1.0-Version besitzen eine action()-Methode, die aufgerufen wird, wenn bei einer Komponente eine Aktion ausgeführt wird. Bei einer Schaltfläche wird diese action()-Methode aufgerufen, wenn sie ausgelöst (angeklickt) wird. An dieser Stelle soll noch einmal explizit darauf hingewiesen werden, dass die Methoden des Eventhandlings 1.0 (etwa auch mouseDown(), mouseDrag(), mouseEnter(), mouseExit(), mouseMove(), mouseUp()) als deprecated gelten und nur dann zu verwenden sind, wenn man nach dem Eventmodell 1.0 programmiert. Wenn eine Aktion bei einer Komponente ausgeführt wird, ruft das AWT (Eventmodell 1.0) die handleEvent()-Methode auf. Dabei wird bei einer Schaltfläche der Ereignistyp von ACTION_EVENT übergeben. Die StandardhandleEvent()-Methode ruft die action()-Methode bei der Komponente auf. Standardmäß ig kann die Komponente selbst keine Aktion bearbeiten. Deshalb wird jedes Ereignis an die handleEvent()-Methode im Eltern-Container übergeben. Dort wird die action()-Methode dann aufgerufen und entweder bearbeitet oder von dem nächst höheren Container in der Hierarchie ignoriert. Eine Komponente signalisiert mit der Weitergabe des Wertes true aus der Ereignisbehandlungsmethode, dass sie das Ereignis bearbeitet hat. Der Wert false bedeutet bei der Ereignisbehandlungsmethode, dass die Komponente das Ereignis nicht verarbeiten konnte. In diesem Fall sollte das Ereignis dann in der Elternhierarchie weiter nach oben gegeben werden. Die Syntax der action()-Methode ist bei allen Komponenten identisch: public boolean action(Event ereignis, Object welcheAktion) ereignis steht für das 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 auszuwerten. Es ist das Label, also die Beschriftung der Schaltfläche, die ausgelöst
540
Java 2 Kompendium
Schaltflächen
Kapitel 10
worden ist. Der ereignis-Parameter enthält weitere Informationen, die für die Aktion spezifisch sind. Darunter fallen z.B. Informationen über die Komponente, bei der die Aktion aufgetreten ist (event.target), oder den Zeitpunkt, zu dem die Aktion ausgelöst wurde (event.when). Mit dem instanceof-Operator können Sie die event.target-Variable überprüfen, um sicherzustellen, dass die Aktion auch für das gewünschte Objekt erfolgt. Dazu kontrolliert man, dass event.target instanceof Button den Wert true ergibt. Sie wissen nun, wie Sie einen Button erzeugen und kennen die action()Methode. Gehen wir ein konkretes Beispiel an. Dabei wollen wir die vorher besprochenen Methoden zum Setzen und Abfragen von der Button-Beschriftung anwenden. Das Beispiel verändert die Beschriftung eines Buttons in einer switch-Anweisung. Im Detail passiert Folgendes: 1.
Zuerst wird ein leerer Button erzeugt.
2.
Danach wird er im Container platziert.
3.
Nun weist man dem Button eine Beschriftung zu.
4.
Wenn die Schaltfläche gedrückt wird, wird die Methode public boolean action(Event evt, Object welcheAktion) aufgerufen.
5.
Zuerst überprüft die Methode, ob die Aktion, die die action()Methode ausgelöst hatte, auch vom dem Button ausgelöst wurde.
6.
Danach wird über die Informationen der Komponente, bei der die Aktion aufgetreten ist (evt.target) eine neue Instanz von Button in der action()-Methode erzeugt, um darüber die Beschriftung ändern zu können. Dies ist erforderlich, da sonst (in unserem Fall) nicht direkt auf die Beschriftung des Buttons über setLabel() zugegriffen werden kann.
7.
Über die Angabe welcheAktion kann jedoch direkt die Beschriftung ausgewertet werden. Dies folgt im nächsten Schritt. Es wäre aber ebenso möglich, mit getLabel() zu arbeiten. In diesem Fall kann man vollständig auf die Angabe welcheAktion verzichten. Statt dessen muss man diese Zeile verwenden: String buttonBeschriftung = meinButton_in_der_actionmethode.getLabel();.
8.
Im letzten Schritt wird je nach aktueller Beschriftung die Beschriftung der Schaltfläche wechselweise umbenannt.
import java.awt.*; public class ButtonTest extends java.applet.Applet { public void init() { Button meinButton = new Button(); add(meinButton); meinButton.setLabel("Alles klar!!"); } Java 2 Kompendium
Listing 10.5: Ein Java-Programm, das auf Schaltflächen reagiert
541
Kapitel 10
Das AWT public boolean action(Event evt, Object welcheAktion) { if (!(evt.target instanceof Button)) { return false; } Button meinButton_in_der_actionmethode = (Button)evt.target; String buttonBeschriftung = (String) welcheAktion; if (buttonBeschriftung == "Alles klar!!") { meinButton_in_der_actionmethode.setLabel( "Doch nicht?"); } else { meinButton_in_der_actionmethode.setLabel("Alles klar!!"); } return true; } }
Abbildung 10.5: Die Schaltfläche hat sich verändert.
Abbildung 10.6: Das frisch gestartete Applet
542
Java 2 Kompendium
Labels
10.5
Kapitel 10
Labels
Ein Label ist die wohl einfachste Form einer Komponente in einer Benutzeroberfläche. Unwichtig ist sie dennoch beileibe nicht. Es handelt sich um Textketten, die ausschließlich dafür benutzt werden, um andere Komponenten zu beschriften. Zwar besitzen alle Komponenten als Ableitung der Component-Klasse eine action()-Methode, aber da Beschriftungen normalerweise nur angezeigt werden, wird die action()-Methode eines Labels niemals aufgerufen und auch sonstige Methoden machen im Zusammenhang mit Labels selten Sinn. Das gilt selbstverständlich auch für das Eventhandling 1.1. Labels sind normalen Textketten auf Panels sehr ähnlich, haben jedoch den Vorteil, dass Labels neben dem reinen Text Layoutangaben des jeweiligen Panels einhält und dass es nicht jedes Mal nachgezeichnet werden muss, wenn das Panel neu gezeichnet wird. Ferner können Labels sehr leicht ausgerichtet werden. Wie bei fast allen Komponenten gibt es verschiedene Möglichkeiten, ein Label zu erstellen. Die einfachste Variante ist, ein leeres, linksausgerichtetes Label zu erzeugen. Dies funktioniert über den leeren Konstruktor Label() mit folgender Syntax: Label [Label] = new Label()
Beispiel: Label leeresLabel = new Label(); Ein leeres Label ist normalerweise kaum sonderlich dienlich, weil es einfach nichts zu sehen gibt. Dennoch kann es Sinn machen, wenn man zur Laufzeit das Label erst dynamisch füllen möchte. Dies funktioniert mit der Methode public void setText(String text). Die Abfrage erfolgt wieder analog mit public String getText(). Zur Beschriftung von Komponenten dienen allgemein entweder setText() oder setLabel(). Beachten Sie, dass für Labels setText() verwendet wird, während beispielsweise für Buttons setLabel() Anwendung findet. Wenn Sie ein Label bereits vorher mit Text versehen wollen, gehen Sie wieder vor wie bei Schaltflächen – nur mit dem Konstruktor Label(String) mit folgender Syntax: Label [Labelvariable] = new Label([Text])
Beispiel: Label meinLabel = new Label("Labeltext");
Java 2 Kompendium
543
Kapitel 10
Das AWT Labels können links, rechts oder zentriert ausgerichtet sein. Sowohl zur Laufzeit mit der Methode public void setAlignment(int alignment), wobei für die Ausrichtung Label.LEFT, Label.RIGHT und Label.CENTER verwendet werden können (auch diese Methode kann bei beliebigen Komponenten zum Einsatz kommen, wo eine Beschriftung ausgerichtet werden kann). Dazu passend liefert public int getAlignment() die Ausrichtung wieder zurück. Die Ausrichtung kann auch direkt schon bei der Erzeugung angegeben werden. Der dritte Konstruktor Label(String, int) erzeugt ein Label mit einer Textkette und einer bestimmten Ausrichtung. Die verfügbaren Ausrichtungen sind wie bei der Ausrichtungsmethode Label.LEFT, Label.RIGHT und Label.CENTER. Dabei entsprechen die Konstanten den Integerwerten 0 (Label.LEFT), 1 (Label.CENTER) oder 2 (Label.RIGHT), die natürlich ebenfalls verwendet werden kann. Die Sache sieht also beispielsweise so aus: Label mLabel1 = new Label("Labeltext", Label.LEFT); Label mLabel2 = new Label("Labeltext", Label.RIGHT); Label mLabel3 = new Label("Labeltext", Label.CENTER);
Um Label in einem Container platzieren, verwenden Sie wieder die add()Methode. Schauen wir uns die Label-Technik nun einmal in einem Beispiel an. Das Beispiel fügt drei Labels mit unterschiedlichen Techniken in den Container (ein Applet) ein. Label 3 wird mittels zwei verschiedener Button manipuliert. Eine Schaltfläche verändert den Labeltext, die andere Schaltfläche die Ausrichtung. Listing 10.6: Tests mit Labels
544
import java.awt.*; public class Label1 extends java.applet.Applet { Label mL1 = new Label(); public void init() { add(new Label("Text 1")); Label mL2 = new Label("Text 2"); add(mL2); mL1.setText("Text 3"); add(mL1); Button mB1 = new Button("Wechsel Label3"); add(mB1); Button mB2 = new Button( "Label3 rechts ausrichten"); add(mB2); } public boolean action(Event evt, Object welcheAktion) { if (!(evt.target instanceof Button)) { return false; } String bBeschr = (String) welcheAktion;
Java 2 Kompendium
Kontrollkästchen und Optionsfelder
Kapitel 10
if (bBeschr == "Wechsel Label3") { mL1.setText("Text 4"); } if (bBeschr == "Label3 rechts ausrichten") { mL1.setAlignment(Label.RIGHT); } return true; } }
Zu Verdeutlichung sehen wir uns die drei Situationen im Appletviewer an. Abbildung 10.7: Original-Label
Abbildung 10.8: Die Ausrichtung des dritten Labels ist verändert.
Abbildung 10.9: Der Text des dritten Labels hat sich geändert.
10.6
Kontrollkästchen und Optionsfelder
Kontrollkästchen und Optionsfelder (Checkbutton/Checkbox und Radiobutton) sind jedem Anwender von grafischen Oberflächen bekannt. Es ist aber vielen Anwendern (besonders nicht so versierten) oft nicht klar, worin
Java 2 Kompendium
545
Kapitel 10
Das AWT der genaue Unterschied besteht (zumal oft für beide zusammen der Oberbegriff Optionsfelder verwendet wird). Sie sind sehr eng verwandt. Beide gleichen Buttons, mit der Einschränkung, dass Sie nur auf »Ja« oder »Nein« gesetzt werden dürfen. Der entscheidende Unterschied zwischen Kontrollkästchen und Optionsfeldern ist der, dass Kontrollkästchen unabhängig von anderen Kontrollkästchen auf Ja oder Nein gesetzt werden können. Es können kein, ein oder mehrere Kontrollkästchen auf Ja gesetzt werden. Kontrollkästchen können auch alleine auftreten. Optionsfelder werden dahingegen immer mindestens paarweise verwendet und sind immer von den anderen Optionsfelder in speziellen wechselseitig sich ausschließenden Gruppierungen angeordnet, bei denen jeweils immer nur genau ein Button auf Ja geschaltet sein kann und muss. Der englische Name Radiobutton macht die Funktionalität sehr deutlich, den bei einem Radio kann man ebenfalls immer nur einen Sender einstellen und genau einer ist auch immer eingestellt (zumindest bei einem sinnvoll »konfigurierten« Radio). Auf den meisten Plattformen werden Kontrollkästchen viereckig dargestellt, während Optionsfelder rund gezeigt werden. Wir schauen uns die beiden unterschiedlichen Komponenten getrennt an. Zuerst wenden wir uns den Kontrollkästchen zu.
10.6.1
Kontrollkästchen
Kontrollkästchen unterscheiden sich ein wenig von unseren bisherigen Fällen. Ein Kontrollkästchen hat zwei Bestandteile – ein Label zur Bezeichnung und einen Zustand. Das Label ist der Text, der neben dem Kontrollkästchen angezeigt wird, der Zustand ist eine boolesche Variable, die angibt, ob die Box angeschaltet wurde oder nicht. Standardmäß ig ist die Box ausgeschaltet und ihr Wert ist false oder off. Ausgewählte Optionen werden auch optisch so dargestellt. Die Syntax zum Erzeugen eines Kontrollkästchens kann wieder mit verschiedenen Konstruktoren erfolgen. Sie können ein Kontrollkästchen ohne Label erzeugen, indem Sie den leeren Konstruktor Checkbox() verwenden. Beispiel: Checkbox meineCheckbox = new Checkbox(); Solche Objekte müssen Sie dann zur Laufzeit sinnvoll einstellen. Um ein Kontrollkästchen direkt mit einer Beschriftung zu erstellen, können Sie den Konstruktor Checkbox(String) verwenden: Beispiel: Checkbox mCbox = new Checkbox("Selektier mich");
546
Java 2 Kompendium
Kontrollkästchen und Optionsfelder
Kapitel 10
Die Variante drei mit dem Konstruktor Checkbox(String, null, boolean) erzeugt ein Kontrollkästchen, das je nach gesetzter boolescher Zustandsvariable vorselektiert (true) ist oder nicht (false). Das mittlere Argument ist bei Kontrollkästchen immer auf null zu setzen. Es dient als Platzhalter für ein Gruppenargument, das nur bei Radiobuttons auftritt. Beispiel: Checkbox meineCheckbox = new Checkbox("Selektier mich ", null, true)
Kontrollkästchen müssen wie alle Komponenten nach der Erzeugung erst in einem Container platziert werden. Dazu dient wieder die add()-Methode. Ein kleines Beispiel (ohne Eventhandling) demonstriert die Verwendung. import java.awt.*; public class CheckBoxTest extends java.applet.Applet { public void init() { add(new Checkbox("Visual Basic")); add(new Checkbox("C/C++")); add(new Checkbox("Delphi")); add(new Checkbox("Java", null, true)); add(new Checkbox("HTML")); }}
Listing 10.7: Kontrollkäschen – Java ist vorselektiert
Abbildung 10.10: Kontrollkästchen – Java ist vorselektiert
Den Status von Kontrollkästchen kann man programmtechnisch leicht überprüfen und setzen. Mit der Methode public boolean getState() können Sie überprüfen, ob ein Kontrollkästchen angeklickt wurde. Beispiel: if (meineCheckbox.getState()) { // Kontrollkästchen selektiert } else { // Kontrollkästchen nicht selektiert }
Java 2 Kompendium
547
Kapitel 10
Das AWT Die Methode public void setState(boolean state) setzt den Status (true für gesetzt), und die bereits bekannte getLabel()-Methode fragt das Label des jeweiligen Kontrollkästchens ab.
10.6.2
Optionsfelder
Die Vorgehensweise zur Erstellung von Optionsfeldern unterscheidet sich ein wenig von der Erzeugung von Kontrollkästchen. Ein Kontrollkästchen hat nur zwei Bestandteile – ein Label zur Bezeichnung und einen Zustand. Radiobuttons sind zwar nur ein Spezialfall der Kontrollkästchen, aber bei einem Optionsfeld muss noch zwingend eine Checkbox-Gruppe erstellt werden, in die Sie dann die Optionsfelder hinzufügen. Es gibt keine eigene Radiobutton-Klasse. Um Optionsfelder zu erstellen, legen Sie als ersten Schritt eine Instanz einer solchen Checkbox-Gruppe an. Dazu dient der Konstruktor CheckboxGroup(), der keinerlei Argumente besitzt. Beispiel: CheckboxGroup mCboxGruppe = new CheckboxGroup() Wenn Sie eine Gruppe erzeugt haben, erstellen Sie die einzelnen zugehörigen Optionsfelder und fügen sie der Gruppe hinzu. Dies funktioniert im Wesentlichen wie bei Kontrollkästchen, nur wird jetzt auf jeden Fall der Konstruktor benötigt, wo wir die Gruppe angeben können und müssen. Beispiel: Checkbox meineCheckbox = new Checkbox( "Selektier mich ", mCboxGruppe, true)
Um Optionsfelder in einem Container zu platzieren, wenden sie dann einfach wieder die add()-Syntax an. Beispiel: add(meineCheckbox); Eine wichtige Besonderheit von Radiobuttons unter Java ist, dass alle (!) Buttons in einer Gruppe deselektiert sein können. Zumindest so lange, bis der erste Eintrag ausgewählt wurde. Wenn Sie bei der Erstellung der Radiobuttons sämtliche Objekte mit dem Parameter false erzeugen, wird keiner der Radiobuttons vorselektiert. Das widerspricht natürlich den Gestaltungsregeln von Radiobuttons. Diese besagen ja, dass genau ein Button jeder Gruppe selektiert ist. Die Geschichte ist nicht sonderlich problematisch. Sie müssen nur daran denken, sich selbst um eine Vorselektion zu kümmern.
548
Java 2 Kompendium
Kontrollkästchen und Optionsfelder
Kapitel 10 Abbildung 10.11: Kein Radiobutton ist selektiert
Das nachfolgende Beispiel demonstriert die Verwendung in einer vollständigen Anwendung, wobei immer noch auf das konkrete Eventhandling verzichtet werden soll. import java.awt.*; public class CheckboxGroupTest extends java.applet.Applet { public void init() { CheckboxGroup meineCheckboxGruppe = new CheckboxGroup(); add(new Checkbox("Visual Basic", meineCheckboxGruppe , false)); add(new Checkbox("C/C++", meineCheckboxGruppe , false)); add(new Checkbox("Java", meineCheckboxGruppe , false)); add(new Checkbox("Delphi", meineCheckboxGruppe , false)); add(new Checkbox("HTML", meineCheckboxGruppe , true)); } }
Listing 10.8: Radiobutton – HTML ist vorselektiert
Abbildung 10.12: Optionsfelder – HTML ist vorselektiert
Optionsfelder können genau wie Kontrollkästchen gesetzt und überprüft werden. Die Methoden getState() bzw. setState() und getLabel() bzw. setLabel() funktionieren ebenfalls bei Optionsfeldern. Daneben können Sie die Methoden public CheckboxGroup getCheckboxGroup() und public void setCheckboxGroup(CheckboxGroup g) verwenden, um auf die Gruppe eines Optionsfeldes zuzugreifen und sie zu ändern. Des Weiteren stehen Ihnen mit den in der Optionsfeldergruppe definierten Methoden public Checkbox getCurrent() und public void setCurrent(Checkbox box) zwei Methoden zur Verfügung, um das momentan ausgewählte Optionsfeld zu holen und zu setzen.
Java 2 Kompendium
549
Kapitel 10
Das AWT
10.6.3
Reaktionen auf Kontrollfelder und Radiobuttons
Die Reaktionen auf Kontrollfelder und Radiobuttons weisen einige Besonderheiten auf. Kontrollfelder und Radiobuttons verfügen wie alle Komponenten unter dem Eventmodell 1.0 über die action()-Methode. Diese wird bei einer Auswahl aufgerufen, sobald ein Feld selektiert worden ist. Der welcheAktion-Parameter der action()-Methode wird genau dann true liefern, wenn das Feld angeklickt worden ist, und genau dann false, wenn das Feld nicht selektiert ist. Soweit nichts Besonders. Bei einem Radiobutton kann aber das Problem entstehen, dass er den Wert false erhält, wenn er angeklickt wird und bereits an ist. Deshalb sollten Sie immer vorher mit der getState()-Methode den Zustand des Radiobuttons oder der Checkbox überprüfen. Um nun zu bestimmen, welches Feld angeklickt worden ist, können Sie die getLabel()-Methode verwenden. Eine solche action()-Methode bei einer Auswahl wird normalerweise ungefähr wie folgt aussehen: public boolean action(Event evt, Object welcheAktion) // Überprüfung, ob ein Kontrollkästchen oder ein // Radiobutton. Falls nicht Rückgabewert false if (!(event.target instanceof Checkbox)) { return false; } // Instanz des Ereignisses Checkbox welcheAuswahl = (Checkbox)evt.target; .... tue etwas mit der Auswahl ... }
Wenden wir uns einem konkreten Beispiel zu. Das Beispiel setzt je nach Auswahl des Radiobuttons die Hintergrundfarbe des Applets um. Listing 10.9: Reaktionen auf Optionsfelder – Eventmodell 1.0
550
import java.awt.*; public class CheckAction 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("Grün", meineCheckboxGruppe , false)); add(new Checkbox("Gelb", meineCheckboxGruppe , true)); // Vorbelegen der Hintergrundfarbe passend zur // Vorselektion setBackground(Color.yellow); } public boolean action(Event evt, Object welcheAktion) { // Überprüfung, ob ein Kontrollkästchen oder // Radiobutton // Falls nicht Rückgabewert false
Java 2 Kompendium
Kontrollkästchen und Optionsfelder
Kapitel 10
if (!(evt.target instanceof Checkbox)) { return false; } // Instanz des Ereignisses Checkbox welcheAuswahl = (Checkbox)evt.target; boolean checkboxStatus = welcheAuswahl.getState(); if (welcheAuswahl.getLabel() == "Blau") { if (checkboxStatus) { setBackground(Color.blue); } return true; } if (welcheAuswahl.getLabel() == "Rot") { if (checkboxStatus) { setBackground(Color.red); } return true; } if (welcheAuswahl.getLabel() == "Grün") { if (checkboxStatus) { setBackground(Color.green); } return true; } if (welcheAuswahl.getLabel() == "Gelb") { if (checkboxStatus) { setBackground(Color.yellow); } return true; } return false; } } Abbildung 10.13: Die Voreinstellung
Schauen wir uns noch ein etwas komplexeres Beispiel an, das nach dem Eventmodell 1.1 auf Check- und Radiobuttons reagiert. Es werden jeweils Labels sichtbar bzw. unsichtbar gesetzt. Das Beispiel wird zeigen, dass das Eventmodell 1.1 nicht immer einfacher ist als das alte Modell. Das Beispiel arbeitet zudem mit verschachtelten Panels.
Java 2 Kompendium
551
Kapitel 10
Das AWT
Abbildung 10.14: Blau wird als Hintergrundfarbe des Applets gewählt.
Wenn Sie mit Labels unter bestimmten Layouts wie dem Flowlayout arbeiten, sollten Sie beachten, dass sich die Länge des Labels dem Inhalt anpassen kann. Das kann dann ein Problem werden, wenn man einen kurzen Text schreibt (mit setText()) und danach einen längeren Text. Dieser wird unter gewissen Konstellationen nicht ganz angezeigt, wenn das Layout die Länge des Labels so einschränkt, dass eine Vergrö ß erung nicht möglich ist. Listing 10.10: Reaktionen auf Optionsfelder und Radiobuttons nach dem Eventmodell 1.1
552
import java.awt.*; import java.awt.event.*; public class CheckRadio extends Frame { Panel pnl1 = new Panel(); Panel pnl2 = new Panel(); FlowLayout fl1 = new FlowLayout(); FlowLayout fl2 = new FlowLayout(); Checkbox chkbx1 = new Checkbox(); CheckboxGroup chkbxGroup1 = new CheckboxGroup(); Checkbox chkbx2 = new Checkbox(); Checkbox chkbx3 = new Checkbox(); Panel pnl3 = new Panel(); FlowLayout fl3 = new FlowLayout(); Label lb1 = new Label(); Label lb2 = new Label(); public CheckRadio() { initial(); } public static void main(String[] args) { CheckRadio checkRadio = new CheckRadio(); } private void initial() { pnl2.setLayout(fl1); pnl1.setLayout(fl2); chkbx1.setLabel("Text schreiben"); chkbx1.setState(true); chkbx1.addItemListener( new java.awt.event.ItemListener() { public void itemStateChanged(ItemEvent e) { chkbx1_itemStateChanged(e); } });
Java 2 Kompendium
Kontrollkästchen und Optionsfelder
Kapitel 10
chkbx2.setCheckboxGroup(chkbxGroup1); chkbx2.setLabel("Text schreiben"); chkbx2.setState(true); chkbx2.addItemListener( new java.awt.event.ItemListener() { public void itemStateChanged(ItemEvent e) { chkbx2_itemStateChanged(e); } }); chkbx3.setCheckboxGroup(chkbxGroup1); chkbx3.setLabel("Text löschen"); chkbx3.addItemListener( new java.awt.event.ItemListener() { public void itemStateChanged(ItemEvent e) { chkbx3_itemStateChanged(e); } }); pnl3.setLayout(fl3); lb1.setText("Kontrolle"); lb2.setText("Radio"); this.add(pnl1, BorderLayout.WEST); pnl1.add(chkbx1, null); this.add(pnl2, BorderLayout.EAST); pnl2.add(chkbx3, null); pnl2.add(chkbx2, null); this.add(pnl3, BorderLayout.CENTER); pnl3.add(lb2, null); pnl3.add(lb1, null); setSize(500,200); show(); } void chkbx1_itemStateChanged(ItemEvent e) { if(chkbx1.getState()) { lb1.setVisible(true); } else { lb1.setVisible(false); } } void chkbx3_itemStateChanged(ItemEvent e) { if(!chkbx2.getState()) { lb2.setText(""); } } void chkbx2_itemStateChanged(ItemEvent e) { if(chkbx2.getState()) { lb2.setText("Radio"); } }}
Java 2 Kompendium
553
Kapitel 10
Das AWT
Abbildung 10.15: Die Voreinstellung
Abbildung 10.16: Die Alternativen
Beachten Sie, dass das Programm keinen Mechanismus zum Beenden besitzt (es würde sonst noch komplexer). Sie können es auf Befehlszeilenebene mit CTRL+C beenden. Das gilt auch für einige noch folgende Beispiele. Das Beispiel verwendet (wie nahezu alle Beispiele bezüglich des Eventmodells 1.1) explizit anonyme Klassen zum Eventhandling. An dieser Stelle soll uns vor allem interessieren, dass das Ereignis itemStateChanged(ItemEvent e) verwendet wird. Dies tritt auf, wenn sich der Status eines Radiobuttons oder eines Kontrollkästchens ändert. Die Methode getState() liefert den Status zurück, der dann ausgewertet wird. Beachten Sie, dass das Kontrollkästchen als An-/Aus-Schalter fungiert, während bei den Radiobuttons die gleiche Funktionalität über zwei Objekte realisiert wird. Zudem ist erwähnenswert, dass einmal das Label nur unsichtbar und wieder sichtbar gesetzt wird, während das andere Mal der Text explizit gelöscht und wieder geschrieben wird.
10.7
Auswahlmenüs und Listenfelder
Kommen wir nun zu einer etwas komplexeren Komponente einer Benutzeroberfläche – dem Auswahlmenü oder Auswahlfeld bzw. der Kombinationsliste oder dem einzeiligen Listenfeld, wie die Komponente oft auch genannt wird. Es handelt sich dabei 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. Eng verwandt damit sind mehrzeilige Listenfelder, bei denen mehrere Einträge bereits sichtbar sind und die sich nicht mehr aufklappen lassen (nur bei Bedarf scrollen). Ein wesentlicher Unterschied bei der Auswahl der Einträge ist, dass einzeilige Listenfelder nur die Auswahl eines Werts erlauben. Wenn mehr als eine Option gleichzeitig ausgewählt werden soll, müssen Listenfelder oder Kontrollkästchen verwendet werden.
554
Java 2 Kompendium
Auswahlmenüs und Listenfelder
10.7.1
Kapitel 10
Einzeilige Listenfelder
Um ein Auswahlmenü zu erzeugen, wird eine Instanz der Choice-Klasse erstellt. Es gibt keine Optionen für den zugehörigen Konstruktor Choice(). Deshalb sieht der Vorgang immer so aus: Choice [auswahlm] = new Choice() Beispiel: Choice meinAuswahlmenue = new Choice(); Dann aber wird es etwas unterschiedlich zu den bisherigen Situationen. Es wird jetzt nicht einfach die add()-Methode verwendet, um das Objekt in einem Container zu platzieren. Erst muss das Objekt mit Einträgen gefüllt werden. Dazu benutzen Sie die public void addItem(String item)-Methode, um einzelne Optionen in der Reihenfolge hinzuzufügen, in der sie angezeigt werden sollen. Beispiel: meinAuswahlmenue.addItem("Visual Basic"); meinAuswahlmenue.addItem("C/C++"); meinAuswahlmenue.addItem("Java"); meinAuswahlmenue.addItem("Delphi"); meinAuswahlmenue.addItem("HTML"); Erst dann wird das Auswahlmenü in einem Container platziert – wie üblich mit der add()-Methode. Beispiel: add(meinAuswahlmenue); Ein vollständiges Beispiel demonstriert die Anwendung: import java.awt.*; public class ChoiceTest extends java.applet.Applet { public void init() { Choice meinAuswahlmenue = new Choice(); meinAuswahlmenue.addItem("Visual Basic"); meinAuswahlmenue.addItem("C/C++"); meinAuswahlmenue.addItem("Java"); meinAuswahlmenue.addItem("Delphi"); meinAuswahlmenue.addItem("HTML"); add(meinAuswahlmenue); } }
Listing 10.11: Ein einzeiliges Listenfeld
Sie können übrigens dynamisch mit dem Hinzufügen von Optionen zu einem bestehenden Menü fortfahren. Das bedeutet, dass auch nach dessen Erstellung und Platzierung im Container die Einträge verändert, reduziert oder erweitert werden können. Es gibt dabei einige interessante Methoden, mit denen Sie Auswahlmenüs beeinflussen und überprüfen können. Die Methode public void select(int pos) wählt das an der angegebenen Stelle befindliche Element aus. Dabei ist zu beachten, dass von 0 ab gezählt wird. In unserem oberen Beispiel wählt meinAuswahlmenue.select(2) »Java« aus. Java 2 Kompendium
555
Kapitel 10
Das AWT
Abbildung 10.17: Das aktivierte Auswahlmenü
Die Methode public void select(String str) wählt das Element mit der angegebenen Zeichenkette aus. In Analogie zu dem eben gewählten Beispiel selektiert meinAuswahlmenue.select("Java") auch diesen Eintrag. Die public int getSelectedIndex()-Methode gibt die Position des ausgewählten Elements zurück. Dabei ist wieder zu beachten, dass von 0 ab gezählt wird. Die public String getSelectedItem()-Methode gibt den Zeichennamen des momentan ausgewählten Elements zurück. Die public String getItem(int index)-Methode gibt die Zeichenkette des Elements an diesem Index aus. Über String ausgewaehlterEintrag = meinAuswahlmenue.getItem(2) würde in der angegebenen Variable der Wert »Java« gespeichert. Die public int countItems()-Methode bestimmt die Anzahl der im Menü enthaltenen Elemente. Bei der Reaktion auf Auswahlmenüs kann man im Rahmen des Eventmodells 1.0 wie bei allen Komponenten auf die action()-Methode zurückgreifen. Diese wird bei einer Auswahl aufgerufen, sobald ein Feld selektiert worden ist, sogar dann, wenn die gleiche Auswahl wiederholt wird. Der welcheAktion-Parameter enthält den Namen des ausgewählten Elements, der in einer String-Variable gespeichert werden kann. Eine action()-Methode bei einem Auswahlmenü wird normalerweise ungefähr wie folgt aussehen: String auswahl; public boolean action(Event evt, Object welcheAktion) if (!(event.target instanceof Choice)) { return false; } Choice welcheWahl = (Choice) evt.target; if (welcheWahl == meinAuswahlmenue) { auswahl = (String) welcheAktion; ... tue etwas sinnvolles ...
556
Java 2 Kompendium
Auswahlmenüs und Listenfelder
Kapitel 10
return true; } return false; }
Wandeln wir unser Beispiel, bei dem die Hintergrundfarbe je nach Auswahl gesetzt wird, in eine Selektion über ein Auswahlmenü um. import java.awt.*; public class ChoiceAction extends java.applet.Applet { public void init() { Choice meinAuswahlmenue = new Choice(); meinAuswahlmenue.addItem("Blau"); meinAuswahlmenue.addItem("Rot"); meinAuswahlmenue.addItem("Grün"); meinAuswahlmenue.addItem("Gelb"); add(meinAuswahlmenue); // Vorbelegen der Hintergrundfarbe passend zur // Vorselektion setBackground(Color.blue); } public boolean action(Event evt, Object welcheAktion) // Überprüfung, ob ein Kontrollkästchen oder ein // Radiobutton // Falls nicht Rückgabewert false { if (!(evt.target instanceof Choice)) { return false; } // Instanz des Ereignisses Choice welcheAuswahl = (Choice)evt.target; if (welcheAuswahl.getSelectedItem() == "Blau") { setBackground(Color.blue); return true; } if (welcheAuswahl.getSelectedItem() == "Rot") { setBackground(Color.red); return true; } if (welcheAuswahl.getSelectedItem() == "Grün") { setBackground(Color.green); return true; } if (welcheAuswahl.getSelectedItem() == "Gelb") { setBackground(Color.yellow); return true; } return false; } }
Java 2 Kompendium
Listing 10.12: Ein einzeiliges Listenfeld mit Reaktion nach dem Eventmodell 1.0
557
Kapitel 10
Das AWT
Abbildung 10.18: Das aktivierte Auswahlmenü hat die Farbe verändert.
Das zweite Beispiel zu diesem Thema zeigt die Reaktion nach dem Eventmodell 1.1, dieses Mal nicht als Applet, sondern als eigenständige Applikation. Es kommt wieder das Ereignis itemStateChanged(ItemEvent e) zum Zug, dass wir eben schon angewendet haben. Listing 10.13: Ein einzeiliges Listenfeld mit Reaktion nach dem Eventmodell 1.1
558
import java.awt.*; import java.awt.event.*; public class ChoiceAction2 extends Frame { Choice meinAuswahlmenue = new Choice(); public ChoiceAction2() { initial(); } public static void main(String[] args) { ChoiceAction2 choiceAction2 = new ChoiceAction2(); } void initial() { meinAuswahlmenue.addItem("Blau"); meinAuswahlmenue.addItem("Rot"); meinAuswahlmenue.addItem("Grün"); meinAuswahlmenue.addItem("Gelb"); meinAuswahlmenue.addItemListener(new java.awt.event.ItemListener() { public void itemStateChanged(ItemEvent e) { meinAuswahlmenue_itemStateChanged(e); } }); this.add(meinAuswahlmenue, BorderLayout.NORTH); setSize(300,200); show(); } void meinAuswahlmenue_itemStateChanged(ItemEvent e) { if (meinAuswahlmenue.getSelectedItem() == "Blau") { setBackground(Color.blue); } if (meinAuswahlmenue.getSelectedItem() == "Rot") { setBackground(Color.red);
Java 2 Kompendium
Auswahlmenüs und Listenfelder
Kapitel 10
} if (meinAuswahlmenue.getSelectedItem() == "Grün") { setBackground(Color.green); } if (meinAuswahlmenue.getSelectedItem() == "Gelb") { setBackground(Color.yellow); } } } Abbildung 10.19: Das aktivierte Auswahlmenü hat die Farbe verändert.
Beachten Sie, dass das Programm wieder keinen Mechanismus zum Beenden besitzt. Sie können es auf Befehlszeilenebene mit CTRL+C beenden.
10.7.2
Mehrzeilige Listenfelder
Mehrzeilige Listenfelder sind nahe Verwandte der Auswahlmenüs. Es gibt – wie schon angedeutet – jedoch einige wichtige Unterschiede. Mit den Elementen der List-Klasse erzeugen Sie keine Popup- bzw. Pulldown-Menüs, sondern permanent sichtbare Listen. Insbesondere können scrollbare Wertebereiche erzeugt werden. Sofern nicht alle Einträge direkt angezeigt werden können, wird automatisch eine Bildlaufleiste generiert. Ein weiterer Unterschied zu Auswahlmenüs ist der, dass nicht nur eine von sich gegenseitig ausschließenden Optionen ausgewählt werden kann, sondern auch bei entsprechender Konfiguration mehrere Werte ausgewählt werden können (sofern sie sich nicht gegenseitig ausschließen). Ein Listenfeld lässt sich entsprechend konfigurieren. Zum Erzeugen von Listenfeldern legen Sie eine Instanz der List-Klasse an und fügen danach die gewünschten Einträge hinzu. Die List-Klasse besitzt zwei Konstruktoren. Der Konstruktor List() erzeugt eine Liste, die keine Mehrfachauswahl erlaubt. Beispiel: List meineListe = new List(); Der zweite Konstruktor List(int, boolean) erzeugt eine Liste, in der die Anzahl der Listeneinträge festgelegt werden kann, die im Listenfenster zu
Java 2 Kompendium
559
Kapitel 10
Das AWT einem beliebigen Zeitpunkt sichtbar sind (der Integer-Wert), und ob auch Mehrfachauswahlen erlaubt (der boolesche Wert auf true) oder verboten (der boolesche Wert auf false) sind. Machen wir uns das an zwei Beispielen kurz klar: Eine Liste mit maximal 10 sichtbaren Einträgen und erlaubter Mehrfachauswahl: List meineListe = new List(10, true);
Eine Liste mit maximal 5 sichtbaren Einträgen ohne Mehrfachauswahl: List meineListe = new List(5, false);
Die Anzahl der maximal sichtbaren Einträge begrenzt jedoch nicht die Anzahl der maximal in der Liste erlaubten Einträge. Gibt es mehr Einträge, wird die Liste scrollbar dargestellt. Genausowenig erzwingt sie, dass mindestens so viele Einträge enthalten sind. Wenn weniger Einträge vorhanden sind, bleibt ein Bereich einfach leer. Wenn die Liste erzeugt wurde, können Sie mit der schon von Auswahlmenüs bekannten addItem()-Methode neue Einträge in diese hinzufügen. Beispiel: meineListe.addItem("Blau"); meineListe.addItem("Rot"); meineListe.addItem("Grün"); meineListe.addItem("Gelb");
Es ist ebenfalls möglich, Elemente an einer bestimmten Position in der Liste hinzuzufügen, auch zur Laufzeit. Die Listenpositionen werden, von 0 beginnend, durchnummeriert. Wenn ein Element an der Position 0 hinzugefügt wird, ist es das erste in der Liste. Wenn es an der Position -1 eingefügt werden soll oder Sie eine Position auswählen, die höher ist als die in der Liste verfügbaren Positionen, wird das Element ans Ende der Liste gestellt. Beispiel: meineListe.addItem("Grün",0); // Einfügen an Position 0 meineListe.addItem("Grün",-1); // Am Ende einfügen
Listenfelder in einem Container zu platzieren bedeutet nichts Neues. Wie bisher dient zum Platzieren eines Listenfelds in einem Container die add()Methode. Beispiel: add(meineListe);
560
Java 2 Kompendium
Auswahlmenüs und Listenfelder
Kapitel 10
Zur Vollständigkeit wieder ein kleines Beispiel. import java.awt.*; public class Liste extends java.applet.Applet { // 5 sichtbare Einträge, Mehrfachauswahl erlaubt List meineListe = new List(5, true); public void init() { meineListe.addItem("Blau"); meineListe.addItem("Rot"); meineListe.addItem("Grün"); meineListe.addItem("Gelb"); meineListe.addItem("Cyan"); meineListe.addItem("Pink"); meineListe.addItem("Braun"); meineListe.addItem("Schwarz"); add(meineListe);} }
Listing 10.14: Ein mehrzeiliges Listenfeld mit Mehrfachauswahl
Abbildung 10.20: Eine Auswahlliste mit Mehrfachauswahl, wo nicht alle Einträge angezeigt werden
Die Bildlaufleisten und Schieber zum Scrollen des Inhalts werden übrigens automatisch generiert und sind (wie gewohnt) nicht vorhanden, wenn weniger Inhalt anzuzeigen ist, als Platz zur Verfügung steht. Das nachfolgende Beispiele zeigt es. import java.awt.*; public class Liste2 extends java.applet.Applet { // 5 sichtbare Einträge, // keine Mehrfachauswahl erlaubt List meineListe = new List(5, false); public void init() { // nur 4 Einträge meineListe.addItem("Blau"); meineListe.addItem("Rot"); meineListe.addItem("Grün"); meineListe.addItem("Gelb"); add(meineListe);}}
Java 2 Kompendium
Listing 10.15: Ein mehrzeiliges Listenfeld mit Leerraum
561
Kapitel 10
Das AWT
Abbildung 10.21: Eine Auswahlliste ohne Mehrfachauswahl, wo alle Einträge sichtbar sind (keine Bildlaufleisten)
Um Listenfelder zu beeinflussen und zu überprüfen, stellt die List-Klasse verschiedene Methoden bereit. Damit kann der Inhalt einer Liste manipuliert und verändert werden. Die Methode public void replaceItem(String newValue, int index) ersetzt ein an der angegebenen Position (der Integer-Wert) vorhandenes Element durch ein neues Element (der String-Wert). Beispiel: meineListe.replaceItem("Grün",1);
Die Methode ersetzt an der Position 1 den Wert durch »Grün«. Die Methode public void delItem(int position) löscht ein Element in der Liste an der angegebenen Position. Beispiel: meineListe.delItem(2); // löscht 3. Eintrag der Liste
Die public void delItems(int start, int end)-Methode löscht einen ganzen Bereich von Elementen aus der Liste. Der erste Integer-Wert gibt den Beginn und der zweite Integer-Wert das Ende der zu löschenden Elemente in der Liste an. Bei dieser Methode muss allerdings der Hinweis erfolgen, dass sie nicht nur – wie einige der anderen Methoden – als deprecated angeben wird, sondern von Sun explizit von der Verwendung abgeraten wird. Dennoch ein Beispiel: meineListe.delItems(2, 5); inklusive Position 5 */
/*
Löschen
von
der
Position
2
bis
Mittels der public void clear()-Methode können Sie alle Elemente in der Liste löschen. Beispiel: meineListe.clear();
562
Java 2 Kompendium
Auswahlmenüs und Listenfelder
Kapitel 10
Die bereits bekannte Methode getSelectedIndex() gibt die Indexposition des aktuell ausgewählten Elements zurück. Sofern kein Element ausgewählt worden ist, wird -1 zurückgegeben. Beispiel: int aktAusw = meineListe.getSelectedIndex();
Die bereits bekannte Methode getSelectedItem() gibt das ausgewählte Element als Zeichenkette zurück. Beispiel: String aktAusw = meineListe.getSelectedItem();
Die Methode public int[] getSelectedIndexes() gibt bei Listen mit erlaubter Mehrfachauswahl alle Auswahlen zurück. Dabei muss beachtet werden, dass die Anzahl der zurückgegebenen Werte natürlich nicht von vornherein feststeht. Beispiel: int aktuelleAuswah[] = meineListe.getSelectedIndexes();
Die Methode public String[] getSelectedItems() gibt eine Reihe von Zeichenketten zurück, die die ausgewählten Elemente enthalten. Beispiel: String aktuelleAuswahlen[] = meineListe.getSelectItems();
Mittels der bekannten Methode select(int) können Sie ein beliebiges Element auswählen. Wenn die Liste keine Mehrfachauswahl zulässt, wird das vorher ausgewählte Element abgewählt. Beispiel: mListe.select(2); // Wählt das 3. Element aus
Die Methode select(String) kennen wir auch schon. Sie arbeitet fast identisch. Damit können Sie ein mit String bezeichnetes Element auswählen. Beispiel: meineListe.select("Blau"); /* Wählt das Element mit dem passenden Eintrag aus */
Die Methode public void deselect(int index) deselektiert das angegebene Element. Beispiel: meineListe.deselect(2);
Diese Methode deselektiert das dritte Element der Liste. Die bekannte Methode isSelected(int) überprüft, ob das Element mit dem angegebenen Index ausgewählt worden ist oder nicht und liefert ein boolesches Ergebnis zurück, das beispielsweise in Schleifen ausgewertet werden kann.
Java 2 Kompendium
563
Kapitel 10
Das AWT Beispiel: if (meineListe.isSelected(2)) { // tue etwas, wenn das 3. Element ausgewählt wurde }
Mit der Methode public void setMultipleSelections(boolean b) können Sie die Mehrfachauswahl dynamisch an- und abstellen. Beispiel: meineListe.setMultipleSelections(true);
Die Methode bedeutet das Aktivieren der Multiselektion. Passend dazu können Sie überprüfen, ob Mehrfachauswahl erlaubt ist. Dazu dient die public boolean allowsMultipleSelections()-Methode. Sie gibt den Wert true zurück, sofern Mehrfachauswahl erlaubt ist, andernfalls false. Beispiel: if (meineListe.allowsMultipleSelections()) { // tue etwas sinnvolles }
Die Methode public void makeVisible(int index) stellt sicher, dass ein mit dem Integer-Wert angegebenes Element in dem Listenfenster sichtbar ist. Beispiel: meineListe.makeVisible(10);
Die Anweisung macht das Element Nr. 11 sichtbar. Die uns bereits bekannte Methode countItems() gibt die Anzahl der Einträge in der Liste aus. Beispiel: int anzahl = meineListe.countItems();
Die Reaktion von Listenfeldern auf Aktionen im Rahmen des Eventmodells 1.0 unterscheidet sich von den bisherigen Komponenten. Es wird nicht die action()-Methode aufgerufen. Statt dessen wird eine alternative Möglichkeit verwendet – die handleEvent()-Methode. Diese stellt dazu die Reaktionsmöglichkeit auf zwei Ereignisse der Listenfelder zur Verfügung: Reaktion auf Listenauswahl Reaktion auf Deselektion eines Listeneintrags Die Syntax der handleEvent()-Methode kennen wir bereits: public boolean handleEvent(Event evt)
564
Java 2 Kompendium
Auswahlmenüs und Listenfelder
Kapitel 10
Wir wollen dennoch auf ein Beispiel nach dem alten Eventmodell verzichten und gleich ein Beispiel nach dem Eventmodell 1.1 durchspielen. Die Details wiederholen sich. import java.awt.*; import java.awt.event.*; public class Liste3 extends Frame { List meineListe = new List(); public Liste3() { initial(); } public static void main(String[] args) { Liste3 liste3 = new Liste3(); } private void initial() { meineListe.setMultipleMode(false); meineListe.addItemListener( new java.awt.event.ItemListener() { public void itemStateChanged(ItemEvent e) { meineListe_itemStateChanged(e); }}); meineListe.addItem("Blau"); meineListe.addItem("Rot"); meineListe.addItem("Grün"); meineListe.addItem("Gelb"); this.add(meineListe, BorderLayout.WEST); setSize(300,200); show(); } void meineListe_itemStateChanged(ItemEvent e) { if(meineListe.getSelectedItem() == "Blau") { setBackground(Color.blue); } if (meineListe.getSelectedItem() == "Rot") { setBackground(Color.red); } if (meineListe.getSelectedItem() == "Grün") { setBackground(Color.green); } if (meineListe.getSelectedItem() == "Gelb") { setBackground(Color.yellow); } }}
Listing 10.16: Ein mehrzeiliges Listenfeld mit Reaktion
Beachten Sie, dass das Programm keinen Mechanismus zum Beenden besitzt. Sie können es auf Befehlszeilenebene mit CTRL+C beenden.
Java 2 Kompendium
565
Kapitel 10
Das AWT
Abbildung 10.22: Reaktion auf die Auswahl
10.8
Textbereiche und Textfelder
Um Anwendern eine vernünftige Benutzeroberfläche zur Verfügung zu stellen, müssen Möglichkeiten zur Eingabe von freien Texten vorhanden sein. Das AWT verfügt deshalb über verschiedene Klassen, um Textdaten einzugeben – die Klasse TextField für die so genannten Textfelder und die Klasse TextArea für die Textbereiche. Wesentlicher Unterschied zwischen beiden Elementen ist, dass Textbereiche Funktionalitäten zur Behandlung von grö ß eren Textmengen zur Verfügung stellen. Textfelder begrenzen die Menge der einzugebenden Texts und können insbesondere nicht gescrollt werden. Sie eignen sich deshalb hauptsächlich für einzeilige Eingabefelder mit begrenzter Textmenge. Dahingegen können in Textbereiche mehrere Zeilen verarbeitet werden, und Textbereiche sind scrollbar. Dementsprechend haben sie standardmäßig Bildlaufleisten. Trotz der Unterschiede besitzen beide Klassen viele ähnliche Methoden, da sie beide aus der gemeinsamen Klasse TextComponent abgeleitet sind. Das Erzeugen von Textbereichen und Textfeldern ist sehr ähnlich. Sie müssen für einen Textbereich nur zusätzlich sowohl Spalten als auch Reihen (Zeilen) angeben, wenn Sie eine bestimmte Grö ß e für einen Textbereich angeben. Wenden wir uns zunächst dem Textbereich zu.
10.8.1
Textbereiche
Zum Erstellen eines Textbereiches können Sie einen der folgenden Konstruktoren verwenden: TextArea() erzeugt einen leeren Textbereich. Das Problem ist hier, dass damit weder die Anzahl von Spalten und Zeilen genau bestimmt ist. Beide sind erst einmal auf 0 festgelegt. Bevor dieser Textbereich in ein Panel eingefügt und dort angezeigt werden kann, muss die Anzahl von Spalten und Zeilen verändert werden.
566
Java 2 Kompendium
Textbereiche und Textfelder
Kapitel 10
Beispiel: TextArea meinTextBereich = new TextArea();
Sie können ebenso einen Textbereich mit Text vorbelegen. Dazu dient der Konstruktor TextArea(String). Auch hier müssen Sie die genaue Grö ß e des Textbereichs noch festlegen. Beispiel: TextArea meinTextBereich = new TextArea("Vorbelegen oder nicht vorbelegen?");
Der Konstruktor TextArea(int, int) erzeugt einen leeren Textbereich mit einer bereits festgelegten Anzahl von Spalten und Zeilen. Beispiel: TextArea meinTextBer = new TextArea(13, 42 ); // 13 Zeilen, 42 Spalten
Sie können ebenso einen Textbereich mit einem Anfangstext und mit einer bereits festgelegten Anzahl von Spalten und Zeilen erzeugen. Dazu dient der Konstruktor TextArea(String, int, int). Beispiel: TextArea meinTextBer = new TextArea("Belag", 13, 42 ); // 13 Zeilen, 42 Spalten
10.8.2
Textfelder
Auch für Textfelder gibt es diverse Konstruktoren. Der Konstruktor TextField() ist die einfachste Möglichkeit, ein Textfeld zu erstellen. Dadurch wird wie im Fall der Textbereiche ein leeres Textfeld mit einer noch nicht spezifizierten Anzahl von Spalten (Default 0) erzeugt. Zum Vorbelegen eines Textfeldes (mit einer noch nicht spezifizierten Anzahl von Spalten) dient der Konstruktor TextField(String). Beispiel: TextField meinTextFeld = new TextField("Belag"); In der Regel ist es sinnvoll, die Anzahl von Spalten/Zeichen in dem Textfeld festzulegen. Das funktioniert mit dem Konstruktor TextField(int). Beispiel: TextField mTxtFld = new TextField(42); Wie bei den Textbereichen kann man die Angabe der Grö ß e mit einer Vorbelegung kombinieren. Dazu dient der Konstruktor Textfield(String, int). Beispiel: TextField mTxtFd = new TextField("Belag?", 42); // Vorbelegt + 42 Spalten groß
Java 2 Kompendium
567
Kapitel 10
Das AWT Das Platzieren von Textbereichen und Textfeldern in einem Container funktioniert wie bei den meisten Komponenten mit der add()-Methode. Schauen wir uns wieder ein kleines Beispiel an:
Listing 10.17: Textfelder
import java.awt.*; public class Text1 extends java.applet.Applet { // 3 Zeilen, 42 Spalten TextArea meinTextBereich1 = new TextArea(3, 42 ); // 8 Zeilen, 20 Spalten, vorbelegt TextArea meinTextBereich2 = new TextArea("Vorbelegen oder nicht orbelegen?", 8, 20 ); // 15 Spalten TextField meinTextFeld1 = new TextField(15); // Vorbelegt und 27 Spalten groß TextField meinTextFeld2 = new TextField("Vorbelegen oder nicht vorbelegen?", 27); public void init() { add(meinTextFeld1); add(meinTextBereich1); add(meinTextFeld2); add(meinTextBereich2); } }
Abbildung 10.23: Textfelder
568
Java 2 Kompendium
Textbereiche und Textfelder
Kapitel 10
Textkomponenten lassen sich natürlich auch beeinflussen und überprüfen. Da sowohl Textbereiche als auch Textfelder von der abstrakten Klasse TextComponent erben, sind die meisten Methoden dieser Klasse in beiden Textkomponenten anzuwenden.
10.8.3
Gemeinsame Methoden
Die Methode public String getText() gibt den Textinhalt eines Textfeldes oder eines Textbereiches zurück. Beispiel: String textinhalt = meinTextFeld.getText();
Die Methode public void setText(String t) fügt den mit String angegebenen Text in die Komponenten ein. Beispiel: mTxtFd.setText("Schiebe Text in Textfeld");
Mit der Methode public String getSelectedText() können Sie herausfinden, welcher Text selektiert worden ist. Beispiel: String ausgew = mTxtBereich.getSelectedText();
Eng mit der Methode getSelectedText() zusammenhängend sind die Methoden public int getSelectionStart() und public int getSelectionEnd(). Sie können damit herausfinden, wo eine Auswahl beginnt und wo sie aufhört. Beide Methoden liefern einen IntegerWert, der die relativen Positionen (ganzzahlig) des selektierten Anfangs- und Endpunktes der Selektion für den gesamten Text angeben. Wenn die Auswahl beispielsweise am Anfang des Textes beginnt, gibt die Methode getSelectionStart() den Wert 0 zurück. Beispiel: int auswahlStart, auswahlEnde; auswahlStart = meinTextFeld.getSelectionStart(); auswahlEnde = meinTextFeld.getSelectionEnd();
Zum Selektieren eines bestimmten Textbereiches innerhalb einer Textkomponente mittels einer Methode gibt es wieder select(int, int). Diese Methode wählt den Text zwischen dem ersten Integerwert (Anfangspunkt der Selektion) und dem zweiten Integerwert (Endpunkt der Selektion) aus. Beispiel: meinTextFeld.select(0, 42);
Die Methode selektiert die Zeichen vom Anfang bis zur Position 42 inklusive.
Java 2 Kompendium
569
Kapitel 10
Das AWT Zur Auswahl des gesamten Textes gibt es die Methode public void selectAll().
Beispiel: meinTextBereich.selectAll();
Die Methode selektiert alle Zeichen im Textbereich. Nicht immer ist Text in einer Textkomponente editierbar. Zur Überprüfung können Sie die Methode public boolean isEditable() verwenden. Diese Methode liefert einen booleschen Wert zurück, der true ist, wenn die Komponente bearbeitet werden kann und false, wenn sie nicht bearbeitet werden kann. Beispiel: boolean aenderbar; aenderbar = meinTextBereich.isEditable();
Wenn Sie selbst eine Textkomponente als nicht-editierbar definieren oder sie wieder als editierbar freigeben wollen, haben Sie die Schwestermethode public void setEditable(boolean b) zur Verfügung. Wenn Sie true als booleschen Wert verwenden (Standardwert), ist eine Textkomponente editierbar, bei Verwendung von false hingegen nicht (also read-only). Beispiel: meinTextFeld.setEditable(false);
Mit der Methode public int getColumns() können Sie die Breite einer Textkomponente sowohl im Textbereich als auch im Textfeld in Zeichen bzw. Spalten erfragen (d.h. die Anzahl der sichtbaren Spalten und nicht wie viel Text da ist). Beispiel: int breite = meinTextBereich.getColumns();
10.8.4
Spezielle TextField-Methoden
Textfelder besitzen einige spezielle Eigenschaften, die Textbereiche nicht haben. Dementsprechend gibt es einige Methoden, die nur dort Sinn machen. Textfelder eignen sich insbesondere für die zur Verfügungstellung von Eingabemöglichkeiten von Passwörtern. Sie kennen sicher das Phänomen, dass bei der Eingabe von Passwörtern die eingegebenen Zeichen nicht auf dem Bildschirm angezeigt werden. Sie werden durch AliasZeichen (meist der Stern *) ersetzt. Man nennt diese Alias-Zeichen auch Echo-Zeichen. In der TextField-Klasse findet sich nun eine Methode, mit der Sie ein Echo-Zeichen setzen können, das anstelle des eingegebe-
570
Java 2 Kompendium
Textbereiche und Textfelder
Kapitel 10
nen Zeichens auf dem Bildschirm dargestellt wird. Die Methode heißt public void setEchoCharacter(char c). Beispiel: meinTextFeld.setEchoCharacter('*');
Die Anweisung ersetzt alle eingegebenen Zeichen durch *. Wenn Sie das explizit verwendete Echozeichen für ein Feld abfragen wollen, können Sie die Methode public char getEchoChar() verwenden. Beispiel: char echoChar = meinTextFeld.getEchoChar();
Bevor Sie ein Echozeichen abfragen, ist es sicher sinnvoll zu überprüfen, ob das Textfeld überhaupt ein Echozeichen besitzt. Die Methode public boolean echoCharIsSet() gibt true zurück, wenn das Textfeld mit einen Echozeichensatz belegt ist, sonst wird sie false zurückgegeben. Beispiel: boolean echCh_janein = meinTextFeld.echoCharIsSet();
10.8.5
Spezielle TextArea-Methoden
Wie wir wissen, haben auch Textbereiche einige spezielle Eigenschaften, die Textfelder nicht besitzen. Einige davon dienen der Bearbeitung von Text und Realisieren das Einfügen, Anhängen und Ersetzen von Text. Die Methode public void appendText(String str) hängt den mit str spezifizierten Text an das Ende eines Textbereichs an. Beispiel: meinTextBereich.appendText("Häng das an das Ende des bisher im Textbereich vorhandenen Textes an.");
Die Methode public void insertText(String str, int pos) fügt an der mit int spezifizierten Stelle den mit String angegebenen Text ein. Beispiel: meinTextBereich.insertText("Füge Textbereiches ein.", 0);
den
Text
am
Anfang
des
Die Methode public void replaceText(String str, int start, int end) ersetzt die Teile des Textes, die sich zwischen der Anfangsposition (1. Integerwert) und der Endposition (2. Integerwert) befinden durch den mit dem String spezifizierten Text. Beispiel: meinTextBereich.replaceText("Ersetze den Text zwischen dem Anfang des Textbereiches und der Position 42 (inkl.) durch diesen Text.", 0, 42);
Java 2 Kompendium
571
Kapitel 10
Das AWT Die Ermittlung von der Anzahl der Spalten ist bei beiden Textkomponenten identisch (die Methode getColumns()). Die Anzahl der Zeilen in einem Textbereich macht jedoch natürlich nur bei Textbereichen Sinn. Mit der Methode public int getRows() wird die Anzahl der Zeilen eines Textbereichs ermittelt. Diese ist in der Regel nicht identisch mit der Anzahl der im Textbereich befindlichen Textzeilen. Beispiel: int laenge = meinTextBereich.getRows();
Reaktionen auf Ereignisse in Textbereichen und Textfeldern zählen nicht zu den häufig ausgewerteten Events. Bis auf wenige Ausnahmen gilt, dass Textbereiche überhaupt keine eigenständige Reaktion auf Ereignisse zeigen sollten. Dies wird bei genauerer Überlegung klar. Wie verhalten sich Textbereiche auf den grafischen Oberflächen, die Sie kennen? Die Ereignisse, die Sie für TextArea- oder TextField-Objekte erzeugen können, sind Tastatur- und Mausereignisse. Bei Eingabe einer bestimmten Tastenkombination oder einem Klick auf den Textbereich würde eine Reaktion erzeugt. Ist Ihnen dieses Verhalten bekannt? Wahrscheinlich nicht, denn üblicherweise werden die Reaktionen mit einer Schaltfläche oder einem Menü für den Anwender gekoppelt sein. Erst diese lösen dann eine Reaktion aus. Wenn dennoch ein Ereignis direkt ausgewertet werden soll, sollte unter dem Eventmodell 1.0 beachtet werden, dass (wie auch die List-Klasse) die TextArea-Klasse nicht die action()-Methode verwendet. Im Prinzip steht Ihnen die handleEvent()-Methode zur Verfügung (die bei Listen zum Einsatz kommt). Sie sollten diese jedoch nicht verwenden. Die TextField-Klasse verwendet zwar die action()-Methode, allerdings nur dann, wenn der Anwender die Eingabetaste drückt. Aber auch hier gilt, dass eine Kopplung mit einer Schaltfläche mehr Sinn macht. Diese Bemerkungen gelten natürlich auch für das aktuelle Eventmodell.
10.9
Schieber und Bildlaufleisten
Die beiden AWT-Elemente Listen und Textbereiche besitzen standardmäß ig die Eigenschaft, bei Bedarf nicht in den Anzeigebereich passenden Inhalt per Bildlaufleisten und Schieber zu scrollen. Die Bildlaufleisten und Schieber sind bereits in diese Elemente der Benutzeroberfläche integriert und werden nur dann zur Verfügung gestellt, wenn es notwendig ist. Sie können jedoch auch individuell einzelne Bildlaufleisten erstellen und mit diversen Komponenten koppeln. Die Scrollbar-Klasse, auf der diese Objekte basieren, besitzt eine Basisschnittstelle für den Bildlauf, der in verschiedenen Situationen verwendet werden kann. Die Steuerelemente der Bildlaufleiste dienen zur Auswahl und Kontrolle eines Werts, der die gegenwärtige Position der Bildlaufleiste anzeigt. Dieser
572
Java 2 Kompendium
Schieber und Bildlaufleisten
Kapitel 10
Positionswert kann zwischen einem Minimumwert und einen Maximumwert gesetzt werden. Um diesen Positionswert zu ändern, können drei verschiedene Teile der Bildlaufleisten verwendet werden und damit drei verschiedene Arten der Positionsveränderung auswählen: Die Positionsveränderung über die Pfeile an den beiden Enden der Bildlaufleiste aktualisieren die Position der Bildlaufleiste über eine feste Einheit in die eine oder andere Richtung. Meist ist als Standard eine Zeile eingestellt. Man nennt diese Methode deshalb auch Zeilenaktualisierung, und es handelt sich um eine relative Positionsveränderung von der aktuellen Position aus gesehen. Die zweite Art der Positionsveränderung erfolgt über den Raum zwischen dem Schieber der Bildlaufleiste und dem Pfeil am jeweiligen Ende der Leiste. Wenn Sie dahin klicken, wird die Position um eine grö ß ere, feste Einheit verändert (Standardeinstellung 10 Zeilen oder gelegentlich auch eine Seite – deshalb hin und wieder seitenweise Aktualisierung genannt). Auch hierbei handelt es sich um eine relative Positionsveränderung von der aktuellen Position aus gesehen. Die dritte Art der Positionsveränderung ist eine absolute Positionsveränderung. Diese absolute Aktualisierung erfolgt über den Schieber (auch Schiebeknopf oder Bildlauffeld genannt), indem er in die eine oder andere Richtung gezogen wird. Sie haben keine Steuerungsmöglichkeit darüber, wie sich die Positionswerte bei einer absoluten Aktualisierung ändern. Sie können höchstens die Maximum- und Minimumwerte steuern. Eine Bildlaufleiste kann nur sich selbst aktualisieren. Dies geschieht vollkommen automatisch und braucht von Ihnen nicht ausgelöst zu werden. Sie müssen nur einen Minimumwert und einen Maximumwert festlegen. Eine Bildlaufleiste kann andererseits aber keine anderen Komponenten automatisch zum Scrollen veranlassen. Damit brauchen Sie auch keine Ereignisse der Bildlaufleiste zu verwalten, die eigentliche Funktionalität der Bildlaufleiste ist jedoch noch nicht gegeben. Wenn Sie möchten, dass über eine Bildlaufleiste der Bildinhalt eines Elements gescrollt wird (was ja meist der Sinn und Zweck einer Bildlaufleiste ist), müssen Sie über entsprechenden Code den Positionswert der Bildlaufleiste abfragen und dann den Bildinhalt entsprechend anpassen. Wir werden das in dem nachfolgenden Beispiel demonstrieren. Zum Erzeugen von Bildlaufleisten stehen Ihnen drei Konstruktoren zur Verfügung.
Java 2 Kompendium
573
Kapitel 10
Das AWT Der Konstruktor Scrollbar() ist die einfachste Variante und erzeugt eine einfache vertikale Bildlaufleiste. Die anfänglichen Minimum- und Maximumwerte sind (0, 0). Beispiel: Scrollbar mBildllst = new Scrollbar();
Der zweite Konstruktor Scrollbar(int) lässt die Angabe zu, ob es sich um eine vertikale oder horizontale Bildlaufleiste handelt. Für das Argument (ein Integerwert), das die Ausrichtung bezeichnet, können Sie die Konstanten Scrollbar.HORIZONTAL oder Scrollbar.VERTICAL angeben. Die anfänglichen Minimum- und Maximumwerte sind auch beim zweiten Konstruktor (0, 0). Beispiel: Scrollbar mBdllst = new Scrollbar(Scrollbar.HORIZONTAL);
Der Konstruktor Scrollbar(int, int, int, int, int) erzeugt eine Bildlaufleiste mit einer vordefinierten Ausrichtung, Position, Grö ß e sowie Minimum- und Maximumwerte. Das erste Argument ist die Ausrichtung der Bildlaufleiste und entspricht dem Argument des zweiten Konstruktors. Das zweite Argument ist der Anfangswert der Bildlaufleiste (also die Position des Schiebers) und sollte ein Wert zwischen Minimumwert (das vierte Argument) und Maximumwert (das fünfte Argument) der Bildlaufleiste sein. Das dritte Argument ist die Gesamtbreite oder -höhe der Bildlaufleiste (abhängig von der Ausrichtung). Beispiel: Scrollbar meineBildlaufleiste HORIZONTAL,0,10,0,100);
=
new
Scrollbar(Scrollbar.
Auch neu erstellte Bildlaufleisten müssen vor ihrer Verwendung einem Container hinzugefügt werden, was in diesem Fall dem Element entspricht, das gescrollt werden soll. Dazu dient wie üblich die add()-Methode. Die Scrollbar-Klasse stellt einige Methoden zur Verfügung, mit denen die Werte von Bildlaufleisten überprüft und beeinflusst werden können. Die Methode public int getMinimum() fragt den Minimumwert ab. Beispiel: int mini = meineBildlaufleiste.getMinimum();
Die Methode public int getMaximum() fragt den Maximumswert ab. Beispiel: int maxi = meineBildlaufleiste.getMaximum();
Mit public void setLineIncrement(int v) können Sie die Einheit setzen, um die eine Bildlaufleiste bei einer Positionsveränderung über die Pfeile an den beiden Enden der Bildlaufleiste verändert wird.
574
Java 2 Kompendium
Schieber und Bildlaufleisten
Kapitel 10
Beispiel: meineBildlaufleiste.setLineIncrement(3);
Die Anweisung setzt die Einheit auf 3. Mit public int getLineIncrement() können Sie die Einheit abfragen, um die eine Bildlaufleiste bei einer Positionsveränderung über die Pfeile an den beiden Enden der Bildlaufleiste verändert wird : Beispiel: int zeilenverschg = mBildllst.getLineIncrement();
Die Methode public int getOrientation() fragt die Ausrichtung der Bildlaufleiste ab. 0 steht für vertikal (Scrollbar.VERTICAL) und 1 für horizontal (Scrollbar.HORIZONTAL). Sie können entweder die Konstanten verwenden oder den Integerwert. Beispiel: if (meineBildlaufleiste.getOrientation() == Scrollbar.HORIZONTAL) { // tue was sinnvolles } else { // tue was sinnvolles }
Bezüglich der Ereignisbehandlung von Bildlaufleisten gibt es ein paar Feinheiten zu beachten. Die Scrollbar-Klasse verwendet im Eventmodell 1.0 – wie die List- und die TextArea-Klassen – die action()-Methode nicht. Sie müssen dort auf die handleEvent()-Methode zurückgreifen. Damit können Sie Veränderung einer Bildlaufleiste überprüfen. Die Ereignisse werden wie üblich mit evt.id an die handleEvent()-Methode übergeben. Die möglichen Werte der evt.id bei Ereignissen einer Bildlaufleiste sind folgende: Wert von evt.id
Beschreibung
Event.SCROLL_ABSOLUTE
Der Schieber der Bildlaufleiste wird gezogen.
Event.SCROLL_LINE_DOWN
Der obere (vertikale Bildlaufleiste) oder linke (horizontale Bildlaufleiste) Pfeilknopf wird angeklickt.
Event.SCROLL_LINE_UP
Der untere (vertikale Bildlaufleiste) oder rechte (horizontale Bildlaufleiste) Pfeilknopf wird angeklickt.
Event.SCROLL_PAGE_DOWN
Klick in den Bereich zwischen den Schieber und dem unteren (vertikale Bildlaufleiste) oder linken (horizontale Bildlaufleiste) Pfeil.
Event.SCROLL_PAGE_UP
Klick in den Bereich zwischen dem Schieber und dem oberen (vertikale Bildlaufleiste) oder rechten (horizontale Bildlaufleiste) Pfeil.
Java 2 Kompendium
Tabelle 10.2: Ereignisse von Bildlaufleisten im Eventmodell 1.0
575
Kapitel 10
Das AWT Das konkrete Ereignis ist oft gar nicht so von zentraler Bedeutung. In vielen Fällen genügt die Information, dass sich die Position der Bildlaufleiste verändert hat. Sie können dann einfach mithilfe der Methode public int getValue() die neue Position herausfinden. Im Rahmen des Eventmodells 1.1 erfolgt die Behandlung von Ereignissen – wie dort allgemein üblich – wieder über das vereinheitlichte Verfahren. Allgemein gilt, dass eine Bildlaufleiste Adjustment-Ereignisse sendet. Die potenziellen Empfänger implementieren die Schnittstelle AdjustmentListener und registrieren sich über addAdjustmentListener. Im Allgemeinen wird dann auf das Ereignis adjustmentValueChanged reagiert. Nachfolgend finden Sie ein kleines Beispiel, das die Position des Schiebers einer Bildlaufleiste in einem Label anzeigt. Beachten Sie, dass der Rückgabewert der Methode getValue() ein int-Wert ist, der mit einem Wrapper in ein Integer-Objekt konvertiert wird, aus dem dann ein String extrahiert wird, damit die Methode setText() verwendet werden kann.
Listing 10.18: Bildlaufleisten
576
import java.awt.*; import java.awt.event.*; public class Bild extends Frame { BorderLayout borderLayout1 = new BorderLayout(); Label label1 = new Label(); Scrollbar scrollbar1 = new Scrollbar(); public Bild() { initial(); } public static void main(String[] args) { Bild bild = new Bild(); } private void initial() { label1.setText("label1"); this.setLayout(borderLayout1); scrollbar1.addAdjustmentListener(new java.awt.event.AdjustmentListener() { public void adjustmentValueChanged(AdjustmentEvent e) { scrollbar1_adjustmentValueChanged(e); } }); add(label1, BorderLayout.CENTER); add(scrollbar1, BorderLayout.EAST); setSize(200,200); show(); } void scrollbar1_adjustmentValueChanged( AdjustmentEvent e) { label1.setText((new Integer(scrollbar1.getValue())).toString()); }}
Java 2 Kompendium
Schieber und Bildlaufleisten
Kapitel 10 Abbildung 10.24: Der Wert der Bildlaufleiste wird im Label ausgegeben.
Das AWT stellt seit dem JDK 1.1 neben einfachen Bildlaufleisten noch eine weitere Komponente zur Verfügung, die einen Container für automatisches horizontales und vertikales Scrollen darstellt – ScrollPane. Es handelt sich dabei um ein spezielles Panel, das sich in folgenden Details von einem gewöhnlichen Panel unterscheidet: Es kann nur genau eine Komponente aufnehmen. Es verwendet keinen Layoutmanager. Es kann eine Ausgabefläche verwalten, die über den angezeigten Bereich hinausgeht (ein so genannter virtueller Ausgabebereich). Gerade der dritte Punkt stellt die Funktionalität dar, die zum Scrollen benötigt wird. Wenn ein in einem ScrollPane-Objekt dargestellter Inhalt nicht vollständig angezeigt werden kann, werden automatisch Scrollbars eingeblendet. Für die Erzeugung eines ScrollPane-Objekts gibt es einmal den leeren Konstruktor und einen Kontruktor mit einem int-Wert, in dem der int-Wert drei Zustände des ScrollPane-Objekts festlegen kann. Diese können auch wieder über Konstanten festgelegt werden. Konstante
Beschreibung
ScrollPane.SCROLLBARS_AS_NEEDED
Wenn notwendig, werden Scrollbars angezeigt.
ScrollPane.SCROLLBARS_NEVER
Niemals Scrollbars anzeigen. Eine Verschiebung ist nur programmtechnisch möglich.
ScrollPane.SCROLLBARS_ALWAYS
Immer Scrollbars anzeigen.
Tabelle 10.3: ScrollPaneKonstanten
Das Hinzufügen eines ScrollPane-Objekts zu einem Container erfolgt wie üblich mit add(). Die Methode setSize() legt die sichtbare Grö ß e des ScrollPane-Objekts fest (im Rahmen der Layouts des umgebenden Containers), die Grö ß e des virtuellen Ausgabebereichs wird durch die enthaltene
Java 2 Kompendium
577
Kapitel 10
Das AWT Komponente bestimmt. Betrachten wir ein kleines Beispiel (etwas künstlich, aber die Grundidee ist sicher zu erkennen).
Listing 10.19: Bildlaufleisten am Button
import java.awt.*; import java.applet.*; public class ScrollPane1 extends Applet { ScrollPane scrollPane1 = new ScrollPane(ScrollPane.SCROLLBARS_ALWAYS); BorderLayout borderLayout1 = new BorderLayout(); public void init() { setLayout(borderLayout1); scrollPane1.add(new Button("Test")); add(scrollPane1, BorderLayout.CENTER); } }
Abbildung 10.25: Der Button mit Scrollbars
10.10
Zeichenbereiche
Die meisten AWT-Komponenten bieten Möglichkeiten, darauf Zeichnenoperationen auszuführen oder Grafiken anzuzeigen. Zeichenbereiche sind jedoch AWT-Komponenten, die speziell zum Zeichnen da sind. Sie enthalten keine anderen Komponenten, akzeptieren demgegenüber jedoch Ereignisse. Außerdem lassen sich Animationen und Bilder in Zeichenbereichen darstellen. Insbesondere erlauben Zeichenbereiche, die Hintergrundfarbe zu beeinflussen. Die standardmäß ig in allen Applets und AWT-Komponenten bereitstehende paint()-Methode zeichnet erst einmal nur in der aktuellen Hintergrundfarbe. Eine in einem Canvas-Objekt überschriebene Methode hat da natürlich viel mehr Möglichkeiten. Zeichenbereiche werden erzeugt, indem Sie Instanzen der Canvas-Klasse generieren oder eine Klasse von Canvas ableiten.
578
Java 2 Kompendium
Zeichenbereiche
Kapitel 10
Beispiel: Canvas meinZeichenbereich= new Canvas(); Mittels der add()-Methode wird der neu erstellte Zeichenbereich wieder in einen Container eingefügt. Beispiel: add(meinZeichenbereich); Wir spielen das Verfahren in einem einfachen Beispiel durch. Dazu verwenden wir zwei Klassen. Die eine Klasse wird als Erweiterung von Canvas gebildet und lädt ein Bild aus einer relativen Pfadangabe. Im Gegensatz zu den bisherigen Fällen verwenden wir für das gesamte Beispiel kein Applet, sondern eine eigenständige Applikation. Damit wird das Laden eines Bildes auch nicht mehr ganz so verlaufen, wie es bei einem Applet erfolgt. Zwar greifen wir wieder auf die Methode getImage() zurück, aber nicht die der Applet-Klasse. Wir verwenden die Methode public abstract Image getImage(String filename) aus der Klasse java.awt.Toolkit. Damit wir diese Methode anwenden können, benötigen wir allgemein bei Frames das Toolkit des Frames der Applikation. Dieses liefert die Methode public Toolkit getToolkit() aus java.awt.Window. In unserem Fall, wo wir eine Canvas-Komponente verwenden, können wir die Methode aus java.awt.Component verwenden, die für AWT-Komponenten deren Toolkit liefert. Listing 10.20: Eine von Canvas abgeleitete Klasse, in der ein Bild geladen wird.
import java.awt.*; public class MeinZeichenBereich1 extends Canvas { Image bild; public void paint(Graphics g) { bild = getToolkit().getImage("images/Bild1.jpg"); g.drawImage(bild, 0, 0, this); } }
Das
eigentliche
Java-Programm
erzeugt
ein
Objekt
der
Klasse
MeinZeichenBereich1 und fügt es der Oberfläche hinzu. import java.awt.*; public class CanvasTest extends Frame { BorderLayout bl1 = new BorderLayout(); MeinZeichenBereich1 cv1 = new MeinZeichenBereich1(); public CanvasTest() { initial(); } public static void main(String[] args) { new CanvasTest(); } void initial() { this.setLayout(bl1); this.add(cv1, BorderLayout.CENTER); setSize(500,400); show(); }}
Java 2 Kompendium
Listing 10.21: Die Applikation implementiert den Zeichenbereich.
579
Kapitel 10
Das AWT
Abbildung 10.26: Der Zeichenbereich zeigt ein Bild.
Beachten Sie, dass das Programm keinen Mechanismus zum beenden besitzt. Sie können es auf Befehlszeilenebene mit CTRL+C beenden.
10.11
Menüs
Da jedes Fenster eine eigene Menüleiste besitzen kann, müssen wir uns natürlich damit beschäftigen. Immerhin zählen Menüs in grafischen Oberflächen zu den wichtigsten Interaktionselementen und kein halbwegs professionelles Programm kann darauf verzichten. Jede Menüleiste in Java kann mehrere Menüs enthalten und dort jedes Menü wieder beliebige Einträge (halt so, wie man Menüs kennt). Das AWT enthält dafür die Klassen MenuBar, Menu und MenuItem. Das Erzeugen von Menüleisten besteht aus zwei zentralen Schritten. Einmal muss eine Menüleiste erzeugt werden. Als zweiter Schritt erfolgt die Erzeugung der konkreten Einträge im Menü. Das Erzeugen einer Menüleiste erfolgt, indem Sie mit dem Konstruktor MenuBar() eine Instanz der Klasse MenuBar erstellen. Beispiel: MenuBar meineMenueleiste = new MenuBar(); Um dann eine Menüleiste als Menü eines Fensters zu definieren, verwenden Sie die public void setMenuBar(MenuBar mb)-Methode. Beispiel: meinFrame.setMenuBar(meineMenueleiste);
580
Java 2 Kompendium
Menüs
Kapitel 10
Bevor man eine Menüleiste sinnvoll verwenden kann, muss sie »mit Fleisch gefüllt« werden. Das soll heißen, es müssen Menüeinträge erzeugt und dann dem Menü hinzugefügt werden. Anschließend müssen noch die einzelnen Menüs mit Einträgen gefüllt werden. Aber beginnen wir mit dem ersten Schritt. Das
Erzeugen
eines
Menüeintrags
erfolgt
mit
dem
Konstruktor
Menu(String).
Beispiel: Menu meinMenupunkt = new Menu("Hilfe"); Um die Menüeinträge in einer Menüleiste hinzuzufügen, wird wieder die add()-Methode verwendet. Beispiel: meineMenueleiste.add(meinMenupunkt); Selbstverständlich können Menüeinträge zur Laufzeit aktivierbar (default) oder für den Anwender gesperrt gesetzt werden. Dazu gibt es die üblichen Methoden public void disable() und public void enable(). Diese sind zwar deprecated, funktioniern aber. Alternativ gibt es wieder setEnabled(). Bei Menüeinträgen benötigt man in vielen Fällen die Information darüber, um welche Art von Eintrag es sich handelt – die so genannte Menüoption. Java unterscheidet vier Optionsarten, die allesamt in ein Menü eingefügt werden können. 1.
Instanzen der Klasse MenuItem. Dies sind die gewöhnlichen (regulären) Menüeinträge.
2.
Menüs mit eigenen Untermenüs
3.
Trennlinien
4.
Instanzen der Klasse CheckboxMenuItem. Damit werden umschaltbare (aus/an) Menüeinträge erzeugt.
Reguläre Menüeinträge werden mit dem Konstruktur der MenuItem-Klasse erzeugt und mit der add()-Methode eingefügt. Beispiel: mMenpkt.add(new MenuItem("Über das Programm")); Abbildung 10.27: Reguläre Menüeinträge
Java 2 Kompendium
581
Kapitel 10
Das AWT Untermenüs werden hinzugefügt, indem eine neue Instanz von Menu erstellt und diese dann in das Obermenü eingefügt wird. Danach wird die Option in dieses Menü eingefügt. Beispiel: Menu untermenue = new Menu("Untermenü 1"); meinMenupunkt.add(untermenue); untermenue.add(new MenuItem("Untereintrag 1"));
Abbildung 10.28: Untermenüs
Trennlinien bilden ein wichtiges Gestaltungsmittel, um Optionsgruppen optisch voneinander zu trennen. Mehr Funktionalität besitzen sie nicht und daher ist der Einbau sehr einfach gehalten. Genau genommen ist er identisch mit der Vorgehensweise bei regulären Menüeinträgen. Sie verwenden nur das Argument – als Label. Dabei ist auffällig, dass sich der Trennstrich über die gesamte Breite des Menüs erstreckt, obwohl nur ein Zeichen – der Strich – als Argument verwendet wird (dies ist auch der deutliche Unterschied zu regulären Menüeinträgen). Beispiel: MenuItem meineTrennlinie = new MenuItem("-"); meinMenupunkt.add(meineTrennlinie); Abbildung 10.29: Trennlinien in einem Menü
Umschaltbare Menüeinträge werden mit der Klasse CheckboxMenuItem realisiert. Damit wird ein umschaltbarer Menüeintrag mit einem Kontrollzeichen erzeugt. Anklicken mit der Maus schaltet einen Eintrag an, erneutes Anklicken wieder aus. Auch bei CheckboxMenuItems ist die Vorgehensweise 582
Java 2 Kompendium
Menüs
Kapitel 10
analog wie bei regulären Menüeinträgen (es handelt sich um eine Subklasse von MenuItem). Es wird nur deren Konstruktor – CheckboxMenuItem(String) – verwendet. Beispiel: CheckboxMenuItem meinSchalter = new CheckboxMenuItem("Licht an/aus"); Anschließend wird der Eintrag wie üblich dem Menü mit der add()Methode zugefügt. Beispiel: meinMenupunkt.add(meinSchalter); Abbildung 10.30: Ein CheckboxMenuItem
Die Verwendung von umschaltbaren Menüeinträgen war in älteren JavaVersionen nicht unkritisch. Es konnte zu Fehlfunktionen und Ausnahmen kommen. In neuen Versionen – mit entsprechendem Eventhandling – gibt es keine Probleme. Ein Menü macht wie die meisten AWT-Komponenten nur dann Sinn, wenn damit eine Aktion ausgelöst werden kann. Diese Aktion kann in dem Eventmodell 1.0 wie fast jede andere Aktion mit der action()-Methode behandelt werden. Sowohl reguläre Menüoptionen als auch CheckboxMenuItem besitzen eine Beschriftung, die als Argument bei der action()-Methode ausgewertet werden kann. Beispiel: public boolean action(Event evt, Object arg) { if (evt.target instanceof MenuItem) { String label = (String)arg; if (label.equals("Über das Programm")) { // tue etwas sinnvolles } else { // tue etwas sinnvolles } }
Auch am Ende des Menüabschnitts sollen wir zwei vollständige Beispiele stehen. Nummer eins arbeitet nach dem Eventhandling 1.0 (es handelt sich um ein Applet, wo das ja oft notwendig ist). Es wird nicht sonderlich umfangreich, jedoch die wichtigsten Schritte zu Erstellung einer Menüstruktur werden durchgeführt. Beachten Sie, dass die Checkbox zum »Aus- und Anschalten des Lichtes« in einigen JVMs eine Ausnahme auswirft. Dies ist bei dem Beispiel bewusst einkalkuliert.
Java 2 Kompendium
583
Kapitel 10 Listing 10.22: Ein Applet mit einem Folgefenster, das ein Menü beinhaltet
584
Das AWT import java.awt.*; public class Menu1 extends java.applet.Applet { meinFenster mFrame; public void init() { // Füge zwei Schaltflächen dem Container hinzu add(new Button("Öffne Fenster")); add(new Button("Schließe Fenster")); // Erstelle Frame mFrame = new meinFenster("Sub-Fenster"); // Lege Framegröß e fest mFrame.resize(350,150); } // Die action-Methode der Klasse menu1 // Reaktionen auf Schaltflächen public boolean action(Event evt, Object arg) { String label = (String)arg; if (evt.target instanceof Button) { // Wenn Schaltfläche "Öffne Fenster" if (label.equals("Öffne Fenster")) { // Überprüfe, ob Frame noch nicht angezeigt if (!mFrame.isShowing()) mFrame.show(); // Zeige Frame } // Wenn Schaltfläche "Schließe Fenster" else if (label.equals("Schließe Fenster")) { // Überprüfe, ob Frame angezeigt if (mFrame.isShowing()) mFrame.hide(); // Verstecke Frame } return true; } else return false; }// Ende der action-Methode des Applets } // Ende der menu1-Klasse class meinFenster extends Frame { meinFenster(String title) { // Menüleiste erzeugen MenuBar mML = new MenuBar(); // Menüleiste am Rahmen verankern setMenuBar(mML); // Menüpunkte erzeugen Menu mMP1 = new Menu("Farben"); Menu mMP2 = new Menu("Menüpunkt 2"); Menu mMP3 = new Menu("Menüpunkt 3"); // Ein Untermenü erzeugen Menu uM = new Menu("Farbanpassung"); //Menüeinträge in der Menüleiste platzieren mML.add(mMP1); mML.add(mMP2);
Java 2 Kompendium
Menüs
Kapitel 10
mML.add(mMP3); // Einträge in Menüpunkt 1 mMP1.add(new MenuItem("Rot")); mMP1.add(new MenuItem("Blau")); mMP1.add(new MenuItem("Grün")); mMP1.add(new MenuItem("Gelb")); // Trennlinie MenuItem mTL = new MenuItem("-"); mMP1.add(mTL); // Untermenü dem ersten Menü hinzufügen mMP1.add(uM); uM.add(new MenuItem("Farbe")); uM.add(new MenuItem("Mono")); // Menüeintrag 2 in der Menüleiste deaktivieren mMP2.disable(); // Einträge in Menüpunkt 3 – CheckboxMenuItems /* Beachten Sie, dass die Checkbox zum "Aus- und Anschalten des Lichtes" in einigen JVMs eine Ausnahme auswirft. Dies ist bei dem Beispiel bewusst einkalkuliert.*/ CheckboxMenuItem meinSchalter2 = new CheckboxMenuItem("Ton an/aus"); mMP3.add(new CheckboxMenuItem("Licht an/aus")); mMP3.add(meinSchalter2); // 2. Eintrag im 3. Menüpunkt deaktivieren meinSchalter2.disable(); } // Die Reaktion auf Ereignisse in der Klasse // meinFenster public boolean action(Event evt, Object arg) { String label = (String)arg; // Reaktionen auf reguläre Menüeinträge if (evt.target instanceof MenuItem) { if (label.equals("Rot")) setBackground(Color.red); else if (label.equals("Blau")) setBackground(Color.blue); else if (label.equals("Grün")) setBackground(Color.green); else if (label.equals("Gelb")) setBackground(Color.yellow); // Reaktionen auf CheckboxMenuItem if (evt.target instanceof CheckboxMenuItem) { if (getBackground() != Color.black) setBackground(Color.black); else setBackground(Color.white); } repaint(); return true; } else return false; }// Ende der action-Methode des Frames }// Ende der fenster-Klasse
Abschließend soll ein Beispiel mit dem Eventhandling 1.1 demonstriert werden.
Java 2 Kompendium
585
Kapitel 10
Das AWT
Abbildung 10.31: Das Applet mit Folgefenster und Menü
Listing 10.23: Eine Applikation mit Menü und Eventhandling 1.1
586
import java.awt.*; import java.awt.event.*; public class FrameMenu extends Frame { Label lb1 = new Label(); MenuBar menuBar1 = new MenuBar(); Menu menu1 = new Menu(); Menu menu2 = new Menu(); Menu menu3 = new Menu(); MenuItem mI1 = new MenuItem(); MenuItem mI2 = new MenuItem(); MenuItem mI3 = new MenuItem(); MenuItem mI4 = new MenuItem(); MenuItem mI5 = new MenuItem(); MenuItem mI6 = new MenuItem(); MenuItem mI7 = new MenuItem(); MenuItem mI8 = new MenuItem(); public FrameMenu() { initial(); } public static void main(String[] args) { FrameMenu fM = new FrameMenu(); fM.setSize(300,300); fM.show(); } private void initial() { lb1.setAlignment(1); lb1.setFont( new java.awt.Font("Dialog", 0, 42)); lb1.setText("Alles wird gut");
Java 2 Kompendium
Menüs this.setMenuBar(menuBar1); menu1.setLabel("Farbe"); menu2.setLabel("Groesse"); menu3.setLabel("Text"); mI1.setLabel("Rot"); mI2.setLabel("Blau"); mI3.setLabel("Schwarz"); mI4.setLabel("12"); mI5.setLabel("24"); mI6.setLabel("42"); mI7.setLabel("Alles wird gut"); mI8.setLabel("Egal"); mI1.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent mI1_actionPerformed(e); } }); mI2.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent mI2_actionPerformed(e); } }); mI3.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent mI3_actionPerformed(e); } }); mI4.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent mI4_actionPerformed(e); } }); mI5.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent mI5_actionPerformed(e); } }); mI6.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent mI6_actionPerformed(e); } }); mI7.addActionListener( new java.awt.event.ActionListener() {
Java 2 Kompendium
Kapitel 10
e) {
e) {
e) {
e) {
e) {
e) {
587
Kapitel 10
Das AWT public void actionPerformed(ActionEvent e) { mI7_actionPerformed(e); } }); mI8.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { mI8_actionPerformed(e); } }); menuBar1.add(menu1); menuBar1.add(menu2); menuBar1.add(menu3); menu1.add(mI1); menu1.add(mI2); menu1.add(mI3); menu2.add(mI4); menu2.add(mI5); menu2.add(mI6); menu3.add(mI7); menu3.add(mI8); this.add(lb1, BorderLayout.CENTER); } void mI1_actionPerformed(ActionEvent e) { lb1.setForeground(new Color(255,0,0)); } void mI2_actionPerformed(ActionEvent e) { lb1.setForeground(new Color(0,0,255)); } void mI3_actionPerformed(ActionEvent e) { lb1.setForeground(new Color(0,0,0)); } void mI4_actionPerformed(ActionEvent e) { lb1.setFont(new java.awt.Font("Dialog", 0, 12)); } void mI5_actionPerformed(ActionEvent e) { lb1.setFont(new java.awt.Font("Dialog", 0, 24)); } void mI6_actionPerformed(ActionEvent e) { lb1.setFont(new java.awt.Font("Dialog", 0, 42)); } void mI7_actionPerformed(ActionEvent e) { lb1.setText("Alles wird gut"); } void mI8_actionPerformed(ActionEvent e) { lb1.setText("Egal wie schlimm es ist – es wird noch schlimmer"); } }
588
Java 2 Kompendium
Eine Schablone für alle Fälle
Kapitel 10 Abbildung 10.32: Das Original-Layout
Abbildung 10.33: Veränderung über das Menü
Das Beispiel verändert die Farbe des Labels, dessen Schriftgrö ß e und die Beschriftung.
10.12
Eine Schablone für alle Fälle
Wir wollen nun eine Schablone erstellen, die sowohl als Applet als auch als Standalone-Anwendung mit einer grafischen Oberfläche (also einer JavaFrame-Struktur) funktioniert. Das beinhaltet zwingend das Eventhandling 1.0. Da wir in der Schablone grundsätzlich nur Techniken des ersten JavaKonzepts verwenden, läuft diese Schablone sowohl in älteren Java-Umgebungen als auch unter neueren Java-Plattformen (hat aber zugegeben auch die Schwächen des alten Konzepts). Diese Schablone enthält die Grundfunktionalität für einen Aufruf als Applet als auch als eigenständige Applikation (die main()-Methode). Der Trick besteht darin, dass der nicht benötigte Anteil jeweils ignoriert wird. import java.applet.*; import java.awt.*; public class AlleineFenster extends Applet { // STANDALONE APPLICATION
Java 2 Kompendium
Listing 10.24: Ein Applet-Applikations-Zwitter
589
Kapitel 10
Das AWT // Die boolesche Variable StandAlone_ja wird auf // true gesetzt, wenn die Klasse als Standalone// Application gestartet wird //--------------------------private boolean StandAlone_ja= false; public static void main(String args[]) { ALFrame mFenst = new ALFrame("AlleineFenster"); mFenst.resize(300,300); AlleineFenster apAF = new AlleineFenster(); mFenst.add("Center", apAF); apAF.StandAlone_ja= true; apAF.init(); apAF.start(); mFenst.show(); } public AlleineFenster(){ } public void init(){ resize(300, 300); } public void destroy(){ } public void paint(Graphics g){ } public void start(){ } public void stop(){ } } // Neue Klasse class ALFrame extends Frame { public ALFrame(String str){ super (str); } public boolean handleEvent(Event evt){ switch (evt.id) { case Event.WINDOW_DESTROY: dispose(); System.exit(0); return true; default: return super.handleEvent(evt); } } }
10.13
Layoutmanager
Es wird Ihnen bei unseren bisherigen AWT-Experimenten aufgefallen sein, dass wir uns (scheinbar) überhaupt nicht um die genauen Positionen der AWT-Komponenten innerhalb der Container gekümmert haben. Sie waren einfach »irgendwo« auf der Oberfläche platziert worden. Vielleicht haben 590
Java 2 Kompendium
Layoutmanager
Kapitel 10
Sie auch einmal die Grö ß e eines Applets oder Fensters verändert. Dann sind plötzlich AWT-Komponenten an ganz anderen Stellen aufgetaucht als bei der Orginalgrö ß e oder ihre Grö ß e war verändert. Dieses Verhalten ist kein Zufall und schon gar nicht eine Schwäche von Java. Im Gegenteil – es ist eine der großen Stärken des Java-AWT-Konzepts. Auch wenn man sich erst einmal mit der Konzeption vertraut machen muss. Schauen wir uns als Basis zunächst andere Fensterkonzepte an. Dort geben Sie für die Komponenten der Benutzeroberfläche überwiegend exakte Koordinaten (meist in Pixel) an. Eine Koordinate für den Abstand vom oberen oder unteren Rand und eine Koordinate für den Abstand vom linken oder rechten Rand, also ein hardcodiertes Koordinaten-Tupel, wie wir es in Java bei Grafikoperationen auch tun. Ebenso wird die Grö ß e eines Elements exakt festgelegt. Ein Programmierer hat vollständige Kontrolle über das Aussehen der Benutzeroberfläche. Oder nicht? Doch, jedoch nur in genau der Bildschirmauflösung und der Grö ß e des Java-Fensters, die er beim Generieren der Anwendung oder des Applets verwendet. Was ist, wenn der Anwender nun eine andere Auflösung fährt? Eine feinere Auflösung ist vielleicht vom ästhetischen Standpunkt ein kleines Problem, gleichwohl noch kein Beinbruch. Eine geringere Auflösung hingegen kann dazu führen, dass einige Komponenten der Oberfläche gar nicht angezeigt werden und damit mit der Maus nicht auswählbar sind. Die Funktionalität kann massiv beeinträchtigt werden. Bei solchen konventionellen Fensterkonzepten muss ein Programmierer sämtliche denkbaren Auflösungen berücksichtigen (zumindest diejenigen, die er unterstützen möchte), diese zur Laufzeit abfragen und für jede Maske eine individuelle Oberfläche für jede Auflösung generieren. Ein erheblicher Aufwand und dennoch immer noch keine befriedigende Lösung. Denn was ist, wenn der Anwender die Fenstergrö ß e verändert (sofern kein Vollbild vorliegt)? Vergrö ß ern ist wieder nur ein ästhetisches Problem, aber Verkleinern kann zum Verdecken von Komponenten der Oberfläche führen. Nicht umsonst können Windows-Dialogfenster nicht in der Grö ß e verändert werden. Java löst diese Probleme auf eine äußerst intelligente Art und Weise. Das AWT-System kümmert sich weitgehend selbstständig um Grö ß enanpassung und Positionierung von den Komponenten auf der Oberfläche. Je nach Plattform und Bedingungen werden die Komponenten auf die Oberfläche optimal angepasst. Die Technik nennt sich Layoutmanager. Damit können Sie das AWT anweisen, wo Ihre Komponenten im Verhältnis zu den anderen Komponenten stehen sollen. Ein Layoutmanager findet
Java 2 Kompendium
591
Kapitel 10
Das AWT nach gewissen Regeln heraus, an welche Stelle die Komponenten am besten passen und ermittelt die beste Grö ß e der Komponenten.
10.13.1
Layout-Regeln
Das genaue Aussehen einer AWT-Oberfläche wird von drei Aspekten festgelegt. 1.
Der Plattform und den genauen Bedingungen dort.
2.
Der Reihenfolge, in der die AWT-Komponenten in einem Container eingefügt werden.
3.
Der Art des Layoutmanagers.
Über Punkt eins kann ein Programmierer keine Aussagen machen und muss es unter Java auch nicht. Plattformunabhängigkeit ist ja gerade ein Highlight. Der zweite Punkt ist naheliegend: Wer zuerst kommt, mahlt zuerst. Das soll heißen, dass zuerst eingefügte Komponenten auch vorher angeordnet werden. Richtig interessant ist die Art des Layoutmanagers. Das AWT beinhaltet fünf verschiedene Typen von Layoutmanagern, die die Oberfläche unterschiedlich anordnen: FlowLayout GridLayout BorderLayout CardLayout GridBagLayout Diese Layoutmanager können für jeden grafischen Container festgelegt werden. Insbesondere kann jedes Panel einen eigenen Layoutmanager verwenden. Dies ist besonders für verschachtelte Panels interessant. Um einen Layoutmanager für einen grafischen Container zu erstellen, können Sie die Methode public void setLayout(LayoutManager mgr) verwenden. Beispiel: public void init() { setLayout(new CardLayout()); }
592
Java 2 Kompendium
Layoutmanager
Kapitel 10
Alternativ geht es auch wieder so: public void init() { meinFlowLayout = new FlowLayout(); setLayout(meinFlowLayout); }
Das Setzen eines Layoutmanagers in einer Initialisierungsmethode ist nicht zwingend, jedoch meist angeraten, da dann die Anordnung der Komponenten von Anfang an auf den Layoutmanager ausgerichtet wird. Dies betrifft vor allem die Reihenfolge der Komponenten.
10.13.2
Die Layoutmanager im Detail
Schauen wir uns nun die Layoutmanager genauer an. Die FlowLayout-Klasse Die FlowLayout-Klasse stellt das einfachste Layout zur Verfügung. Eine FlowLayout-Klasse behandelt einen Container wie mehrere Spalten. Deren Grö ß e ergibt sich aus der Grö ß e der Komponenten. Die Komponenten werden in der Reihenfolge, in der sie in den Container eingefügt werden, spaltenweise einfach von links nach rechts anordnet, bis keine weiteren Komponenten mehr in eine Zeile passen. Dann geht es zur nächsten Zeile und analog weiter. Die Höhe der Zeilen wird von der Höhe der Elemente, die in der Reihe platziert sind, bestimmt. Schauen wir uns einmal ein kleines Beispiel als vollständigen Quelltext an. import java.awt.*; public class FlowLayoutTest extends java.applet.Applet { public void init() { setLayout(new FlowLayout()); Button meinButton1 = new Button("Rot"); add(meinButton1); Button meinButton2 = new Button("Blau"); add(meinButton2); Button meinButton3 = new Button("Grün"); add(meinButton3); Button meinButton4 = new Button("Pink"); add(meinButton4); Button meinButton5 = new Button("Rosa"); add(meinButton5); Button meinButton6 = new Button("Gelb"); add(meinButton6); Button meinButton7 = new Button("Cyan"); add(meinButton7); } }
Java 2 Kompendium
Listing 10.25: Ein Applet mit FlowLayout
593
Kapitel 10
Das AWT
Abbildung 10.34: Die Originalanordnung
Abbildung 10.35: Größ enveränderung des Applets macht den neuen, zeilenweisen Aufbau deutlich
Wenn wir die Reihenfolge, in der wir die Schaltflächen einfügen, im Quelltext umsortieren, wird auch das angezeigte Ergebnis abweichen. Jeder Layoutmanager beinhaltet auch eine Ausrichtung. Die FlowLayoutKlasse versucht je nach Vorgabe, die Zeilen entweder links, rechts oder zentriert auszurichten. Die Standard-Ausrichtung für ein FlowLayout ist zentriert. Wenn die Ausrichtung verändert werden soll, kann das entweder mit der Methode setAlignment(int ausrichtung) erfolgen (0 ist links, 1 zentriert und 2 rechts bzw. die nachfolgend besprochenen Konstanten), aber auch schon bei der Erstellung durch einen passenden Konstruktor festgelegt werden. Der FlowLayout-Layoutmanager ist der Standard-Layoutmanager für alle Applets. Der einfachste Konstruktor ist FlowLayout(). Er erstellt ein StandardFlowLayout (zentrierte Ausrichtung). Beispiel: meinFlowLayout = new FlowLayout(); setLayout(meinFlowLayout);
Für ein FlowLayout mit Ausrichtung verwenden Sie den Konstruktor FlowLayout(int). Dabei können Sie als Argument für die Ausrichtung die Konstanten FlowLayout.LEFT, FlowLayout.RIGHT und FlowLayout.CENTER verwenden. Beispiel: mFlowLt = new FlowLayout(FlowLayout.LEFT);
594
Java 2 Kompendium
Layoutmanager
Kapitel 10
Sie können mit FlowLayout Werte für den Zwischenraum in horizontaler und vertikaler Richtung angeben. Dafür gibt es den Konstruktor FlowLayout(int, int, int). Das erste Argument ist wieder die Ausrichtung, die Argumente zwei und drei sind die Werte für die Mindestgrö ß e des horizontalen und vertikalen Raums, der zwischen den Komponenten bestehen bleibt. Die Zwischenräume werden in Pixel angegeben. Diese Angaben sind oft sehr sinnvoll, da die Standardabstände zwischen den Komponenten nur 3 Pixel betragen. Beispiel: mFlowLt = new FlowLayout(FlowLayout.RIGHT, 15, 8);
Die Anweisung bedeutet: rechts ausgerichtet, horizontaler Zwischenraum 15 Pixel, vertikaler Zwischenraum von 8 Pixel. Die GridLayout-Klasse Bei diesem Layoutmanager wird ein Rasterlayout mit Zeilen und Spalten verwendet (eine Art virtuelle Tabellenstruktur). Ein Container wird wie ein Gitter mit gleich großen Teilen behandelt. Jede eingefügte Komponente wird in einer Zelle des Gitters platziert. Dabei ist wieder die Reihenfolge relevant, in der mit der add()-Methode eine Komponente hinzugefügt wurde. Die Anordnung beginnt immer oben links und geht dann wie bei FlowLayout von links nach rechts weiter. Ein wesentlicher Unterschied zwischen dem GridLayout und dem FlowLayout ist, dass GridLayout jeder Komponente den gleichen Raum zur Verfügung stellt. GridLayout ermöglicht in Verbindung mit verschachtelten Panels eine ziemlich genaue Kontrolle der Anordnung von Komponenten. Damit lassen sich Komponenten fast genauso genau auf der Oberfläche platzieren, wie bei der pixelgenauen Angabe, ohne jedoch deren Schwachpunkte zu übernehmen (die optimale Grö ß enanpassung der Komponenten ist immer vorhanden). Die GridLayout-Klasse besitzt natürlich einen leeren Default-Konstruktor. Nur sollten Sie beachten, dass ein GridLayout einem Gitter entspricht. Das bedeutet, eine Anzahl von Zeilen oder Spalten muss irgendwie festgelegt werden. Wenn Sie mit dem leeren Default-Konstruktor arbeiten, können Sie nach Erzeugung der Objekte diese Angaben machen (mit der Methode setColumns(int spalten) zum Festlegen der Spaltenanzahl oder setRows(int reihen) für die Anzahl der Zeilen). Oft ist es jedoch sinnvoll, diese Festlegung bereits beim Erstellen eines GridLayouts vorzunehmen. Das Format des dazu zu verwendenden GridLayout-Konstruktors ist GridLayout(int, int). Das erste Argument ist die Anzahl der Zeilen, das zweite Argument die Anzahl der Spalten.
Java 2 Kompendium
595
Kapitel 10
Das AWT Beispiel: setLayout(new GridLayout(3, 4));
Dabei verhält sich der Layoutmanager wie folgt: Die Angabe der Zeilenanzahl bewirkt die Berechnung der Anzahl der notwendigen Spalten, wenn Sie Komponenten einfügen. Sie sollten in diesem Fall 0 für die Spaltenzahl verwenden. Die Angabe der Spaltenanzahl bewirkt die Berechnung der Anzahl der notwendigen Zeilen, wenn Sie Komponenten einfügen. Sie sollten in diesem Fall 0 für die Anzahl der Zeilen verwenden. Wenn Sie sowohl für die Anzahl der Zeilen als auch der Spalten von Null verschiedene Werte angeben, werden nur die Angaben für die Zeilenzahl berücksichtigt. Die Anzahl der Spalten wird auf der Basis der Anzahl der Komponenten und der Zeilen berechnet – unabhängig davon, ob sie mit dem von Ihnen gesetzten Wert übereinstimmt oder nicht. GridLayout(5, 4) ist identisch mit GridLayout(5, 0) oder GridLayout(5, 2). Dies trifft auch zu, wenn sich auf Grund der Berechnung der Komponentenanzahl und der Anzahl der Zeilen eine Spaltenanzahl ergibt. Abbildung 10.36: GridLayout(3, 0) bei drei Komponenten
Für die Festlegung des horizontalen und vertikalen Zwischenraums zwischen den Komponenten steht ein weiterer Konstruktor zur Verfügung. Analog zum FlowLayout erweitern Sie die Argumentenliste um die Argumente für die Mindestgrö ß e des horizontalen und vertikalen Raumes, der zwischen den Komponenten bleibt. Der Konstruktor GridLayout(int, int, int, int) hat als erstes Argument die Anzahl der Zeilen, als zweites Argument die Anzahl der Spalten, Argument drei ist der horizontale und Argument vier der vertikale Mindestabstand. Die Zwischenräume werden in Pixel angegeben.
596
Java 2 Kompendium
Layoutmanager
Kapitel 10 Abbildung 10.37: GridLayout(3, 0) bei acht Komponenten – beachten Sie die leere Zelle rechts unten
Abbildung 10.38: GridLayout(0, 4) bei acht Komponenten
Abbildung 10.39: GridLayout(4, 4) bei acht Komponenten. Trotz Angabe von vier Spalten werden nur zwei verwendet.
Beispiel: setLayout(new GridLayout(3, 4, 10, 15));
Java 2 Kompendium
597
Kapitel 10
Das AWT Die GridBagLayout-Klasse und die GridBagConstraints-Klasse Behandeln wir nun die GridBagLayout-Klasse. Ein Container wird wieder in ein Gitter mit gleich großen Zellen unterteilt. Die Bedeutung ist nicht annähernd so groß wie die von FlowLayout oder BorderLayout. Die GridBagLayout-Klasse ähnelt der GridLayout-Klasse, ist aber viel flexibler (und damit leider etwas komplizierter). Der Unterschied zum GridLayout besteht zum einen darin, dass nicht Sie, sondern der Layoutmanager die Anzahl der Zeilen und Spalten bestimmt. Der andere wichtige Unterschied ist der, dass eine Komponente mehr als eine Zelle belegen kann. Damit wird bei geschickter Anwendung eine kleine Komponente nicht überflüssig viel Platz verschwenden, während ihre »große Schwester« zusammengestaucht wird. Kommen wir nun zu den Details. Der gesamte Bereich, den eine Komponente einnimmt (unter Umständen mehrere Zellen), wird Display Area genannt. Wir hatten schon festgehalten, dass nicht Sie, sondern der Layoutmanager die Anzahl der Zeilen und Spalten entscheidet. Sie können jedoch eine grobe Richtung angeben, wie die Komponenten vom Layoutmanager angeordnet werden sollen. Dazu verwenden Sie eine eigene Klasse – die GridBagConstraints-Klasse. Bevor Sie einem Container eine Komponente hinzufügen, setzen Sie über verschiedene Variablen der Klasse die grobe Anordnung der Komponenten. Diese Variablen müssen wir uns zu Gemüte führen: Es gibt Variablen, die auf den ersten Blick an absolute Koordinatenangaben erinnern – gridx und gridy. Es handelt sich aber dabei um die Koordinaten der Zelle, in der die nächste Komponente platziert werden soll (nicht die Komponente selbst, aber diese liegt damit natürlich indirekt ebenso fest). Die obere linke Ecke von GridBagLayout liegt bei 0, 0. Der Standardwert für gridx und gridy ist GridBagConstraints.RELATIVE. Dies bedeutet für gridx die erste rechte Zelle neben der letzten Komponente, die hinzugefügt worden ist, für gridy die Zelle, die unterhalb der letzten Komponente hinzugefügt worden ist. Sie können ebenso die Höhe und Breite von Komponenten indirekt festlegen. Über die Variablen gridwidth und gridheight legen Sie fest, wie viele Zellen hoch und breit eine Komponente sein sollte. Der Standardwert ist für beide Variablen 1. Sie können als besonderes Feature festlegen, dass eine Komponente die letzte in der aktuellen Zeile ist (damit ist die Breite natürlich festgelegt). Dazu dient der Wert GridBagConstraint.REMAINDER für gridwidth.
598
Java 2 Kompendium
Layoutmanager
Kapitel 10
Für gridheight bedeutet die Zuweisung dieses Wertes, dass diese Komponente die letzte in der Spalte sein sollte. Der Wert 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. Der Standardwert GridBagConstraint.NONE veranlasst, dass die Grö ß e der Komponente unverändert bleibt. GridBagConstraint.HORIZONTAL verbreitert die Komponente so, dass sie in der horizontalen Ebene den gesamten Anzeigebereich ausfüllt, in der Höhe aber unverändert bleibt. GridBagConstraint.VERTICAL macht das Entsprechende für die vertikale Ebene. GridBagConstraint.BOTH sorgt für eine Streckung der Komponenten in beide Richtungen so, dass sie den Anzeigebereich voll ausfüllt. Die Werte ipadx und ipady definieren, um wie viele Pixel die Grö ß e einer Komponente in x- und y-Richtung erweitert werden soll, um damit den Abstand zur nächstliegenden Komponente zu vergrö ß ern. Die Pixel werden auf jeder Seite der Komponente hinzugefügt, was bedeutet, dass die Grö ß e der Komponente immer um das Doppelte der angegebenen Pixel anwächst. Standard für ipadx und ipady ist jeweils 0. Bei Insets handelt es sich um eine Instanz der (als veraltet erklärten) Insets-Klasse. Insets-Objekte definieren die Ränder für alle Seiten einer Komponente. Es wird angezeigt, wie viel Platz zwischen den Grenzen einer Komponente und den Kanten des Anzeigebereichs gelassen werden kann. Das Feld anchor wird eingesetzt, wenn eine Komponente kleiner als ihr Anzeigebereich ist. Damit wird angegeben, wo die Komponente innerhalb des Anzeigebereichs platziert werden soll. Der Standardwert ist GridBagConstraint.CENTER (im Zentrum des Anzeigebereichs). Die Angaben GridbagConstraints.NORTH, GridBagConstraints.NORTHEAST, GridBagConstraints.EAST, GridBagConstraints.SOUTHEAST, GridBagConstraints- .SOUTH, GridBagConstraints.SOUTHWEST, GridBagConstraints.WEST, und GridBagConstraints.NORTHWEST sind wie bei der BorderLayout-Klasse zu verstehen und geben Richtungen wie bei
einem Kompass an. Die Werte weightx und weighty definieren eine Gewichtung für freien Raum innerhalb eines Containers und bestimmen die relative Grö ß e der Komponenten. Eine Komponente mit weightx 2.0 nimmt doppelt so viel horizontalen Platz wie weightx 1.0 ein. Die Erstellung eines GridBagLayout-Layouts erfolgt mit dem Konstruktor GridBagLayout().
Beispiel: GridBagLayout mGrdBgLt = new GridBagLayout(); Danach setzen Sie das Layout.
Java 2 Kompendium
599
Kapitel 10
Das AWT Beispiel: setLayout(meinGridBagLayout); Dann wird eine Instanz von GridBagConstraints erzeugt. Beispiel: GridBagConstraints Beding = new GridBagConstraints();
Danach werden die genauen Bedienungen der Komponente festgelegt. Beispiel: Beding.weightx = 2.0; Beding.gridwidth = GridBagConstraints.RELATIVE; Beding.fill = GridBagConstraints.HORIZONTAL;
Der nächste Schritt ist der Aufbau der Beschränkungen in dem GridBag-Layout. Beispiel: meinGridLayout.setConstraints(meinButton, Beding); Im letzten Schritt wird die Komponente in den Container eingefügt. Beispiel: add(meinButton); Die BorderLayout-Klasse Bei der BorderLayout-Klasse handelt es sich um ein Rahmenlayout. Es ist eines der wichtigsten Layouts in Java. Die Komponenten werden dem Container nach geografischen Aspekten zugeordnet (wie bei einem Kompass). Es gibt fünf Gebiete, wo eine Komponente platziert werden kann: Zentrum (Center) Nord (North) Süd (South) Ost (East) West (West) Wenn Sie unter dem BorderLayout dem Container Komponenten hinzufügen wollen, müssen Sie bei der add()-Methode einen dieser fünf Bereiche angeben. Dabei kann in jedem Bereich nur eine Komponente platziert werden. Das ist jedoch keine Einschränkung der Funktionalität. Man muss nur mit verschachtelten Panels arbeiten.
600
Java 2 Kompendium
Layoutmanager
Kapitel 10
Die fünf Bereiche werden wie auf einem Kompass angeordnet. Nord ist oben im Container, Ost rechts, Süd unten und West links im Container. Das Zentrum befindet sich in der Mitte. Die einfachste Form für ein BorderLayout erfolgt über den Konstruktor BorderLayout().
Beispiel: setLayout(new BorderLayout()); Optional können Sie wieder einen horizontalen und einen vertikalen Zwischenraum zwischen den Komponenten anzugeben. Dazu verwenden Sie wieder einen Konstruktor mit zwei Argumenten – BorderLayout(int, int) Beispiel: setLayout(new BorderLayout(15,10)); Um nun einem Bereich des BorderLayouts eine Komponente hinzuzufügen, müssen Sie eine spezielle add()-Methode verwenden. Ursprünglich wurde folgende Methode verwendet: Das erste Argument dieser speziellen add()Methode ist eine Zeichenkette, die die Position der Komponenten bezeichnet. Beispiel: add("North", new Button("Rot")); Die Anweisung fügt eine Schaltfläche im oberen Bereich hinzu. Alternativ gibt es eine neuere Variante, die aber den gleichen Effekt hat. Dabei wird zuerst die einzufügende Komponente angegeben und dann erst die Positionierung als int-Wert. Für diese Positionsangaben stellt die Klasse BorderLayout wieder sprechende Konstanten zur Verfügung. Beispiel: add(label1, BorderLayout.CENTER); Letztere add()-Methode kann auch für andere Layouts verwendet werden. In diesen Fällen wird einfach das Schlüsselwort null als Bereichsangabe gesetzt. Wenn eine Komponente mit der add()-Methode ohne eine Positionsangabe in einem BorderLayout hinzugefügt wird, wird sie nicht angezeigt. Schauen wir uns auch dieses Layout in einem vollständigen Beispiel an. import java.awt.*; public class BorderLayoutTest extends java.applet.Applet { public void init() { setLayout(new BorderLayout()); Button meinButton1 = new Button("Rot"); add("North", meinButton1); Button meinButton2 = new Button("Blau"); add("West", meinButton2); Button meinButton3 = new Button("Grün");
Java 2 Kompendium
Listing 10.26: Ein Applet mit BorderLayout
601
Kapitel 10
Das AWT add("South", meinButton3); } }
Abbildung 10.40: Die drei Komponenten sind angeordnet, der Rest bleibt leer.
Das nächste Beispiel verwendet nun fünf Komponenten: Listing 10.27: Ein Applet mit BorderLayout und fünf Komponenten
602
import java.awt.*; public class BorderLayoutTest2 extends java.applet.Applet { public void init() { setLayout(new BorderLayout(15,25)); Button meinButton1 = new Button("Rot"); add("North", meinButton1); Button meinButton2 = new Button("Blau"); add("West", meinButton2); Button meinButton3 = new Button("Grün"); add("South", meinButton3); Button meinButton4 = new Button("Gelb"); add("East", meinButton4); Button meinButton5 = new Button("Schwarz"); add("Center", meinButton5); } }
Java 2 Kompendium
Layoutmanager
Kapitel 10
Mit der BorderLayout-Klasse können Sie nur maximal eine Komponente in einem Bereich hinzufügen. Es gibt jedoch keinen Fehler. Wenn zwei Komponenten dem gleichen Bereich hinzugefügt werden, ist nur die zuletzt hinzugefügte zu sehen. Die CardLayout-Klasse Kartenlayouts unterscheiden sich signifikant von den bisher behandelten Layouts. Der wesentliche Unterschied besteht darin, dass von den eingefügten Komponenten immer nur eine Komponente sichtbar ist. Der CardLayoutLayoutmanager behandelt die dem Container hinzugefügten Komponenten wie einen Stapel von Karten. Jede Komponente wird auf einer eigenen Karte platziert, wobei wie bei einer Diashow immer maximal eine Karte angezeigt wird. Die Erstellung eines Kartenlayout erfolgt über den Konstruktor CardLayout(). Beispiel: setLayout(new CardLayout()); Sinn machen Kartenlayouts vor allem dann, wenn es sich bei den eingefügten Komponenten um Panels handelt. Damit kann pro »Karte« ein vollständiges, eigenes Layout definiert werden, das beim vor- und zurückblättern jeweils angezeigt wird. Da sich ein Panel (denken Sie an ein Fenster) aufteilen lässt, indem Panels ineinander geschachtelt werden, macht dieser Layoutmanager durchaus Sinn. Nach Erstellung des Kartenlayouts fügen Sie mit einer speziellen Version der add()-Methode die Karten hinzu. Dabei wird jede Karte benannt. Das erste Argument dieser speziellen add()-Methode ist eine Zeichenkette, die den Namen der Karte bezeichnet. Die Ähnlichkeit zum BorderLayout ist zwar da, aber die Angabe des Argumentes hat eine andere Bedeutung. Wenn eine Karte mit der normalen add()-Methode (ohne eine Namensangabe) hinzugefügt wird, können Sie diese nicht so einfach über die in der CardLayoutKlasse definierte show()-Methode direkt ansprechen. Beispiel: Panel karte1 = new Panel(); add("Rot", karte1); Panel karte2 = new Panel(); add("Gelb", karte2); Panel karte3 = new Panel(); add("Blau", karte3); show(this, "Gelb");
Auch bei diesem Layout gibt es wieder eine modernere Version der add()Methode, die das Gleiche bewirkt. Dabei wir als erstes Argument die einzufügende Komponente angegeben.
Java 2 Kompendium
603
Kapitel 10
Das AWT Beispiel: add(Button1, "Button1"); Zum Anzeigen der »Karten« stehen Ihnen im CardLayout folgende Methoden zur Verfügung:
Tabelle 10.4: Methoden der CardLayout-Klasse
Methode
Beschreibung
first()
Anzeige der ersten Karte
last()
Anzeige der letzten Karte
next()
Anzeige der nächsten Karte
previous()
Anzeige der vorherigen Karte
show()
Anzeige der Karte über Angabe des Namens
Schauen wir uns ein kleines Beispiel an, das bei jedem Klick auf einen Button diesen durch einen neuen austauscht. Listing 10.28: CardLayout
604
import java.awt.*; import java.awt.event.*; public class Card extends Frame { CardLayout cardLayout1 = new CardLayout(); Button b1 = new Button(); Button b2 = new Button(); Button b3 = new Button(); public Card() { initial(); } public static void main(String[] args) { Card frame1 = new Card(); frame1.setSize(100,100); frame1.show(); } private void initial() { b1.setLabel("button1"); b1.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { b1_actionPerformed(e);}}); setLayout(cardLayout1); b2.setLabel("button2"); b2.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { b2_actionPerformed(e);}}); b3.setLabel("button3"); b3.addActionListener(
Java 2 Kompendium
Die Eventmodelle 1.0 und 1.1
Kapitel 10
new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { b3_actionPerformed(e);}}); add(b1, "button1"); add(b2, "button2"); add(b3, "button3"); } void b1_actionPerformed(ActionEvent e) { cardLayout1.next(this); } void b2_actionPerformed(ActionEvent e) { cardLayout1.next(this); } void b3_actionPerformed(ActionEvent e) { cardLayout1.first(this); }} Abbildung 10.41: Erster Zustand
Abbildung 10.42: Der Button wurde ausgetauscht
10.14
Die Eventmodelle 1.0 und 1.1
Wir sind jetzt bereits an verschiedenen Stellen auf die Reaktionsmöglichkeiten unter Java eingegangen. Hier sollen noch einmal die wichtigsten Details zusammengefasst werden. Außerdem wird es Zeit, sich mit den beiden Eventmodellen von Java auseinander zu setzen und sie direkt gegenüberzustellen. Java und das JDK bieten seit geraumer Zeit zwei verschiedene Modelle, wie auf Ereignisse durch den Anwender (etwa das Bedienen einer AWT-Komponente, aber auch die Betätigung einer beliebigen Taste) zu reagieren ist. Da gibt es einmal das Eventhandling, wie es in Java und dem JDK vor der Version 1.1 realisiert war. Das nachfolgende Eventhandling 1.1 ist in allen Folgeversionen aktuell. Die Reaktion auf Ereignisse hat sich in der JDKVersion 1.1 gegenüber seinem Vorgänger total verändert und nicht mehr viel mit diesem gemeinsam. Allerdings besitzen beide Konzepte noch ihre Existenzberechtigung. Das neue Konzept ist zwar in fast jeder Hinsicht besser, schneller und leistungsfähiger, aber für die weitere Verwendung des
Java 2 Kompendium
605
Kapitel 10
Das AWT ersten Modells gibt es – wie wir bereits wissen – ein unschlagbares Argument. Viele Browser unterstützen bis jetzt das neuere Eventhandling noch nicht (zumindest, wenn die Applets mit dem allgemein üblichen <APPLET>Tag in eine Webseite eingebunden werden). Deshalb muss das ältere Eventhandling-Konzept für diejenige Applets immer noch verwendet werden, die die Kompatibilität zu diesen Browsern sicherstellen müssen. Dabei gibt es auch keine großen Probleme, denn um die Abwärtskompatibilität sicherzustellen, unterstützt das neue JDK beide Verfahren. Sun rät allerdings explizit, die beiden Modelle nicht in einem Programm zu mischen. Wir werden hier beide Konzepte durchsprechen und überdies im direkten Vergleich gegenüberstellen.
10.14.1
Der AWT-Event-Handler 1.0
Eventhandling über eine grafische Benutzeroberfläche muss das Problem lösen, dass alle Elemente, die als Interface zwischen Benutzer und Anwenderprogramm dienen, portabel für alle unterstützten Betriebssysteme bleiben. Die Programmierung dieser Elemente auf der Ebene der jeweiligen Betriebssysteme erfolgt je nach Betriebssystem in einer anderen Weise. In traditionellen prozeduralen Programmen, werden die Eingaben von Maus und Tastatur in einer Schleife abgefragt, die kontinuierlich durchlaufen werden muss. Das Eventhandling-System von Java arbeitet sowohl in der ersten als auch in der neuen Variante nach einen anderen Prinzip. Zur Bearbeitung von Ereignissen stehen Event-Methoden zur Verfügung. Im AWT des JDK 1.0 ist nun zur Gewährleistung der Portabilität zwischen Java und den jeweiligen Betriebssystemen eine Zwischenebene eingeführt worden, die so genannten Peers (die gesamte Zwischenebene wird Peer-Interface genannt). Hierbei handelt es sich um spezielle Objekte, die direkt zwischen den JavaObjekten und der entsprechenden Benutzeroberfläche kommunizieren können. Diese werden nur dann aufgerufen, wenn auch tatsächlich ein Ereignis auftritt. Die Event-Methoden bekommen von den Peer-Objekten eine EventInstanz übergeben, in der alle Daten zum Ereignis gespeichert sind, z.B. die Ursprungsinstanz, die Koordinaten, die Zeit beim Auftreten des Ereignisses, usw. Zu jedem Java-Oberflächenobjekt unter 1.0 gehört ein Peer-Objekt. Die Methoden der Peer-Objekte sind auf allen Betriebssystemen gleich, nur die Implementierung unterscheidet sich. Der Programmierer, der das AWT lediglich verwenden will, muss über die Peers eigentlich kaum etwas wissen, da die jeweiligen plattformspezifischen Elemente vollkommen transparent sind. Wird etwa die Maus auf ein Textfeld bewegt und die linke Maustaste gedrückt, erzeugt sein zugehöriges Peer-Objekt ein Event-Objekt. Dieses wird der postEvent()-Methode des Textfelds übergeben. Direkt im Anschluss wird die Methode handleEvent() ausgeführt. Soll das Textfeld auf den Mausklick reagieren, muss die Methode handleEvent() entsprechend überschrieben werden. Hat diese Methode das Ereignis abgearbeitet, sollte sie, soweit das Ereignis nicht an 606
Java 2 Kompendium
Die Eventmodelle 1.0 und 1.1
Kapitel 10
weitere, übergeordnete Objekte gesendet werden soll, den Rückgabewert true liefern. Für den Fall, dass dieses Objekt nicht auf dieses Ereignis reagieren konnte, sendet postEvent() es an die postEvent()-Methode des übergeordneten Objekts, bis das Event-Objekt als abgearbeitet (konsumiert) gekennzeichnet wurde oder alle beteiligten Objekte durchlaufen sind. Dies sollten wir uns bereits im Hinterkopf behalten, denn hier wird ein wesentlicher Unterschied des 1.1-Eventhandlingmodells ansetzen. Unter Umständen kann es vorkommen, dass Objekte auf viele verschiedene Ereignisse reagieren müssen. Damit die handleEvent()-Method nicht zu groß und unübersichtlich wird, ruft handleEvent() mehrere Hilfs-Eventmethoden (mouseEnter(), keyDown(), action(), ...) auf, die die jeweils zugeordneten Ereignisse verarbeiten. Allerdings verfügt das 1.0-Modell nur über ein einziges und allgemein einzusetzendes Event-Objekt, das sämtliche notwendigen Informationen zur Behandlung von Ereignissen enthält. Die handleEvent()-Methode bedeutet also unter dem 1.0-Modell die allgemeinste Art, wie das AWT auf irgendwelche Ereignisse eingeht, die auf der Benutzeroberfläche stattfinden. Die Ereignisse werden innerhalb der handleEvent()-Methode interpretiert und dann gezielt passende Methoden aufgerufen. Wenn die in der Event-Klasse definierten Standardereignisse eintreten, müssen Sie die handleEvent()-Methode überschreiben. Diese sieht schematisch so aus: public boolean handleEvent (Event evt) { // tue etwas sinnvolles }
Wie wir bereits gesehen haben, überprüfen Sie zum Test eines Ereignisses die ID-Instanzvariable des Event-Objekts, das an die handleEvent()-Methode übergeben wird. Die ID-Instanzvariable ist eigentlich eine Ganzzahl. Wir haben jedoch bereits davon Notiz genommen, dass für eine ganze Reihe von Ereignissen sprechende Klassenvariablen zur Verfügung stehen. Sie können jederzeit diese statt der Zahlenwerte testen. Zum Test eignen sich besonders gut switch-Anweisungen, als auch if-Konstrukte. Natürlich sind auch andere Konstruktionen denkbar.
10.14.2
Tastaturereignisse des AWT-Event-Handlers
Hier folgt nun noch eine kleine Liste mit Tastaturereignissen, die Sie in der handleEvent()-Methode unter dem Modell 1.0 abfragen können:
Java 2 Kompendium
607
Kapitel 10 Tabelle 10.5: Tastaturereignisse im Eventmodell 1.0
Das AWT
Wert von evt.id
Beschreibung
Event.KEY_PRESS
Eine beliebige Taste wird gedrückt (entspricht der keyDown()-Methode).
Event.KEY_RELEASE
Eine beliebige, bereits gedrückte Taste wird losgelassen.
Event.KEY_ACTION
Eine beliebige Taste wird gedrückt oder losgelassen, d.h., es findet eine beliebige Tastenaktion statt.
10.14.3
Mausereignisse des AWT-Event-Handlers
Natürlich kann der AWT-Event-Handler auch auf Mausereignisse reagieren: Tabelle 10.6: Mausereignisse im Eventmodell 1.0
Wert von evt.id
Beschreibung
Event.MOUSE_DOWN
Eine Maustaste wird gedrückt (entspricht der mouseDown()-Methode).
Event.MOUSE_UP
Eine Maustaste wird losgelassen (entspricht der mouseUp()-Methode).
Event.MOUSE_MOVE
Die Maus wird bewegt (entspricht der mouseMove()Methode).
Event.MOUSE_DRAG
Die Maus wird mit gedrückter Maustaste bewegt (entspricht der mouseDrag()-Methode).
Event.MOUSE_ENTER
Die Maus wird in den Bereich eines Applets oder einer seiner Komponenten bewegt (entspricht der mouseEnter()-Methode).
Event.MOUSE_EXIT
Die Maus verlässt den Bereich eines Applets (entspricht der mouseExit()-Methode).
Sowohl im Zusammenhang mit den Mausereignissen, als auch mit den Tastaturereignissen können Sie auf Betätigung einer Zusatztaste überprüfen. Dazu müssen Sie nur die Methoden shiftDown(), metaDown() oder controlDown() zusätzlich abfragen.
10.14.4
Zentrale Aspekte des 1.0-Eventhandlings
Fassen wir zusammen, was das Eventhandling unter dem 1.0-Modell auszeichnet. Wenn der Benutzer in einer GUI-Schnittstelle eine Eingabe vornimmt, wird vom System ein Objekt vom Typ Event erzeugt. Das Event-
608
Java 2 Kompendium
Die Eventmodelle 1.0 und 1.1
Kapitel 10
Objekt hat ein Feld id, in dem vermerkt ist, welche Art von Ereignis ausgelöst wurde. Abhängig von der Art des Ereignisses enthält das Event-Objekt noch weitere Informationen. Feld
Bedeutung
Event.id
Art des Ereignisses
Event.arg
Information, die je nach Ereignis von unterschiedlichem Typ ist
Event.clickCount
Anzahl der aufeinander folgenden Mausklicks
Event.key
Tastencode der gedrückten Taste
Event.modifiers
Status der Modifier-Tasten Ctrl, Shift und Meta
Event.target
Instanz der Komponente, in der das Ereignis ausgelöst wurde
Event.when
Zeitpunkt der Ereignisauslösung
Event.x
X-Koordinate des Ereignisses
Event.y
Y-Koordinate des Ereignisses
Tabelle 10.7: Eigenschaften eines Event-Objekts
Allgemein unterstützt das AWT 1.0 folgende Arten von Events: Action-Events: Betätigung eines Buttons, Auswahl aus einem Auswahlmenü oder Listenfeld, Wahl eines Menüpunktes, Eingabe in ein Textfeld Keyboard-Events: Eingabe über die Tastatur Maus-Events: Bewegen der Maus oder Drücken der Maustasten List-Events: Selektion oder Deselektion eines Listen-Eintrages, aber nur bei Einfachklick mit der Maus Scrolling-Events: Bewegen des Schiebers einer Scrollbar Window-Events: Schließen eines Fensters oder Verändern der Grö ß e. Weitere Events: Beispielsweise den Eingabefokus erhalten oder verlieren usw. Die nachfolgende Tabelle zeigt die Werte von Event.id und welche Klasse diese erzeugen kann.
Java 2 Kompendium
609
Kapitel 10 Tabelle 10.8: Die Werte von Event.id im Eventmodell 1.0
Das AWT
Wert von Event.id
Klasse
ACTION_EVENT
Button, Checkbox, Choice, List, MenuItem, TextField
GOT_FOCUS, LOST_FOCUS
Component
KEY_ACTION, KEY_ACTION_RELEASE, KEY_PRESS, KEY_RELEASE
Component
MOUSE_ENTER, MOUSE_EXIT, MOUSE_DOWN, MOUSE_UP, MOUSE_MOVE, MOUSE_DRAG
Component
LIST_SELECT, LIST_DESELECT
List
SCROLL_LINE_UP, SCROLL_LINE_DOWN, SCROLL_PAGE_UP, SCROLL_PAGE_DOWN, SCROLL_ABSOLUTE
Scrollbar
WINDOW_MOVED, WINDOW_DESTROY, WINDOW_ICONIFY, WINDOW_DEICONIFY
Frame, Dialog
Folgende Action-Events gibt es im Detail: Tabelle 10.9: Die Action-Events
Komponente
Event.arg
Aktion
Button
Label des Buttons
Drücken des Buttons mit Maus oder Leertaste
Checkbox
Status der Checkbox
Anwahl der Checkbox
Choice
Label des ausgewählten Eintrags
Auswahl des Eintrags
List
Label des ausgewählten Eintrags
Return-Taste oder Doppelklick auf Listenelement
MenuItem
Label des Menüpunktes
Auswahl des Menüpunktes Abschluss der Eingabe mit Return
TextField
Das sind die Methoden, die bei Focus-Events auftreten: Tabelle 10.10: Die Focus-Events
610
Methode
Ausgelöst
gotFocus()
Die Komponente erhält den Fokus.
lostFocus()
Die Komponente verliert den Fokus.
Java 2 Kompendium
Die Eventmodelle 1.0 und 1.1
Kapitel 10
Hier folgen die Maus-Events: Methode
Ausgelöst
mouseDown()
Die Maustaste wird gedrückt.
mouseUp()
Die Maustaste wird losgelassen.
mouseMove()
Die Maus wird bewegt.
mouseDrag()
Die Maus wird mit gedrückter Maustaste bewegt.
mouseEnter()
Der Mauszeiger tritt in den Bereich einer Komponente ein.
mouseExit()
Der Mauszeiger verlässt den Bereich einer Komponente.
Tabelle 10.11: Die Maus-Events
And last but not least die Tatatur-Events: Methode
Ausgelöst
keyDown()
Eine Taste wird gedrückt.
keyUp()
Eine Taste wird losgelassen.
Tabelle 10.12: Die Tastatus-Events
Als Parameter werden ein Event-Objekt und der Code der gedrückten Taste übergeben. Für die Sondertasten gibt es spezielle Konstanten. Konstante
Taste
Event.UP
Cursor oben
Event.DOWN
Cursor unten
Event.LEFT
Cursor links
Event.RIGHT
Cursor rechts
Event.HOME
Home-Taste
Event.END
Ende-Taste
Event.F1 bis Event.F12
Funktionstasten F1 bis F12
10.14.5
Tabelle 10.13: Event-Konstanten
Das Eventhandling 1.1
Das Eventmodell 1.1 hat mit dem Vorgängermodell nur noch wenig gemein. Schauen wir uns zuerst die Neuerungen an.
Java 2 Kompendium
611
Kapitel 10
Das AWT Die expliziten Neuerungen des Eventmodells Eine ganz wichtige Erweiterung des Eventmodells 1.1 ist beispielsweise, dass neben der Verwendung von Ereignissen bei grafischen Benutzereingaben diese auch für JavaBeans genutzt werden können. Die zweite auffällige Neuerung des neueren Eventmodells ist, dass es dort mehr als eine EreignisKlasse gibt. Unter dem alten Modell beruhte (wie wir gesehen haben) die gesamte Verarbeitung von Ereignissen auf der Klasse Event. Die Instanzvarible id der K1asse Event beinhaltet dabei die Art des Ereignisses und liefert alle notwendigen Informationen. Das Attribut target enthält die Information, welches Objekt das Ereignisses ausgelöst hat. In dem neuen Ereignis-Modell erben alle Ereignis-Klassen von der Klasse java.util.EventObject. AWT-Events erben von der Klasse java.awt.AWTEvent. Für die letztgenannte Klasse (AWTEvent) stehen wiederum einige Subklassen zur Verfügung, die entweder einfache (Low-Level-) oder höherwertige (semantische oder High-Level-)Ereignisse abbilden. Zu den Low-Level-Ereignissen zählt zum Beispiel die Klasse MouseEvent, die im neuen Modell für Mausaktivitäten zuständig ist. Zu den High-Level-Ereignissen zählen ActionEvent oder ItemEvent, die bei Statusänderungen von Elementen (etwa Selektion eines Menüeintrags oder einer Listbox) ausgelöst werden. Da wir im neuen Modell mehrere Ereignis-Klassen haben, müssen diese auch sinnvoll verwaltet werden. Die Ereignis-Klassen werden dazu in dem neuen Konzept in einer Baumhierarchie gruppiert. Die Einteilung erfolgt ausschließlich über die unterschiedlichen Ereignistypen. Der Typ des Objekts legt damit automatisch fest, welche Art von Ereignis vorliegt. Der Vorteil davon ist, dass eine Abfrage der ID-Nummer (wie bisher) nicht mehr notwendig ist. Die Ereignis-Klassen sind vollständig gekapselt und lassen damit keine unnötigen Informationen (etwa in From von Instanzvariablen) nach außen durch. Die Peer-Philosophie vom Modell 1.0 gibt es nicht mehr. Dies entspricht noch mehr dem objektorientierten Gesamtkonzept, als es in Java 1.0 realisiert war. Normalerweise werden sämtliche 1.1-Ereignisse unveränderliche Objekte sein (man nennt sie deshalb immutable). Wenn Ereignisse entstehen, müssen diese auch irgendwie ausgewertet werden. Es sind ja reine Mitteilungs-Objekte, die erst mal nur da sind und erst nach einer Behandlung durch einen entsprechend aufgebauten Mechanismus eine Reaktion des Programms bewirken können. Das Ereignis-Objekt selbst ist noch keine Reaktion des Programms. Es muss zu einem Auswertungs-Objekt transportiert werden. Sie kennen den Begriff jedoch schon, der den neuen Mechanismus des Ereignis-Transports beschreibt – Deligierte.
612
Java 2 Kompendium
Die Eventmodelle 1.0 und 1.1
Kapitel 10
Dieser funktioniert so: 1.
Ein Ereignis tritt bei einem Quellobjekt (Event Source) auf.
2.
Das entstandene Ereignis-Objekt wird an ein Zuhörer-Objekt (einen so genannten Event-Listener) weitergeleitet.
3.
Erst von dem Event-Listener wird über die konkrete Ereignis-Behandlung entschieden und die Reaktion auch ausgelöst.
In der Auflistung der Arbeitsschritte beim Deligationsmechanismus fällt auf, dass wir dort neben den bisher bekannten Objekten einen weiteren Objekttyp benötigen, ein Listener-Objekt, das dem Ereignis-Prozess eindeutig zugeordnet ist. Dieses Objekt ist neu im 1.1-Eventmodell und von ganz zentraler Bedeutung. Im Source könnte das Verfahren dann folgendermaßen aussehen: Button meinButton; meinButton = new Button("Ende"); ButtonListener meinButtonHandler; meinButtonHandler = new ButtonListener(this); meinButton.addActionListener(meinButtonHandler);
Dieser kurze Sourceteil beschreibt bereits die grundsätzliche Vorgehensweise, um Ereignisse in dem neuen Modell zu behandeln: 1.
Zuerst wird ein Objekt erzeugt, das ein Ereignis auswerfen kann (in unserem Fall ein Button).
2.
Danach erzeugt man einen dazu passenden Event-Listener (in unserem Fall ein ButtonListener, der auf ein Ereignis der Schaltfläche reagieren kann).
3.
Nach der Erzeugung wird das Listener-Objekt bei der Ereignisquelle registriert. Dazu gibt es immer eine passende Methode, die immer so aussieht: addListener
Dabei muss anstelle des in spitzen Klammern stehenden Typ_des_Events ein Listener einer bestimmten Art stehen (in unserem Fall sieht es dann so aus: addActionListener). Ein Ereignis-auslösendes Objekt kann mehrere Listener – sogar des gleichen Typs – besitzen. Die Methode addListener bewirkt eine sinnvolle Hintereinanderschaltung in einer geordneten Listenstruktur.
Java 2 Kompendium
613
Kapitel 10
Das AWT Für die Reihenfolge des Event-Empfangs soll nach Empfehlung von Sun keine explizite Ausnahmbehandlung erfolgen, da das AWT die Reihenfolge des Eintreffens von Ereignis-Objekten nicht garantieren kann. Wenn also ein Ereignis 1 und unmittelbar nachfolgend ein Ereignis 2 ausgelöst wird, kann es vorkommen, dass Ereignis 2 vor Ereignis 1 beim Listener ankommt. Wenn ein Ereignis eintritt, wird es vom Quellobjekt an diejenigen Listener des entsprechenden Typs weitergeleitet, die registriert sind. Diese Technik wird von Sun als Multicast bezeichnet. Das Objekt, bei dem das Ereignis eintritt, überträgt dieses wie in einer Fernsehsendung an alle Zuschauer, die das TV-Gerät angeschaltet haben (was in unserem Zusammenhang bedeutet, die Listener sind registriert). Die von dem Ereignis betroffenen Listener können dann entsprechend reagieren. Das Eventmodell 1.1 beinhaltet noch eine etwas veränderte Technik, mit der es möglich ist, einen einzelnen Listener gezielt anzusprechen (Sun nennt es Unicast). Sie ist jedoch nur für JavaBeans zu verwenden. Ein Event-Listener ist stets ein Objekt der grafischen Oberfläche oder eine eigene Klasse. Um auf die interessanten Ereignisse reagieren zu können, werden die entsprechenden Java-Schnittstellen implementiert. Das neue Java stellt dazu eine Hierarchie von abstrakten Event-Listener-Klassen zur Verfügung. In Analogie zu den bisherigen Ereignis-Klassen gibt es sowohl Low-Level-Schnittstellen für einfache Ereignisse als auch High-LevelSchnittstellen (semantisch) für komplexere Ereignisse. Es gibt für jede sinnvolle Reaktion auf einen spezifischen Ereignis-Typ in einer Event-ListenerSchnittstelle eine dazu passende Schnittstellendefinition. Schauen wir uns ein paar Beispiele an. Die Schnittstelle ContainerListener verwendet folgende Methoden: public abstract void componentAdded(ContainerEvent e) public abstract void componentRemoved(ContainerEvent e)
Das MouseListener-Interface unterstützt folgende Methoden: public abstract void mouseClicked(MouseEvent e)public abstract void mouseEntered(MouseEvent e) public abstract void mouseExited(MouseEvent e) public abstract void mousePressed(MouseEvent e) public abstract void mouseReleased(MouseEvent e)
Das TextListener-Interface unterstützt die Methode public abstract void textValueChanged(TextEvent e)
und das ActionListener-Interface 614
Java 2 Kompendium
Die Eventmodelle 1.0 und 1.1
Kapitel 10
public abstract void actionPerformed(ActionEvent e)
Neben den neuen Methoden und Klassen zur Ereignisbehandlung hat sich auch etwas in der zentralen Art der Ereignisbehandlung im neuen Eventmodell verändert. Dies betrifft die Technik der Weiterleitung eines Ereignisses an die nächsthöhere, umfassende Komponente. In der Version 1.0 musste zur Unterbindung einer Weiterleitung eines Ereignisses an die nächsthöhere, umfassende Komponente von der gerade aktuellen Komponente auf jeden Fall der Wert true zurückgegeben werden. Die aktuelle Komponente musste deshalb auf jeden Fall das Ereignis »ansehen« und verarbeiten. Das Ereignis entsteht in dem 1.0-Modell beim auslösenden Objekt und wird – falls dieses Objekt das Ereignis nicht konsumiert (was bedeutet, der Wert true wird nicht zurückgegeben) – sofort an den nächsthöheren, umfassenden GUIContainer weitergegeben. Es gibt aber diverse Situationen, wo dies nicht gewünscht wird. Das Ereignis soll nicht bei der aktuellen Komponente und auch nicht bei einer das Ereignis umfassenden Komponenten konsumiert werden, sondern das Ziel des Ereignisses ist ein vollkommen anderes Objekt. Es muss sozusagen quer weitergeleitet werden. Eine ganze Kette von GUI-Elementen bekommt unter dem 1.0-Eventmodell ein Ereignis zu sehen. Obwohl sie nicht davon betroffen sind, müssen sie bei der Weiterschleusung das Ereignis auswerten und verarbeiten, damit sie den Wert true zurückgeben können (sonst würde es auch bei einen reinem Mittlerobjekt nach oben an die umfassende Komponente weitergereicht und das wäre in der Regel die falsche Richtung). Hier haben wir einen Grund dafür, dass unter dem Eventmodell 1.0 in einigen Fällen ein beträchtlicher Performanceverlust bei der Reaktion auf Ereignisse entsteht. Ein weiteres Problem unter 1.0 betrifft die handleEvent()-Methode, die je nach Situation verschiedene boolesche Werte zurückgeben beziehungsweise bei Bedarf durch den Aufruf von super.handleEvent(event) die Ereignisbehandlung der Superklasse aufrufen muss. Im Modell 1.1 erreichen Eventobjekte – bis auf notwendige Ausnahmen – nur noch das Zielobjekt. Das unter Umständen zeitaufwändige und überflüssige Durchschleusen (mit der Auswertung des Ereignisses) durch die Reihe von GUI-Container (die nicht betroffen sind) entfällt. Die angedeuteten Ausnahmen sind Situationen, wo eine Auswertung des durchgeschleusten Ereignisses deshalb notwendig ist, um die weitere Ereignisverarbeitung abzubrechen. Falls etwa bestimmte Bedingungen eintreten, die eine Weiterleitung des Ereignisses an das Ziel nicht sinnvoll erscheinen lassen, muss ein Mittlerobjekt als Filter arbeiten. Sie können in jedem Objekt Eingabeereignisse mit der Methode void consume() verarbeiten und damit verbrauchen (Sie finden sie sowohl in der Klasse java.awt.AWTEvent als auch java.awt.event.InputEvent und java.awt.event.InputMethodEvent, wo sie überschrieben werden).
Java 2 Kompendium
615
Kapitel 10
Das AWT Diese Methode setzt einen Schalter im Ereignis-Objekt, den man durch die assoziierte Methode boolean isConsumed() abfragen kann. Das Ereignis selbst kann weiterhin an alle registrierten Listener weitergereicht werden. Diese müssen dann bei Bedarf das Flag kontrollieren und die Verarbeitung gegebenenfalls abschließen. Wenn man das Verhalten eines AWT-Objekts durch Bildung einer Subklasse erweitert, ergeben sich hinsichtlich der Ereignis-Verarbeitung grundsätzlich drei mögliche Verfahrensweisen: 1.
Integration der Event-Listener-Schnittstelle in die Subklasse über eine Befehlszeile der Art: implements <Event>ListenerInterface
2.
Implementation von separaten Event-Listener-Klassen. Dies haben wir durchgesprochen. Dieses Verfahren hat aber den Nachteil, dass es unter Umständen sehr aufwändig sein kann und die Zahl der notwendigen Klassen stark erhöht.
3.
Redefinition der Event-Dispatch-Methoden (processEvent oder process<Event-Klasse>), die jedes GUI-Objekt aus dem AWT standardmäß ig besitzt.
Die dritte Variante verwendet im Gegensatz zu Variante zwei kein extra Event-Listener-Interface, erkauft sich dieses Vorgehen jedoch über den Nachteil, dass es zu den gleichen Problemen wie im Eventhandling des JDK 1.0 kommen kann. Problematisch sind beispielsweise die unübersichtlichen if-else-Konstrukte bei komplexen Applikationen. Diesen Missstand versucht das neue Framework durch eine klare Trennung der Konzepte zu beheben. Das gut gegliederte, objektorientierte Design vermeidet damit solche schwer wartbaren und fehleranfälligen Konstruktionen. Durch das Zwischenschalten der Listener erstellt man nur noch dann Subklassen von Standard-GUI-Objekten, wenn sich diese tatsächlich durch ein eigenes Verhalten auszeichnen. Ansonsten versucht man, das Erzeugen von Subklassen zur Ereignisbehandlung so weit wie möglich zu unterlassen. Das Verhalten passt damit noch besser als vorher in die objektorientierte Philosophie, wo man ja ebenfalls Verhaltensweisen soweit oben wie möglich im Vererbungsbaum ansiedeln möchte. Der Preis für diese Einfachheit ist der Aufbau von zusätzlichen Listener-Klassen. Schauen wir uns nun ein ganz einfaches Beispiel mit dem Eventhandling 1.1 unter dem Aspekt der Ereignisbehandlung an, das die gerade beschriebene Vorgehensweise demonstriert (und sicher einige der bisherigen Beispiele in dem Kapitel verständlicher macht). Eine eigenständige Java-Applikation beinhaltet einen Button. Dazu wird dann ein Action-Listener mit addActionListener(new Event1_button1_actionAdapter(this)) registriert. Die im Parameter verwendete Klasse Event1_button1_actionAdapter implemen-
616
Java 2 Kompendium
Die Eventmodelle 1.0 und 1.1
Kapitel 10
tiert java.awt.event.ActionListener. Die entsprechend festgelegte Methode button1_actionPerformed(ActionEvent e) wird bei einem Klick auf den Button ausgelöst und schließt das Programm. Wir verwenden hier also einen Standard-Adapter für das Eventhandling. import java.awt.*; import java.awt.event.*; public class Event1 extends Frame { Button button1 = new Button(); FlowLayout flowLayout1 = new FlowLayout(); public Event1() { initial(); } public static void main(String[] args) { Event1 mF = new Event1(); mF.setSize(200,100); mF.show(); } private void initial() { button1.setLabel("button1"); button1.addActionListener(new Event1_button1_actionAdapter(this)); this.setLayout(flowLayout1); this.add(button1, null); } void button1_actionPerformed(ActionEvent e) { System.exit(0); }} class Event1_button1_actionAdapter implements java.awt.event.ActionListener { Event1 adapt; Event1_button1_actionAdapter(Event1 adapt) { this.adapt = adapt; } public void actionPerformed(ActionEvent e) { adapt.button1_actionPerformed(e); }}
Listing 10.29: Ein einfaches Beispiel mit Eventhandling 1.1 und Standard-Adapter
Abschließend sei der Hinweis nachgetragen, dass sich beim Eventmodell 1.1 die Verwendung von anonymen Klassen ideal anbietet. Die Umarbeitung des gerade demonstrierten Beispiels zeigt, dass der Quelltext kompakter und besser lesbar wird. Insbesondere entfällt die Implementation der Event-Listener-Schnittstelle. Im Kapitel über Swing werden wir einige weitere Beispiele mit einem Standard-Adapter durchspielen. import java.awt.*; import java.awt.event.*; public class Event2 extends Frame { Button button1 = new Button(); FlowLayout flowLayout1 = new FlowLayout();
Java 2 Kompendium
Listing 10.30: Das gleiche Beispiel mit Eventhandling 1.1 und anonymem Adapter
617
Kapitel 10
Das AWT public Event2() { initial(); } public static void main(String[] args) { Event2 mF = new Event2(); mF.setSize(200,100); mF.show(); } private void initial() { button1.setLabel("button1"); button1.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { button1_actionPerformed(e);}}); this.setLayout(flowLayout1); this.add(button1, null); } void button1_actionPerformed(ActionEvent e) { System.exit(0); }}
10.14.6
Der Umstieg vom 1.0-Eventmodell auf das 1.1-Modell
Obwohl seit der Einführung des Eventmodells 1.1 viel Wasser den Bach heruntergeflossen ist, gibt es – aus mehrfach erwähnten Gründen – noch zahlreiche Projekte, die auf dem alten Eventhandling beruhen. Falls diese auf das 1.1-Modell umgestellt werden sollen, steht eine Menge Arbeit bevor. Zur Erleichterung des Umstiegs vom 1.0-Eventmodell auf das 1.1Modell folgt in diesem Abschnitt eine Event-Konvertierungstabelle. Dabei werden die 1.0-Events ihren 1.1-Partnern gegenübergestellt. Die erste Spalte listet jeden 1.0-Eventtyp mit dem Namen der Methode (falls vorhanden) auf, die dem Ereignis zugeordnet ist. Wo keine Methode angegeben ist, wird das Ereignis immer in der handleEvent()-Methode behandelt. Die zweite Spalte listet die 1.0-Komponenten auf, die den angegebenen Ereignistyp generieren können. Die dritte Spalte gibt das Listener-Interface zum Auffinden des 1.1-Äquivalents der aufgelisteten Ereignisse an. Die vierte Spalte gibt die Methoden in jedem Listener-Interface an. Tabelle 10.14: Gegenüberstellung der 1.0- und 1.1Modelle für den Umstieg
618
Modell 1.0
Modell 1.1
Event/Methode
Generiert von
Listener-Interface Methoden
ACTION_EVENT/ action
Button, List, MenuItem, TextField
ActionListener
actionPerformed (ActionEvent)
Java 2 Kompendium
Die Eventmodelle 1.0 und 1.1
Modell 1.0
WINDOW_DESTROY WINDOW_EXPOSE WINDOW_ICONIFY WINDOW_DEICONIFY
Kapitel 10
Modell 1.1 Checkbox, CheckboxMenuItem, Choice
ItemListener
itemStateChanged (ItemEvent)
Dialog, Frame
WindowListener
windowClosing (WindowEvent) windowOpened (WindowEvent) windowIconified (WindowEvent) windowDeiconified (WindowEvent) windowClosed (WindowEvent) (kein
Äquvivalent in der Version 1.0) windowActivated (WindowEvent) (kein
Äquvivalent in der Version 1.0) windowDeactivated(WindowEvent) (kein
Äquvivalent in der Version 1.0) WINDOW_MOVED
Dialog, Frame
ComponentListener componentMoved (ComponentEvent) componentHidden (ComponentEvent) (kein
Äquvivalent in der Version 1.0) componentResized (ComponentEvent) (kein
Äquvivalent in der Version 1.0) componentShown (ComponentEvent) (kein
Äquvivalent in der Version 1.0) SCROLL_LINE_UP SCROLL_LINE_DOWN SCROLL_PAGE_UP SCROLL_PAGE_DOWN SCROLL_ABSOLUTE SCROLL_BEGIN SCROLL_END
Java 2 Kompendium
Scrollbar
AdjustmentListener adjustmentValueChanged
(alternativ kann (AdjustmentEvent) die neue ScrollPaneKlasse verwendet werden)
619
Kapitel 10
Das AWT
Modell 1.0
Modell 1.1
LIST_SELECT LIST_DESELECT
Checkbox, CheckboxMenuItem, Choice, List
ItemListener
itemStateChanged (ItemEvent)
MOUSE_DRAG/ mouseDrag MOUSE_MOVE/ mouseMove
Canvas, Dialog, Frame, Panel, Window
MouseMotionListener
mouseDragged (MouseEvent) mouseMoved(MouseEvent)
MOUSE_DOWN/ mouseDown MOUSE_UP/mouseUp MOUSE_ENTER/ mouseEnter MOUSE_EXIT/ mouseExit
Canvas, Dialog, Frame, Panel, Window
MouseListener
mousePressed (MouseEvent) mouseReleased (MouseEvent) mouseEntered (MouseEvent) mouseExited (MouseEvent) mouseClicked (MouseEvent) (kein
Äquvivalent in der Version 1.0)
620
KEY_PRESS/ keyDown KEY_RELEASE/ keyUp KEY_ACTION/ keyDown KEY_ACTION_RELEA SE/keyUp
Component
GOT_FOCUS/ gotFocus LOST_FOCUS/ lostFocus
Component
KeyListener
keyPressed(KeyEvent) keyReleased(KeyEvent) keyTyped(KeyEvent)
(kein Äquvivalent in der Version 1.0)
FocusListener
focusGained (FocusEvent) focusLost(FocusEvent)
Kein Äquvivalent in der Version 1.0.
ContainerListener
componentAdded (ContainerEvent) componentRemoved (ContainerEvent)
Kein Äquvivalent in der Version 1.0.
TextListener
textValueChanged (TextEvent)
Java 2 Kompendium
Zusammenfassung
10.15
Kapitel 10
Zusammenfassung
Die Kommunikation mit dem Anwender wird in Java hauptsächlich über das AWT (bzw. das im nächsten Kapitel beschriebene, darauf aufbauende Swing-Konzept) realisiert. Das AWT beinhaltet zur Kommunikation mit dem Anwender im Wesentlichen ein API, über die allgemeine Komponenten der Anwenderschnittstelle wie Buttons oder Menüs plattformunabhängig genutzt werden können. Die von Anfang an vorhandenen Komponenten des AWT sind folgende: Schaltflächen (Buttons) Label Kontrollkästchen (Checkbuttons) Optionsfelder (Radiobuttons) Listen Auswahlfelder Textfelder Textbereiche Menüs Zeichenbereiche Bildlaufleisten Komponenten werden in Java in Containern organisiert, wo sie gemeinsam verwaltet werden. Die Komponenten werden mittels der Container in verwaltbaren Gruppen organisiert. In Java können nur dann die Komponenten im AWT verwendet werden, wenn diese dann auch in einem Container enthalten sind. Die Container im AWT sind: Fenster (Windows) Panels Frames Dialoge Die Applet-Klasse ist eine Unterklasse der Panel-Klasse. Insbesondere stellt bei Applets der Browser eine Umgebung für das AWT (und Grafik) bereit. Ein bedeutender Bestandteil des AWT ist die Technik der Layoutmanager, die das Layout der Oberfläche regeln. Unter Java werden Sie im Allgemeinen nicht mehr genau angeben, wo eine hinzugefügte Komponente in einem Container platziert werden soll. Die genaue Platzierung regeln die Layout-
Java 2 Kompendium
621
Kapitel 10
Das AWT manager. Sie weisen das AWT darüber nur noch an, wo Ihre Komponenten im Verhältnis zu den anderen Komponenten stehen sollen. Der Layoutmanager findet – angepasst an die jeweilige Situation – automatisch heraus, an welche Stelle die Komponenten am besten passen. Das AWT stellt fünf verschiedene Typen von Layoutmanagern zur Verfügung: FlowLayout BorderLayout GridLayout CardLayout GridBagLayout Das Eventhandling muss in Java zweigleisig betrachtet werden. Java und das JDK bieten zwei verschiedene Modelle, wie auf Ereignisse zu reagieren ist. Das Eventhandling hat sich im Wechsel von der Version 1.0 auf die JDK-Version 1.1 total verändert. Allerdings besitzen beide Ereignisbehandlungskonzepte noch ihre Existenzberechtigung. Sun rät explizit, die beiden Modelle nicht in einem Programm zu mischen. Zusammenfassend kann man sagen, dass das neue Eventmodell den wesentlichen Vorteil bietet, dass die gleiche Architektur sowohl in der GUI-Programmierung als auch im Rahmen des neuen Komponentenmodells, JavaBeans, anwendbar ist. Die Entwickler müssen nur ein Framework lernen, das sich allerdings von der Logik des vorher praktizierten Modells unterscheidet. Außerdem wird die gesamte Programmstruktur einfacher, übersichtlicher, stabiler und in einigen Fällen schneller. Wo aber viel Licht ist, ist auch Schatten. Durch die neue Logik in dem Eventmodell werden die unter 1.0 entwickelten Projekte nur mit erheblichem Aufwand auf das veränderte Eventmodell zu portieren sein. Zwar verspricht Sun, dass dies einfach und ohne Probleme zu bewerkstelligen ist, aber für komplexe Anwendungen wird es – entgegen der Marktingbeteuerungen – ein kaum zu realisierender Aufwand sein. Nicht umsonst wird das alte Eventhandling auch unter Java 1.1 und 1.2 weiter unterstützt (wenn auch bei der Kompilierung die Meldungen auftauchen, man würde deprecated-Elemente verwenden). Und man sollte ebenfalls beachten, dass gerade bei Applets das neue Eventmodell erhebliche Probleme bereiten kann.
622
Java 2 Kompendium
11
Swing & Co
Nachdem wir im vorherigen Kapitel das AWT und grundsätzlich das Eventhandling unter Java diskutiert haben, wollen wir in diesem Kapitel Swing und die neueren Oberflächentechniken von Java allgemein (JFC) ansprechen. Swing ist ein praktisches Beispiel eines Konzepts, das massiv auf dem Eventmodell 1.1 aufbaut und eine wesentliche Erweiterung des AWTModells darstellt. Wohlbemerkt, eine Erweiterung. Die bisherigen AWTTechniken lassen sich in Java immer anwenden und im Prinzip auch mit Swing-Elementen mischen, was aber nicht sonderlich sinnvoll ist. Swing implementiert einen neuen Satz von GUI-Komponenten mit anpassungsfähigem Look and Feel. Swing ist vollständig in 100 % purem Java implementiert und basiert auf dem JDK 1.1 Lightweight UI Framework. Lightweight bedeutet, dass keine Peer-Klassen mehr benötigt werden. Das Aussehen und die Reaktionen von GUI-Komponenten passen sich auf Wunsch automatisch an jede unterstützte Betriebssystemplattform (Windows, Solaris, Macintosh) an. Sie schwingen – daher der Name Swing – sozusagen zwischen den verschiedenen Welten hin und her (wenn Sie es wollen). Um dies zu realisieren, gibt es in Swing den UIManager mit Methoden wie setLookAndFeel() oder getSystemLookAndFeelClassName(). Swing-Komponenten beinhalten vollständig die bisherigen AWT-Komponenten (Button, Scrollbar, Label usw.), plus einen Satz von Higher-LevelKomponenten (Baumansichten, Listboxen usw.). Das Kapitel baut auf dem AWT-Kapitel auf und verzichtet deshalb auf einige Erklärungen, die dort bereits erfolgt sind.
11.1
Das Swing-API
Java 1.1 hat Swing das erste Mal eingeführt, aber Swing ist nicht gleich Swing. Die Swing-API-Referenz hat sich über die Entwicklung des Konzepts bis zu der Finalversion 1.2 des JDK erheblich verändert (danach ist es nur noch erweitert worden). Das gesamte API ist beim Wechsel von Java 1.1 zu Java 2 umstrukturiert und verlagert worden. Leider ist diese Umstrukturierung ziemlich chaotisch und schlecht dokumentiert erfolgt. Selbst die Beta3-Version des JDK 1.2 hatte Swing noch so organisiert, wie es unter dem JDK 1.1 üblich war. Erst die kaum beachtete – letzte – Betaversion des Java 2 Kompendium
623
Kapitel 11
Swing & Co JDK 1.2 veränderte dann die Struktur, was in der Entwicklerschaft zu ziemlichem Ungemach führte. Ab Java 2 befindet sich die gesamte Swing-Funktionalität in dem Hauptpaket javax. Swing besteht aus folgenden Paketen: javax.swing javax.swing.border javax.swing.colorchooser javax.swing.event javax.swing.filechooser javax.swing.plaf javax.swing.plaf.basic javax.swing.plaf.metal javax.swing.plaf.multi javax.swing.table javax.swing.text javax.swing.text.html javax.swing.text.html.parser javax.swing.text.rtf javax.swing.tree javax.swing.undo
Eine umfangreichere Dokumentation des Swing-API finden Sie im Anhang.
11.2
Swing und AWT im Vergleich
Um es noch einmal zu betonen: Swing ist eine Erweiterung des AWTs. Das bedeutet, viele bisherige Klassen, Methoden und Techniken sind unter Swing verbessert und erweitert worden. Dabei wurde bei Methoden vielfach der Name beibehalten (wenn die Methode in einer neuen Klasse verwendet wird) und/oder nur eine neue Methodenunterschrift eingeführt (Stichwort Polymorphismus). Bei einigen Klassen wurde nur ein neuer Konstruktor eingeführt. Im Allgemeinen wurden neue Klassen eingeführt, die bisherige Techniken unter dem gleichen Namen bereitstellen und/oder Techniken einführen, deren Bezeichner aus dem Vorgänger herleitbar ist. So basieren viele Swing-Klassen auf den gleichen Superklassen wie AWT-Klassen. Beispielsweise ist die Swing-Klasse JToolBar ein direkter Nachkomme von Container. Viele Swing-Klassen sind auch direkt aus alten AWT-Klassen abgeleitet. Diese direkte oder indirekte Verwandtschaft dokumentiert sich oft im Namen. Viele Klassen bekommen bei verwandter Funktionalität einfach ein J vorangestellt. Aus Button unter dem AWT wird JButton, aus TextField JTextField, aus Lable JLable usw. Sie werden sich vielleicht wundern, dass das Swing-Kapitel im Verhältnis zur Behandlung des konventionellen AWTs kürzer ist. Und das, obwohl das Swing-API erheblich umfangreicher als das AWT-API ist. Um es etwas drastisch zu formulieren – wenn man alle Details zu Swing behandeln wollte,
624
Java 2 Kompendium
Swing und AWT im Vergleich
Kapitel 11
könnte das gesamte Buch damit gefüllt werden. Das ist aber weder sinnvoll noch notwendig. Viele Details würden sich wiederholen, sodass wir uns hier auf eine Auswahl von wesentlichen Elementen beschränken. Entscheidend ist dabei auch, dass man bei Swing sämtliche Erkenntnisse aus den bisherigen Abhandlungen über das AWT (GUI-Gestaltung verbunden mit dem neuen Eventhandling) schlicht zusammenbringen muss und einfach übertragen kann. Wenn man konkret unter dem Swingkonzept programmieren möchte, ordnet man seine UI-Elemente wie gehabt auf einem Panel (unter Swing aus der Klasse JPanel erstellt) oder in einem auf Swing basierenden Applet an (Klasse JApplet als Superklasse). Das Swing-JPanel entspricht weitgehend dem bekannten Panel, benutzt aber wie alle Swing-Elemente keine Peer-Klasse. Außerdem werden quasi von Hause aus diverse Techniken unterstützt, die man unter dem AWT bzw. konventionellem Java »von Hand« selbst programmieren muss. Dazu zählt z.B. die Technik des Double Buffering, um das Flackern des Bildschirms zu verringern (Defaulteinstellung, die mit der Methode setBuffered(boolean)verändert werden kann). Die meisten Swing-Klassen – etwa JMenuBar, JMenu oder JmenuItem – sind mit den Menüklassen des konventionellen AWTs eng verwandt. Die Klasse JFrame besitzt beispielsweise als direkter Nachkomme von java.awt.Frame auch sämtliche der dort besprochenen Methoden und Eigenschaften, aber natürlich auch zahlreiche Erweiterungen. Zwar lassen sich durchaus Details finden, wo Vorgänge geringfügig verändert wurden. Prinzipiell gilt aber das, was auch in der Fahrschule gilt: Wenn man mit einem bestimmten Auto den Führerschein gemacht hat, kann man mit etwas Umgewöhnung jeden anderen Wagen fahren. Wir sprechen einmal eine exemplarische Erstellung einer Swing-Oberfläche durch. 1.
Als ersten Schritt sollten Sie, wenn Sie nicht im Quelltext jedes Mal voll qualifizierte Angaben machen wollen, neben den bisher üblichen Paketen noch javax.swing.* und die sonst aus dem Swing-Konzept verwendeten Pakete am Anfang importieren. Vergessen Sie dabei nicht die Klasse java.awt.event.*, wenn Sie die darauf basierenden Reaktionen behandeln wollen.
2.
Danach verwenden Sie einfach Swing-Komponenten statt der bisherigen Komponenten, um die Oberfläche zu gestalten, also etwa JPanel, JMenuBar, JMenu, JMenuItem, JButton usw.
3.
Die Verwendung des new-Operators erfolgt wie gehabt. Die Anordung auf dem JPanel funktioniert ähnlich wie bisher. Sie ist nur in einigen Details erweitert worden, die aber überwiegend auch im AWT schon möglich sind (dort gibt es aber noch viele Techniken, die explizit unter Swing so nicht mehr funktionieren). Swing stellt einige neuen Methoden und Layoutmanager bereit, die die Anordnung und das Aussehen erweitern. Dort steckt eine wesentliche Erweiterung der Gestaltungsmöglichkeiten. So erfolgt die Platzierung von Komponenten auf einem
Java 2 Kompendium
625
Kapitel 11
Swing & Co Container immer noch über die add()-Methode (wobei die ganz alte Variante mit nur einem Parameter unter Swing keine Verwendung mehr findet). Sie sollten allerdings bei sämtlichen Aktionen, die das Layout einer Oberfläche berühren, die dem Panel zugrundeliegende Gestaltung abfragen. Mit der Methode public Container getContentPane() bekommen Sie als Rückgabewert Informationen über das zugrunde liegende Layoutkonzept. Sie können, da ein Objekt vom Typ JRootPane zurückgegeben wird, direkt die add()-Methode auf die Methode per Punktnotation anwenden: getContentPane().add(). In der Dokumentation des jeweiligen Containers finden Sie die Details, auf die Sie achten müssen. 4.
Die Erstellung von Funktionalitäten in Form von eigenen Klassen und Methoden ist im Grundsatz identisch (außer, dass Sie mehr Möglichkeiten als bisher haben).
5.
Das Eventhandling muss unter Swing immer auf dem 1.1-Modell aufgebaut werden. Dazu gilt es, die passenden Listener-Klassen zu entwerfen und das zugehörige Eventhandling zu erstellen.
Das war es eigentlich. Nur sollte beachtet werden, dass die Swing-Komponenten oft zahlreiche Eigenschaften und Ereignisbehandlungsmethoden beinhalten, die wir aus oben genannten Gründen nicht vollständig durchsprechen. Die API-Dokumentation gibt aber bei Bedarf dazu mehr Informationen. Die Aussagen zu Swing lassen sich auch auf andere neue Techniken und Methoden übertragen. Es wird jeoch sehr oft vorkommen, dass Sie eine als deprecated gekennzeichnete Methode verwenden, denn diese Veränderungen sind fließend und finden ständig statt. Jede (Zwischen-)-Version von Java wird einige Methoden leicht verändern und den Vorgänger als deprecated kennzeichnen, ihn gleichwohl weiter unterstützen. Wenn Sie eine als deprecated gekennzeichnete Methode verwenden, können Sie in der aktuellsten Java-Dokumentation die Methode oder neue Variante der Methode nachschlagen, die statt dessen verwendet werden soll. Ob Sie diese dann verwenden oder nicht, hängt davon ab, ob Sie eine möglichst große Zielgruppe erreichen wollen oder eine leistungsfähigere und meist stabilere Variante vorziehen. Um aber nicht falsch verstanden zu werden – wenn kein entscheidendes Argument dagegen spricht, ist natürlich die neuere Lösung vorzuziehen.
11.2.1
Einige Swing-Probleme
Swing erweitert wie gesagt das AWT erheblich. Die Verwendung von Swing beinhaltet aber auch einige Probleme. Dies betrifft insbesondere die Performance und den Ressourcenbedarf. Swing-Applikationen sind in der Regel langsamer als vergleichbare Anwendungen, die auf dem reinen AWT basie-
626
Java 2 Kompendium
Swing in der Praxis
Kapitel 11
ren. Sie benötigen auch mehr Ressourcen in Hinblick auf den Prozessor und dem Hauptspeicher. Dazu kommen Berichte vergangener Java-Versionen, dass Swing-Appliaktionen die Speicherbereinigung durch den Garbage Collector nicht korrekt befolgt haben. Und nicht zuletzt sollten für Applets die im Buch schon mehrfach beschriebenen Probleme mit Java-Techniken jenseits vom JDK 1.0.2 beachtet werden.
11.3
Swing in der Praxis
Lassen Sie uns einige Swing-Beispiele durchspielen, um die Swing-Philosophie in der Praxis zu sehen. Dabei werden wir die im AWT-Kapitel behandelten Elemente direkt einsetzen.
11.3.1
Umschalten von Look and Feel
Um das Look and Feel von Swing-Applikationen zur Laufzeit umzuschalten, muss man einfach die setLookAndFeel()-Methode verwenden, wie wir es in dem folgenden Beispiel tun. Darüber wird der UIMangager (eine Klasse zum Anpassen des Look and Feel) angewiesen, das Look and Feel entsprechend der der Methode übergebenen Parameter umzuschalten. WindowsAnwender können beispielsweise zwischen Motif, dem Windows-Standard und Metal wählen. Dabei sollte man unbedingt beachten, dass das Wechseln eines Look and Feel einige Exceptions auslösen kann. Wir fangen diese im nachfolgenden Beispiel ab. Das Beispiel beinhaltet nur drei Button, die das Look and Feel jeweils auswählen und dann setzen. Beachten Sie, dass das Programm auf Befehlszeilenebene beendet werden muss. import java.awt.event.*;
import javax.swing.*; public class LookAndFeel extends JFrame implements ActionListener { public LookAndFeel() { super(); JPanel jBPanel = new JPanel(); JButton jB1 = new JButton("Metal"); jB1.addActionListener(this); jBPanel.add(jB1); JButton jB2 = new JButton("Motif"); jB2.addActionListener(this); jBPanel.add(jB2); JButton jB3 = new JButton("Windows"); jB3.addActionListener(this); jBPanel.add(jB3); getContentPane().add("South", jBPanel); } public void actionPerformed(ActionEvent event) { String cmd = event.getActionCommand();
Java 2 Kompendium
Listing 11.1: Dynamisches Umschalten des Look and Feel
627
Kapitel 11
Swing & Co try { // Look and Feel auswählen String laf = "unknown"; if (cmd.equals("Metal")) { laf = "javax.swing.plaf.metal.MetalLookAndFeel"; } else if (cmd.equals("Motif")) { laf = "com.sun.java.swing.plaf.motif.MotifLookAndFeel"; } else if (cmd.equals("Windows")) { laf = "com.sun.java.swing.plaf.windows.WindowsLookAndFeel"; } //Look and Feel umschalten UIManager.setLookAndFeel(laf); SwingUtilities.updateComponentTreeUI(this); } catch (UnsupportedLookAndFeelException e) { System.err.println(e.toString()); } catch (ClassNotFoundException e) { System.err.println(e.toString()); } catch (InstantiationException e) { System.err.println(e.toString()); } catch (IllegalAccessException e) { System.err.println(e.toString()); } } public static void main(String[] args) { LookAndFeel frame = new LookAndFeel(); frame.setLocation(100, 100); frame.pack(); frame.setVisible(true); }}
Abbildung 11.1: Metal-Look
Abbildung 11.2: Motif-Look
Abbildung 11.3: Windows-Look
628
Java 2 Kompendium
Swing in der Praxis
Kapitel 11
Mehr zu den Hintergründen, insbesondere zu Events, folgt im Laufe des Kapitels. Ansonsten wollen wir aber auf das dynamische Umschalten des Look and Feel im Folgenden meist verzichten.
11.3.2
Swing und Applets
Swing lässt sich im Prinzip leicht in Verbindung mit Applets realisieren. Als Erstes wollen wir ein ganz einfaches Applet nach dem Swing-Konzept erstellen, das nur ein paar Komponenten auf der Oberfläche anordnet (ohne Eventhandling). Dabei wird die Java-Syntax kaum Probleme bereiten. Sie kennen alle Komponenten – es ist immer nur ein vorangestelltes J zu dem bisherigen Fall verschieden. Beachten Sie, dass die grundsätzlichen Tools (Layoutmanager, Konstruktoren usw.) so gut wie identisch zu dem konventionellen Fall sind. Wichtigster Unterschied bei dem Beispiel ist, dass die Oberfläche betreffende Aktionen über die Methode getContentPane() »abgefedert« werden, damit das Layout sich auf die spezifisichen Bedingungen der Plattform einstellen kann. Das Beispiel wird dennoch Probleme machen, die von ganz anderer Seite herrühren. import java.awt.*;
import java.awt.event.*; import java.applet.*; import javax.swing.*; public class SwingApplet1 extends JApplet { JButton button1; JButton button2; JCheckBox Klick1; JLabel meinLabel; public void init() { getContentPane().setLayout(new FlowLayout()); button1 = new JButton("Ja"); button2 = new JButton("Nein"); Klick1 = new JCheckBox("Selektier mich"); meinLabel = new JLabel( "Ein einfaches Applet nach dem Swingkonzept"); getContentPane().add(button1); getContentPane().add(button2); getContentPane().add(Klick1); getContentPane().add(meinLabel); } }
Listing 11.2: Ein einfaches Swing-Applet
Sie benötigen zur Referenzierung eine übliche HTML-Datei mit dem <APPLET>-Tag, also eine Datei der folgenden Form:
<APPLET CODE = "SwingApplet1.class" WIDTH = 300 HEIGHT = 200>
Java 2 Kompendium
Listing 11.3: Eine Applet-Referenz für das SwingApplet
629
Kapitel 11
Swing & Co
Abbildung 11.4: Das einfache Swing-Applet im Appletviewer
Das Beispiel ist wirklich einfach und enthält nichts, was so auf den ersten Blick Probleme machen könnte. Oder doch? Dann laden Sie das Applet doch einmal in den Netscape Navigator 4.7 (ein wirklich nicht sonderlich alter Browser). Der Browser meldet einen Fehler. Abbildung 11.5: Probleme mit dem Swing-Applet im Navigator 4.7
Erst der Navigator 6.0 kommt mit dem Swing-Applet zurecht. Ebenso hat der Opera 5 keine Probleme. Das Problem tritt aber auch im Internet Explorer (selbst in der Version 5.5) auf. Dieser kommt erst dann mit dem Applet zurecht, wenn die – sehr kritische1 – Referenzierung über das -Tag erfolgt. Für den Navigator 4.7 referenziert man mit dem <EMBED>-Tag und verschachtelt die HTML-Datei so, dass jeder Browser automatisch seinen richtigen Tag auswählt. Die Referenzierung sieht dann so aus: Listing 11.4: Viel Aufwand für eine einfache Applet-Referenz
1
630
unverhältnismäß g langer Download des Plug-Ins, wenn es noch nicht vorhanden ist, Sicherheitsbedenken gegen ActiveX-Technologie, relativ komplexe Referenzierung
Java 2 Kompendium
Swing in der Praxis
Kapitel 11 Abbildung 11.6: Keine Probleme mehr im Navigator 6
<EMBED type="application/x-java-applet;version=1.3" CODE = "SwingApplet1.class" WIDTH = 300 HEIGHT = 200 scriptable=false pluginspage="http://java.sun.com/products/plugin/1.3/plugininstall.html">
Der in Kapitel 1 bzw. Kapitel 3 (JDK-Tools) besprochene HTML-Konverter von Sun hilft erheblich bei der Konvertierung von einer Applet-Referenzierung mit <APPLET> zu einer -Referenzierung. Diese Probleme bei der Verwendung von Java-Techniken, die jenseits von Java 1.0.2 anzusiedeln sind, macht in Verbindung mit Applets umfangreiche Tests in verschiedenen Browsern notwendig. Oder Sie müssen in den (sehr) sauren Apfel beißen und das - bzw. <EMBED>-Tag verwenden. Swing-Applet-Beispiel mit Interaktion durch Buttons und Radiobuttons Kommen wir aber zu einem etwas komplexeren Applet, mit dem wir auch das Eventhandling unter Swing demonstrieren wollen. Das Applet fügt wieJava 2 Kompendium
631
Kapitel 11
Swing & Co der einige Swing-Komponenten in eine Oberfläche ein und reagiert auf Interaktionen durch den Anwender (JButton und JRadioButton). Dabei werden wir hier ausnahmsweise mit Standard-Adaptern arbeiten. Wir beschränken uns auf Anklicken mit der Maus, aber das Verfahren ist allgemeingültig. Grundsätzlich werden wir uns in dem Kapitel meist auf die Standardereignisse der Swing-Komponenten beschränken. Die verschiedenen Komponenten besitzten meist jedoch zahlreiche mögliche Ereignisse, die bei Bedarf registriert und dann überschrieben werden können. Diese finden Sie allesamt in der API-Dokumentation beschrieben.
Listing 11.5: Ein Swing-Applet mit Eventhandling – Standard-Adapter
632
import javax.swing.*;
import java.awt.*; public class SwingApplet2 extends JApplet { // DEKLARIEREN der CONTROLS JButton jB1 = new JButton(); JButton jB2 = new JButton(); JRadioButton jRB1 = new JRadioButton(); JRadioButton jRB2 = new JRadioButton(); public void init() { // Layout setzen getContentPane().setLayout(new FlowLayout()); // Die nachfolgende Zeile ist reine // Vorsichtsmaßnahme. // Sie unterbindet die Meldung // "Swing: checked access to system event queue", // die bei gewissen Swing-Akionen in einige // Browsern erzeugt wird. getRootPane().putClientProperty("defeatSystemEventQueueCheck", Boolean.TRUE); // INIT_CONTROLS // Hintergrundfarbe setzen getContentPane().setBackground(java.awt.Color.red); // Grö ß e Applet setSize(450,275); // Belegen der CONTROLS mit Werten // danach hinzufügen. jB1.setText("Mache Gross"); getContentPane().add(jB1); jB2.setText("Mache klein"); getContentPane().add(jB2); jRB1.setText("Rot"); jRB1.setSelected(true); // vorselektiert getContentPane().add(jRB1); jRB2.setText("Blau"); getContentPane().add(jRB2); // REGISTIEREN und EINRICHTEN der LISTENER // Erster Schritt: Erzeugen eines Objekts der // Klasse, wo die Auswertung der Events erfolgt.
Java 2 Kompendium
Swing in der Praxis
Kapitel 11
MausReak meineMaus = new MausReak(); // Registrieren der Listener // – hier nur Mauslistener jB1.addMouseListener(meineMaus); jB2.addMouseListener(meineMaus); jRB1.addMouseListener(meineMaus); jRB2.addMouseListener(meineMaus); } // Klasse zum Auswerten der Reaktionen class MausReak extends java.awt.event.MouseAdapter { // Die Methode ruft je nach angeklicktem Control // die entsprechende zu behandelnde Methode auf. public void mouseClicked(java.awt.event.MouseEvent event) { Object object = event.getSource(); if (object == jB1) jB1_mouseClicked(event); else if (object == jB2) jB2_mouseClicked(event); else if (object == jRB1) jRB1_mouseClicked(event); else if (object == jRB2) jRB2_mouseClicked(event); } } void jB1_mouseClicked(java.awt.event.MouseEvent event) { // vergrössere this.setSize(600,400); } void jB2_mouseClicked(java.awt.event.MouseEvent event) { // klein this.setSize(200,100); } void jRB1_mouseClicked(java.awt.event.MouseEvent event) { // Setze Hintergrundfarbe des Applets auf rot. // Beachten Sie, dass der andere Radiobutton // explizit auf false gesetzt werden muss. getContentPane().setBackground(java.awt.Color.red); jRB2.setSelected(false); } void jRB2_mouseClicked(java.awt.event.MouseEvent event) { // Setze Hintergrundfarbe des Applets auf blau. // Beachten Sie, dass der andere Radiobutton // explizit auf false gesetzt werden muss. getContentPane().setBackground(java.awt.Color.blue); jRB1.setSelected(false); } }
Das Beispiel vergrö ß ert das Applet jeweils bei Klick auf den entsprechenden Button bzw. verkleinert es wieder (nur im Appletviewer!). Die Radiobuttons wechseln die Hintergrundfarben des Applets. Beachten Sie die Kommentare im Quelltext, die die entscheidenden Stellen erklären.
Java 2 Kompendium
633
Kapitel 11
Swing & Co
Abbildung 11.7: Groß und rot
Abbildung 11.8: Klein und blau
Swing-Textfelder und absolute Positionierung Sie können unter Swing auch dafür sorgen, dass Komponenten auf der Oberfläche absolut positioniert und in der Grö ß e festgelegt werden. Dafür gibt es die Methode public void setBounds(int x, int y, int breite, int hoehe) aus der Klasse java.awt.Component, die über die Angabe der linken oberen Ecke einer Komponente und seiner Breite/Höhe dafür sorgen. Wir werden dies in dem nächsten Beispiel, wo wir den Quelltext von eben erweitern wollen, anwenden. Aber Vorsicht, solche absoluten Angaben hebeln einen zentralen Vorteil von Java wieder aus: die Anpassung an äußere Umstände. Dies wird das Beispiel demonstrieren. Das Beispiel zeigt aber auch den Sinn von solchen Festlegungen der absoluten Grö ß e (zumindest von einzelnen Komponenten der Oberfläche). Wir belegen ein SwingTextfeld (JTextField) abwechselnd mit einem Text und löschen diesen mit einer anderen Schaltfläche wieder. Da das Textfeld bei der Erzeugung und Platzierung auf dem Panel noch leer ist, würde ein Layout-Manager – ohne
634
Java 2 Kompendium
Swing in der Praxis
Kapitel 11
die feste Vorgabe der Grö ß e – das Textfeld unter Umständen ganz klein darstellen. Bei einem Belegen müsste das Panel jedes Mal neu gezeichnet werden. Das Beispiel reagiert gelegentlich etwas »träge« auf Mausklicks. Eventuell müssen Sie etwas warten oder noch einmal klicken.
import javax.swing.*;
import java.awt.*; public class SwingApplet3 extends JApplet { // DEKLARIEREN der CONTROLS JButton jB1 = new JButton(); JButton jB2 = new JButton(); JButton jB3 = new JButton(); JButton jB4 = new JButton(); JTextField jTF1 = new JTextField(); JRadioButton jRB3 = new JRadioButton(); JRadioButton jRB4 = new JRadioButton(); public void init() { getRootPane().putClientProperty( "defeatSystemEventQueueCheck", Boolean.TRUE); // INIT_CONTROLS // Eventuelles Layout zurücksetzen getContentPane().setLayout(null); // Hintergrundfarbe setzen getContentPane().setBackground(java.awt.Color.red); // Grö ß e Applet setSize(450,350); // Belegen der CONTROLS mit Werten // danach hinzufügen. // Als dritten Schritt legen wir in dem // Beispiel die exakte Position und Grö ß e // des Controls fest jB1.setText("vergroessere"); getContentPane().add(jB1); jB1.setBounds(60,220,140,50); jB2.setText("verkleinere"); getContentPane().add(jB2); jB2.setBounds(228,220,140,50); jB3.setText("Schreibe Text"); getContentPane().add(jB3); jB3.setBounds(60,168,140,50); jB4.setText("Loesche Text"); getContentPane().add(jB4); jB4.setBounds(228,168,140,50); getContentPane().add(jTF1); jTF1.setBounds(60,24,324,38); jRB3.setText("Rot");
Java 2 Kompendium
Listing 11.6: Ein weiteres SwingApplet mit Eventhandling – Standard-Adapter
635
Kapitel 11
Swing & Co jRB3.setSelected(true); // vorselektiert getContentPane().add(jRB3); jRB3.setBounds(60,96,141,22); jRB4.setText("Blau"); getContentPane().add(jRB4); jRB4.setBounds(228,96,141,22); // REGISTIEREN und EINRICHTEN der LISTENER // Erster Schritt: Erzeugen eines Objekts der // Klasse, wo die Auswertung der Events erfolgt. jRB meineMaus = new jRB(); // Registrieren der Listener // – hier nur Mauslistener jB1.addMouseListener(meineMaus); jB2.addMouseListener(meineMaus); jB3.addMouseListener(meineMaus); jB4.addMouseListener(meineMaus); jRB3.addMouseListener(meineMaus); jRB4.addMouseListener(meineMaus); } // Klasse zum Auswerten der Reaktionen class jRB extends java.awt.event.MouseAdapter { // Die Methode ruft je nach angeklicktem Control // die entsprechende Methode zum Behandeln auf. public void mouseClicked(java.awt.event.MouseEvent event){ Object object = event.getSource(); if (object == jB1) jB1_mouseClicked(event); else if (object == jB2) jB2_mouseClicked(event); else if (object == jB3) jB3_mouseClicked(event); else if (object == jB4) jB4_mouseClicked(event); else if (object == jRB3) jRB3_mouseClicked(event); else if (object == jRB4) jRB4_mouseClicked(event); } } void jB1_mouseClicked(java.awt.event.MouseEvent event) { // vergrö ß ere this.setSize(600,400); } void jB2_mouseClicked(java.awt.event.MouseEvent event) { // verkleinere this.setSize(200,100); } void jB3_mouseClicked(java.awt.event.MouseEvent event) { // Schreibe Text jTF1.setText("Ei gude wie?"); }
636
Java 2 Kompendium
Swing in der Praxis
Kapitel 11
void jB4_mouseClicked(java.awt.event.MouseEvent event) { // Lösche Text jTF1.setText(""); } void jRB3_mouseClicked(java.awt.event.MouseEvent event) { getContentPane().setBackground(java.awt.Color.red); jRB4.setSelected(false); } void jRB4_mouseClicked(java.awt.event.MouseEvent event) { getContentPane().setBackground(java.awt.Color.blue); jRB4.setSelected(false); } }
Das Beispiel vergrö ß ert wieder jeweils bei Klick auf den entsprechenden Button das Applet bzw. verkleinert es wieder (nur im Appletviewer!). Dabei werden die Komponenten allerdings festgehalten, was die Probleme mit dieser Technik zeigen soll. Die beiden neuen Buttons schreiben und löschen Text im Textfeld. Die Radiobuttons wechseln wieder die Hintergrundfarben des Applets. Abbildung 11.9: Original-Layout
Java 2 Kompendium
637
Kapitel 11
Swing & Co
Abbildung 11.10: Mit Text, blau und Komponenten leicht zu weit links
Abbildung 11.11: Hier knallt es im Layout
11.4
Eigenständige Applikationen und Swing
Swing ist selbstverständlich nicht auf Applets beschränkt. Im Gegenteil – bei eigenständigen Applikationen treten die Probleme der Inkompatibilität von Browsern nicht auf. Hauptsächlich muss man die Applikation – statt auf Frame – auf JFrame aufbauen. Wir werden jetzt ein kleines Beispiel durchspielen, wo wir diese Technik in Verbindung mit einer eigenständigen Applikation einsetzen. Es handelt sich um eine Swing-Applikation, wo nur ein Button vorhanden ist. Dieser beendet das Programm. Listing 11.7: Eine ganz einfache eigenständige Swing-Applikation
638
import import import public
java.awt.*; javax.swing.*; java.awt.event.*; class SwingAppl extends JFrame {
Java 2 Kompendium
Eigenständige Applikationen und Swing
Kapitel 11
JButton jButton1 = new JButton(); FlowLayout flowLayout1 = new FlowLayout(); public SwingAppl() { initial(); } public static void main(String[] args) { SwingAppl swingAppl = new SwingAppl(); swingAppl.setSize(200,100); swingAppl.setVisible(true); } private void initial() { jButton1.setActionCommand("Ende"); jButton1.setText("Ende"); jButton1.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jButton1_actionPerformed(e);}}); getContentPane().setLayout(flowLayout1); setTitle("Swing-Applikation"); getContentPane().add(jButton1, null); } void jButton1_actionPerformed(ActionEvent e) { System.exit(0); }} Abbildung 11.12: Eine einfache Swing-Applikation
11.4.1
Eine Swing-Applikations-Schablone
Nachfolgend finden Sie eine Schablone für eine eigenständige Swing-Applikation, wo ein wenig mehr Funktionalität realisiert ist. Sie erstellt ein Fenster, installiert einige Reaktionsmöglichkeiten und einen Ende-Dialog. Darauf aufbauend können Sie funktionstüchtige Swing-Applikationen erstellen. import java.awt.*; import java.awt.event.*; import javax.swing.*; /** Eine Basis-Swing-Schablone für eine Applikation. */ public class SwingApplikaSchablone extends JFrame { public SwingApplikaSchablone() { // INIT_CONTROLS setTitle("Swing-Applikation"); Java 2 Kompendium
Listing 11.8: Eine Schablone für eigenständige Swing-Applikationen
639
Kapitel 11
Swing & Co setDefaultCloseOperation(javax.swing.JFrame.DO_NOTHING_ON_CLOSE); getContentPane().setLayout(new BorderLayout(0,0)); setSize(500,300); setVisible(false); // REGISTER_LISTENERS Fenst meinFenst = new Fenst(); this.addWindowListener(meinFenst); Akt meineAktion = new Akt(); } /* Erstellt eine neue Instanz von SwingApplikaSchablone mit dem im Konstruktor angebenen Titel. */ public SwingApplikaSchablone(String sTitle) { this(); setTitle(sTitle); } static public void main(String args[]) { try { // Optional Anpassen an das Look and Feel // des Nativesystems. /* try { UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName()); } catch (Exception e) { } */ // Neue Instanz des Applikationsfensters und // sichtbar machen. (new SwingApplikaSchablone()).setVisible(true); } catch (Throwable t) { t.printStackTrace(); // Ende mit Fehlermeldung im Falle von Problemen. System.exit(1); } } void exitApplication() { try { // Anzeige eines Ende-Dialogs int antwort = JOptionPane.showConfirmDialog(this, "Wollen Sie wirklich beenden?", "Swing-Applikation – Exit" , JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE); if (antwort == JOptionPane.YES_OPTION) { this.setVisible(false); // Verstecke Frame this.dispose(); // Freigabe Systemressourcen System.exit(0); // Ende } } catch (Exception e) { } }
640
Java 2 Kompendium
Eigenständige Applikationen und Swing
Kapitel 11
class Fenst extends java.awt.event.WindowAdapter { public void windowClosing(java.awt.event.WindowEvent event) { Object object = event.getSource(); if (object == SwingApplikaSchablone.this) SwingApplikaSchablone_windowClosing(event); } } void SwingApplikaSchablone_windowClosing(java.awt.event.WindowEvent event) { SwingApplikaSchablone_windowClosing_Interaction1(event); } void SwingApplikaSchablone_windowClosing_Interaction1(java.awt.event.WindowEvent event) { try { this.exitApplication(); } catch (Exception e) { } } class Akt implements java.awt.event.ActionListener { public void actionPerformed(java.awt.event.ActionEvent event) { Object object = event.getSource(); } } } Abbildung 11.13: Die Schablone
Abbildung 11.14: Der zugehörige Swing-Ende-Dialog
Java 2 Kompendium
641
Kapitel 11
Swing & Co Beachten Sie die Methode showConfirmDialog(). Diese gehört wie zahlreiche weitere Methoden zum Anzeigen von verschiedenartigen Dialogfenstern zur Klasse public class JOptionPane extends JComponent implements Accessible. Dort finden Sie unter anderem die folgenden Dialogmethoden: static int showConfirmDialog(Component parentComponent, Object message) static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType) static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType, int messageType) static int showConfirmDialog(Component parentComponent, Object message, String title, int optionType, int messageType, Icon icon) static String showInputDialog(Component parentComponent, Object message) static String showInputDialog(Component parentComponent, Object message, String title, int messageType) static Object showInputDialog(Component parentComponent, Object message, String title, int messageType, Icon icon, Object[] selectionValues, Object initialSelectionValue) static String showInputDialog(Object message) static int showInternalConfirmDialog(Component parentComponent, Object message) static int showInternalConfirmDialog(Component parentComponent, Object message, String title, int optionType) static int showInternalConfirmDialog(Component parentComponent, Object message, String title, int optionType, int messageType) static int showInternalConfirmDialog(Component parentComponent, Object message, String title, int optionType, int messageType, Icon icon) static String showInternalInputDialog(Component parentComponent, Object message) static String showInternalInputDialog(Component parentComponent, Object message, String title, int messageType) static Object showInternalInputDialog(Component parentComponent, Object message, String title, int messageType, Icon icon, Object[] selectionValues, Object initialSelectionValue) static void showInternalMessageDialog(Component parentComponent, Object message) static void showInternalMessageDialog(Component parentComponent, Object message, String title, int messageType) static void showInternalMessageDialog(Component parentComponent, Object message, String title, int messageType, Icon icon) static int showInternalOptionDialog(Component parentComponent, Object message, String title, int optionType, int messageType, Icon icon, Object[] options, Object initialValue) static void showMessageDialog(Component parentComponent, Object message) static void showMessageDialog(Component parentComponent, Object message, String title, int messageType) static void showMessageDialog(Component parentComponent, Object message, String title, int messageType, Icon icon) static int showOptionDialog(Component parentComponent, Object message,
642
Java 2 Kompendium
Swing-Komponenten im Detail
Kapitel 11
String title, int optionType, int messageType, Icon icon, Object[] options, Object initialValue)
11.5
Swing-Komponenten im Detail
Schauen wir uns anhand diverser Beispiele nun die wichtigsten (noch fehlenden) Swingkomponenten an und wie sie von der Syntax her integriert werden können. Die Ähnlichkeiten zu den konventionellen AWT-Komponenten sind offensichtlich, aber es gibt auch zahlreiche Neuerungen von Swing, denen keine Entsprechungen im AWT gegenüberstehen. Sie finden hier keine in je einem nummerierten Unterkapitel aufgeführten einzelnen Komponenten. Beim Aufbau dieses Abschnitts und der Zusammensetzung der besprochenen Swing-Komponenten soll explizit das AWTKapitel als Grundlage dienen, das einen solchen Aufbau realisiert. Motivation ist, die Komponenten in Form von praktischen Beispielen mit gut zusammenpassenden Elementen einzusetzen. Swing-Panels und Swing-Labels Das erste Beispiel ist eine kleine Erweiterung unserer ersten Swing-Applikation. Zusätzlich zu dieser arbeiten wir mit Swing-Panels und einem SwingLabel, das dynamisch beschriftet und gelöscht werden soll. Also ein Beispiel, das wir so auch mit dem AWT realisiert haben. Wir verwenden dabei anonyme Klassen zum Eventhandling. import java.awt.*; import javax.swing.*; import java.awt.event.*; public class SwingAppl2 extends JFrame { BorderLayout borderLayout1 = new BorderLayout(); JPanel jPanel1 = new JPanel(); FlowLayout flowLayout1 = new FlowLayout(); JButton jButton1 = new JButton(); JButton jButton2 = new JButton(); JButton jButton3 = new JButton(); JLabel jLabel1 = new JLabel(); public SwingAppl2() { initial(); } public static void main(String[] args) { SwingAppl2 swingAppl = new SwingAppl2(); swingAppl.setSize(300,100); swingAppl.setVisible(true); } private void initial() { this.getContentPane().setLayout(borderLayout1); this.setTitle("Swing-Applikation"); Java 2 Kompendium
Listing 11.9: Eine Swing-Applikation mit LayoutManager, Button, Panel und Label
643
Kapitel 11
Swing & Co jPanel1.setLayout(flowLayout1); jButton1.setText("Ende"); jButton1.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jButton1_actionPerformed(e);}}); jButton2.setText("Schreibe"); jButton2.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jButton2_actionPerformed(e);}}); jButton3.setText("Lösche"); jButton3.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jButton3_actionPerformed(e);}}); jLabel1.setHorizontalAlignment( SwingConstants.CENTER); this.getContentPane().add(jPanel1, BorderLayout.SOUTH); jPanel1.add(jButton1, null); jPanel1.add(jButton2, null); jPanel1.add(jButton3, null); this.getContentPane().add(jLabel1, BorderLayout.CENTER); } void jButton1_actionPerformed(ActionEvent e) { System.exit(0); } void jButton2_actionPerformed(ActionEvent e) { jLabel1.setText("No Pc no cry"); } void jButton3_actionPerformed(ActionEvent e) { jLabel1.setText(""); }}
Abbildung 11.15: Eine Swing-Applikation mit Panels und Label
Ja nach Button wird das Label geschrieben oder gelöscht. Der Button ganz links beendet das Programm. Menüs, Bildaufleisten, Slider, Progressbar und Toolbar unter Swing Das nächste Beispiel zeigt einige weitere Swing-Techniken und -Komponenten. Etwa einen Slider (JSlider), der im Wesentlichen einer Scrollbar (also 644
Java 2 Kompendium
Swing-Komponenten im Detail
Kapitel 11
Bildlaufleiste – auch die wird in dem Beispiel verwendet) entspricht (unter Swing mit der Klasse JScrollbar realisiert) und eine Toolbar zum Anordnen von Komponenenten (JToolbar). Dazu kommt noch eine Progressbar (ein Fortschrittsbalken, der anzeigt, wie weit ein Prozess gediehen ist) (JProgressBar), die man zum Anzeigen des Fortschritts einer Aktion verwendet (beispielsweise um den Fortschritt einer Installation anzuzeigen). Zusätzlich wird ein Menü unter Swing realisiert. Beachtet werden sollte zudem, dass das Beispiel Bilder für Schaltflächen verwendet, die über die Klasse ImageIcon und und Methode setIcon() realisiert werden. Ebenso wird die Verwendung einer Quick-Info für Komponenten demonstriert (setToolTipText()). Das Beispiel besteht aus drei Dateien und ist etwas umfangreicher, aber ausführlich im Quelltext dokumentiert. Datei Nummer eins ist die eigentliche Applikation und recht klein, denn sie ruft nur das in der zweiten Datei zwei beschriebene Fenster auf. Dabei zeigt diese Datei als kleines Bonbon, wie man eine Applikation auf dem Bildschirm zentrieren kann. import javax.swing.UIManager; import java.awt.*; public class SwingAppl3 { boolean packFrame = false; /**Die Anwendung konstruieren*/ public SwingAppl3() { SwingFrame1 frame = new SwingFrame1(); // Frames überprüfen, die voreingestellte Grö ß e // haben, Frames packen, die nutzbare bevorzugte // Grö ß eninformationen enthalten, z.B. aus ihrem // Layout if (packFrame) { frame.pack(); } else { frame.validate(); } // Fenster auf Bildschirm zentrieren Dimension screenSize = Toolkit.getDefaultToolkit().getScreenSize(); Dimension frameSize = frame.getSize(); if (frameSize.height > screenSize.height) { frameSize.height = screenSize.height; } if (frameSize.width > screenSize.width) { frameSize.width = screenSize.width; } frame.setLocation((screenSize.width – frameSize.width) / 2, (screenSize.height – frameSize.height) / 2); frame.setVisible(true); } Java 2 Kompendium
Listing 11.10: Eine Swing-Applikation mit Slider, Scrollbar, Progressbar, Toolbar, Menü und Statuszeile
645
Kapitel 11
Swing & Co /** main()-Methode der Applikaiton */ public static void main(String[] args) { try { UIManager.setLookAndFeel( UIManager.getSystemLookAndFeelClassName()); } catch(Exception e) { e.printStackTrace(); } new SwingAppl3(); }} Die Auslagerung des Programmstarts Datei Nummer zwei beinhaltet die wesentliche Funktionalität des Beispiels. Beachten Sie bitte die Kommentare im Quelltext. import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.event.*; import java.beans.*; public class SwingFrame1 extends JFrame { /* Die Objekte */ JPanel mPanel; JMenuBar jMBar1 = new JMenuBar(); JMenu jMFile = new JMenu(); JMenuItem jMFileExit = new JMenuItem(); JMenu jMHelp = new JMenu(); JMenuItem jMHelpAbout = new JMenuItem(); JToolBar jToolBar = new JToolBar(); JButton jB1 = new JButton(); JButton jB2 = new JButton(); ImageIcon image1; ImageIcon image2; JLabel statusZeil = new JLabel(); BorderLayout bdrLayout1 = new BorderLayout(); JPanel jPanel1 = new JPanel(); BorderLayout bdrLayout2 = new BorderLayout(); JProgressBar jPB1 = new JProgressBar(); JScrollBar jScBr1 = new JScrollBar(); JSlider jSld1 = new JSlider(); JLabel jLb1 = new JLabel(); JLabel jLb2 = new JLabel(); /* Der Konstruktor */ public SwingFrame1() { enableEvents(AWTEvent.WINDOW_EVENT_MASK); try { initial(); } catch(Exception e) { e.printStackTrace(); }
646
Java 2 Kompendium
Swing-Komponenten im Detail
Kapitel 11
} /**Initialisierung der Komponenten und Registrierung der Listener */ private void initial() throws Exception { // Swing-Panel gesetzt und initialisiert mPanel = (JPanel) this.getContentPane(); mPanel.setLayout(bdrLayout1); // Fenstergroesse und Titelzeile this.setSize(new Dimension(400, 300)); this.setTitle("SwingMuster 1"); // Label1 dient als Statuszeile – leer setzen statusZeil.setText(" "); // Menuebar beschriften jMFile.setText("Datei"); jMFileExit.setText("Beenden"); // Listener zum Beenden über das Menü jMFileExit.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { jMFileExit_actionPerformed(e); } }); // Hilfemenü jMHelp.setText("Hilfe"); jMHelpAbout.setText("Info"); // Listener zum Aufruf des Infodialogs jMHelpAbout.addActionListener( new ActionListener() { public void actionPerformed(ActionEvent e) { jMHelpAbout_actionPerformed(e); } }); // Listener Button 1 jB1.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jB1_actionPerformed(e); } }); // Quick-Info für Button 1 jB1.setToolTipText("Text im Label anzeigen"); // Grafik für Button 1 – ein jB1.setIcon(new ImageIcon(getToolkit().getImage("bild1.gif"))); // Analog Button 2 jB2.setIcon(new ImageIcon(getToolkit().getImage("bild2.gif"))); // Alternative für die Referenzierung eines Icons. // Hardcodierte Pfadangabe und Verwendung von // java.net.URL – AUSKOMMENTIERT /* jB1.setIcon(new ImageIcon(new java.net.URL("file:///C:/WINNT/Profiles/
Java 2 Kompendium
647
Kapitel 11
Swing & Co Administrator/jproject/SwingMuster1/src/swingmuster1/bild1.gif"))); */ jB2.addActionListener( new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jB2_actionPerformed(e); } }); jB2.setToolTipText("Text im Label löschen"); // Panel Layout setzen jPanel1.setLayout(bdrLayout2); /* Slider vertikal ausrichten, Wert setzen und Listener registrieren */ jSld1.setOrientation(JSlider.VERTICAL); jSld1.setValue(0); jSld1.addChangeListener( new javax.swing.event.ChangeListener() { public void stateChanged(ChangeEvent e) { jSld1_stateChanged(e); } }); /* Label 1 formatieren und positionieren */ jLb1.setFont(new java.awt.Font("Dialog", 0, 42)); jLb1.setForeground(Color.red); jLb1.setHorizontalAlignment(SwingConstants.CENTER); // Label 2 dito, sowie Listener registrieren jLb2.setFont(new java.awt.Font("Dialog", 0, 24)); jLb2.setHorizontalAlignment(SwingConstants.CENTER); jLb2.setText("Swing"); jLb2.addMouseListener( new java.awt.event.MouseAdapter() { public void mouseEntered(MouseEvent e) { jLb2_mouseEntered(e); } public void mouseExited(MouseEvent e) { jLb2_mouseExited(e); } }); // Listener für Scrollbar registrieren jScBr1.addAdjustmentListener( new java.awt.event.AdjustmentListener() { public void adjustmentValueChanged(AdjustmentEvent e) { jScBr1_adjustmentValueChanged(e); } }); // Toolbar mit Button füllen jToolBar.add(jB1); jToolBar.add(jB2); // Menubar füllen jMFile.add(jMFileExit); jMHelp.add(jMHelpAbout);
648
Java 2 Kompendium
Swing-Komponenten im Detail
Kapitel 11
jMBar1.add(jMFile); jMBar1.add(jMHelp); // Komponenten dem Fenster hinzufügen this.setJMenuBar(jMBar1); mPanel.add(jToolBar, BorderLayout.NORTH); mPanel.add(statusZeil, BorderLayout.SOUTH); mPanel.add(jPanel1, BorderLayout.CENTER); jPanel1.add(jPB1, BorderLayout.SOUTH); jPanel1.add(jScBr1, BorderLayout.EAST); jPanel1.add(jSld1, BorderLayout.WEST); jPanel1.add(jLb1, BorderLayout.CENTER); jPanel1.add(jLb2, BorderLayout.NORTH); } /**Aktion Beenden */ public void jMFileExit_actionPerformed(ActionEvent e) { System.exit(0); } /**Aktion Info – ein modaler Dialog, der immer zentriert im aufrufenden Fenster angezeigt wird. */ public void jMHelpAbout_actionPerformed(ActionEvent e) { SwingFrame1_Infodialog dlg = new SwingFrame1_Infodialog(this); Dimension dlgSize = dlg.getPreferredSize(); Dimension frmSize = getSize(); Point loc = getLocation(); dlg.setLocation((frmSize.width – dlgSize.width) / 2 + loc.x, (frmSize.height – dlgSize.height) / 2 + loc.y); dlg.setModal(true); dlg.show(); } /* Beenden über Schliessbutton */ protected void processWindowEvent(WindowEvent e) { super.processWindowEvent(e); if (e.getID() == WindowEvent.WINDOW_CLOSING) { jMFileExit_actionPerformed(null); } } /* Veränderung der Scrollbar – Slider, Label und Progressbar zeigen die Veränderung */ void jScBr1_adjustmentValueChanged(AdjustmentEvent e) { jLb1.setText((new Integer(jScBr1.getValue())).toString()); jPB1.setValue(jScBr1.getValue()); jSld1.setValue(jScBr1.getValue()); } /* Veränderung des Sliders – Scrollbar, Label und Progressbar zeigen die Veränderung */ void jSld1_stateChanged(ChangeEvent e) {
Java 2 Kompendium
649
Kapitel 11
Swing & Co jLb1.setText((new Integer(jSld1.getValue())).toString()); jPB1.setValue(jSld1.getValue()); jScBr1.setValue(jSld1.getValue()); } /* Button 1 betätigt – Label wird angezeigt*/ void jB1_actionPerformed(ActionEvent e) { jLb2.setVisible(true); } /* Button 2 betätigt – Label wird ausgeblendet*/ void jB2_actionPerformed(ActionEvent e) { jLb2.setVisible(false); } /* Label 2 mit der Maus überstrichen – Text in Statuszeile */ void jLb2_mouseEntered(MouseEvent e) { statusZeil.setText("Das Label"); } /* Bereich von Label 2 mit der Maus verlassen – Text in Statuszeile wird gelöscht */ void jLb2_mouseExited(MouseEvent e) { statusZeil.setText(""); }}
Abbildung 11.16: Das Label und die Progressbar zeigen die Position des Sliders an.
Nun fehlt nur noch die dritte Datei, in der der Hilfedialog realisiert wird. Diese Datei arbeitet mit einem Dialogfenster und Swing, das auf JDialog basiert. Listing 11.11: Der Infodialog
650
import import import import /* Der
java.awt.*; java.awt.event.*; javax.swing.*; javax.swing.border.*; Infodialog zu der Appplikation SwingAppl3 */
Java 2 Kompendium
Swing-Komponenten im Detail
Kapitel 11
public class SwingFrame1_Infodialog extends JDialog implements ActionListener { /* Die Komponenten */ JPanel pl1 = new JPanel(); JPanel pl2 = new JPanel(); JPanel insetsPanel1 = new JPanel(); JPanel insetsPanel2 = new JPanel(); JPanel insetsPanel3 = new JPanel(); JButton bt1 = new JButton(); JLabel imageLabel = new JLabel(); JLabel lb1 = new JLabel(); JLabel lb2 = new JLabel(); JLabel lb3 = new JLabel(); JLabel lb4 = new JLabel(); BorderLayout bdLayout1 = new BorderLayout(); BorderLayout bdLayout2 = new BorderLayout(); FlowLayout flowLayout1 = new FlowLayout(); GridLayout gridLayout1 = new GridLayout(); String product = ""; String version = "1.0"; String copyright = "Copyright (c) 2001"; String comments = ""; JLabel jLb1 = new JLabel(); JLabel jLb2 = new JLabel(); JLabel jLb3 = new JLabel(); public SwingFrame1_Infodialog(Frame parent) { super(parent); enableEvents(AWTEvent.WINDOW_EVENT_MASK); try { initial(); } catch(Exception e) { e.printStackTrace(); } pack(); } /**Initialisierung der Komponenten*/ private void initial() throws Exception { this.setTitle("Info"); setResizable(false); pl1.setLayout(bdLayout1); pl2.setLayout(bdLayout2); insetsPanel1.setLayout(flowLayout1); insetsPanel2.setLayout(flowLayout1); insetsPanel2.setBorder( BorderFactory.createEmptyBorder( 10, 10, 10, 10)); gridLayout1.setRows(4); gridLayout1.setColumns(1); lb1.setText("Version 1.0");
Java 2 Kompendium
651
Kapitel 11
Swing & Co lb3.setText(copyright); lb4.setText(comments); insetsPanel3.setLayout(gridLayout1); insetsPanel3.setBorder( BorderFactory.createEmptyBorder( 10, 60, 10, 10)); bt1.setText("OK"); bt1.addActionListener(this); jLb1.setText("RJS EDV"); jLb3.setText("WWW.RJS.DE"); insetsPanel2.add(imageLabel, null); pl2.add(insetsPanel2, BorderLayout.WEST); this.getContentPane().add(pl1, null); insetsPanel3.add(lb1, null); insetsPanel3.add(lb2, null); insetsPanel3.add(lb3, null); insetsPanel3.add(lb4, null); insetsPanel3.add(jLb1, null); insetsPanel3.add(jLb2, null); insetsPanel3.add(jLb3, null); pl2.add(insetsPanel3, BorderLayout.CENTER); insetsPanel1.add(bt1, null); pl1.add(insetsPanel1, BorderLayout.SOUTH); pl1.add(pl2, BorderLayout.NORTH); } /* Ende über Schließbutton */ protected void processWindowEvent(WindowEvent e) { if (e.getID() == WindowEvent.WINDOW_CLOSING) { cancel(); } super.processWindowEvent(e); } /* Dialog schließen*/ void cancel() { dispose(); } /**Dialog bei Schalter-Ereignis schließen*/ public void actionPerformed(ActionEvent e) { if (e.getSource() == bt1) { cancel(); } }}
Das Beispiel realisiert eine recht umfangreiche Funktionalität mit diversen Feinheiten, die im Quelltext ausführlich dokumentiert sind. Viel Funktionalität basiert auf der Methode getValue(), mit der im Allgemeinen Werte von Komponenten wie Slider oder Bildlaufleisten abgefragt werden können.
652
Java 2 Kompendium
Swing-Komponenten im Detail
Kapitel 11 Abbildung 11.17: Der Infodialog
Wenn Sie die Maus über die Buttons bewegen, wird eine Quick-Info angezeigt. Dazu wird die Methode setToolTipText() verwendet. Abbildung 11.18: Quick-Info
Wenn Sie auf den einen Button klicken, wird das Label mit dem Text »Swing« angezeigt, ansonsten versteckt. Die Buttons haben keine Beschriftung, sondern zeigen Icons. Diese werden mit der Anweisung jB1.setIcon(new ImageIcon(getToolkit().getImage("bild1.gif")));
gesetzt. Alternativ können Sie hier auch gut mit der Klasse java.net.URL und einer anderen Variante der Methode ImageIcon() arbeiten, wenn Sie ein Bild aus einer beliebigen URL laden. Ein Beispiel für einen hardcodierten Zugriff auf die Festplatte:
Java 2 Kompendium
653
Kapitel 11
Swing & Co jB1.setIcon(new ImageIcon(new java.net.URL("file:///C:/WINNT/Profiles/ Administrator/project/SwingMuster1/src/swingmuster1/bild1.gif")));
Unter Verwendung des http-Protokolls ist dies auch mit eine beliebigen Internet-URL möglich. Auch das Label mit dem Text »Swing« besitzt eine Funktionalität. Wenn der Mauszeiger den Bereich überstreicht, wird in der Statuszeile ein Text angezeigt. Abbildung 11.19: Überstreichen des Labels bewirkt eine Textausgabe in der Statuszeile.
Das Menü der Applikation ermöglicht das Beenden des Programms und die Anzeige eines Infodialogs. Abbildung 11.20: Das Menü – Beachten Sie, dass das Label mit dem Button ausgeblendet wurde
654
Java 2 Kompendium
Swing-Komponenten im Detail
Kapitel 11
Popup-Menüs, Checkboxen, Listenfelder, Textfelder, Toggle-Buttons und JTabbedPanes Das vorletzte Beispiel zum Thema Swing soll den Einsatz von Popup-Menüs (Klasse JPopupMenu), Checkboxen (JCheckBox), Listenfeldern (JComboBox), Textfeldern (JTextArea) und Toggle-Buttons demonstrieren. Toggle-Buttons basieren auf der Klasse JToggleButton und erweitern neben einigen zusätzlichen Eigenschaften im Wesentlichen normale Buttons um die Eigenschaft, einen gedrückten Status anzeigen zu können. Dazu kommt der Einsatz von Registerblättern, wie sie beispielsweise aus Excel oder den verschiedensten Dialogfenstern unter Windows bekannt sind. Damit ist es möglich, in einem Fensterbereich abwechselnd verschiedene Inhalte anzuzeigen. Diese Registerblätter werden als ein besonderes Swing-Panel realisiert, das auf der Klasse JTabbedPane aufbaut. import javax.swing.*; import java.awt.*; import java.awt.event.*; public class VerschiedeneSwingKomponenten extends JFrame { // Die Komponenten BorderLayout borderLayout1 = new BorderLayout(); // Container für Tabs JTabbedPane jTabbedPane1 = new JTabbedPane(); JPanel jPanel1 = new JPanel(); JPanel jPanel2 = new JPanel(); BorderLayout borderLayout2 = new BorderLayout(); JPanel jPanel3 = new JPanel(); FlowLayout flowLayout1 = new FlowLayout(); // Toggle-Button JToggleButton jToggleButton1 = new JToggleButton(); BorderLayout borderLayout3 = new BorderLayout(); JPanel jPanel4 = new JPanel(); JPanel jPanel5 = new JPanel(); JToggleButton jToggleButton2 = new JToggleButton(); JLabel jLabel1 = new JLabel(); BorderLayout borderLayout4 = new BorderLayout(); JPanel jPanel6 = new JPanel(); // Checkbox JCheckBox jCheckBox1 = new JCheckBox(); JPanel jPanel7 = new JPanel(); JPanel jPanel8 = new JPanel(); // TextArea JTextArea jTextArea1 = new JTextArea(); BorderLayout borderLayout5 = new BorderLayout(); // Combobox JComboBox jComboBox1 = new JComboBox(); JLabel jLabel2 = new JLabel(); // Popup-Menü JPopupMenu jPopupMenu1 = new JPopupMenu();
Java 2 Kompendium
Listing 11.12: Popup-Menüs, Checkboxen, Listenfelder, Textfelder, Toggle-Button und JTabbedPanes
655
Kapitel 11
Swing & Co JMenuItem jMenuItem1 = new JMenuItem(); // Die main()-Methode public static void main(String[] args) { VerschiedeneSwingKomponenten u = new VerschiedeneSwingKomponenten(); u.setSize(300,300); u.show(); } // Konstruktor public VerschiedeneSwingKomponenten() { try { initial(); } catch(Exception e) { e.printStackTrace(); } } // Initialisierung private void initial() throws Exception { this.getContentPane().setLayout(borderLayout1); jPanel1.setLayout(borderLayout2); jPanel3.setLayout(flowLayout1); jToggleButton1.setText("Übertrage"); jToggleButton1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jToggleButton1_actionPerformed(e); }}); jPanel2.setLayout(borderLayout3); jToggleButton2.setText("Ende"); jToggleButton2.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jToggleButton2_actionPerformed(e); } }); jPanel4.setLayout(borderLayout4); jCheckBox1.setSelected(true); jCheckBox1.setText("Text anzeigen"); jCheckBox1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jCheckBox1_actionPerformed(e); }}); jPanel8.setLayout(borderLayout5); jLabel2.setText("Textmuster"); jComboBox1.addActionListener(new java.awt.event.ActionListener() { public void actionPerformed(ActionEvent e) { jComboBox1_actionPerformed(e); }}); // Zeilenumbruch in TextArea erlauben jTextArea1.setLineWrap(true); jMenuItem1.setText("Übertragen"); jMenuItem1.addActionListener(new java.awt.event.ActionListener() {
656
Java 2 Kompendium
Swing-Komponenten im Detail
Kapitel 11
public void actionPerformed(ActionEvent e) { jMenuItem1_actionPerformed(e); }}); jPanel3.addMouseListener(new java.awt.event.MouseAdapter() { public void mouseClicked(MouseEvent e) { jPanel3_mouseClicked(e); }}); // Tab-Container hinzufügen this.getContentPane().add(jTabbedPane1, BorderLayout.CENTER); jTabbedPane1.add(jPanel1, "Text eintragen"); jPanel1.add(jPanel3, BorderLayout.NORTH); jPanel3.add(jToggleButton1, null); jPanel1.add(jPanel7, BorderLayout.SOUTH); jPanel7.add(jLabel2, null); // Listenfeld 4 Einträge hinzufügen jComboBox1.addItem(makeObj("Muster 1")); jComboBox1.addItem(makeObj("Muster 2")); jComboBox1.addItem(makeObj("Muster 3")); jComboBox1.addItem(makeObj("Muster 4")); jPanel7.add(jComboBox1, null); jPanel1.add(jPanel8, BorderLayout.CENTER); jPanel8.add(jTextArea1, BorderLayout.CENTER); jTabbedPane1.add(jPanel2, "Text anzeigen"); jPanel2.add(jPanel4, BorderLayout.CENTER); jPanel4.add(jLabel1, BorderLayout.CENTER); jPanel4.add(jPanel6, BorderLayout.SOUTH); jPanel6.add(jCheckBox1, null); jPanel2.add(jPanel5, BorderLayout.NORTH); jPanel5.add(jToggleButton2, null); // Popup-Menü Eintrag hinzufügen jPopupMenu1.add(jMenuItem1); // Popup-Menü dem Panel hinzufügen jPanel3.add(jPopupMenu1); } // Methode zum Generieren von Objekten // Wird beim Erzeugen von Eintägen für das // Listenfeld verwendet private Object makeObj(final String item) { return new Object() { public String toString() { return item; } }; } // Reaktion auf Klicks im Listenfeld void jComboBox1_actionPerformed(ActionEvent e) { // Unterscheidung, welcher Eintrag // ausgewählt wurde if(jComboBox1.getSelectedIndex()==0) { jTextArea1.setText("Es grünt so grün"); } else if(jComboBox1.getSelectedIndex()==1) { jTextArea1.setText("Die Antwort ist 42"); }
Java 2 Kompendium
657
Kapitel 11
Swing & Co else if(jComboBox1.getSelectedIndex()==2) { jTextArea1.setText("Wie denn, wo denn, was denn?"); } else if(jComboBox1.getSelectedIndex()==3) { jTextArea1.setText("Was unterscheidet einen Porsche von einer Ente? Eine Ente ist cooler!"); } } // Programm beenden void jToggleButton2_actionPerformed(ActionEvent e) { System.exit(0); } // Text aus TextArea übertragen in Label auf // anderem Tabellenblatt – per Button void jToggleButton1_actionPerformed(ActionEvent e) { jLabel1.setText(jTextArea1.getText()); } // Text aus TextArea übertragen in Label auf // anderem Tabellenblatt – per Item im Popup void jMenuItem1_actionPerformed(ActionEvent e) { jLabel1.setText(jTextArea1.getText()); } // Anzeigen Popup-Menü void jPanel3_mouseClicked(MouseEvent e) { // Nur wenn rechte Maustaste gedrückt if(e.getModifiers()==Event.META_MASK) { // Zeige Popup in angegebener Komponente // an den Koordinaten, wo Klick erfolgte jPopupMenu1.show(jPanel3,e.getX(),e.getY()); } } // Checkbox ist selektiert oder nicht void jCheckBox1_actionPerformed(ActionEvent e) { if(jCheckBox1.isSelected()) { jLabel1.setVisible(true); } else { jLabel1.setVisible(false); } }}
Wenn das Programm gestartet wird, werden Sie zwei Registerblätter vorfinden. Auf dem zugrunde liegenden Container werden diverse weitere Panels und Komponenten platziert. Auf dem ersten Registerblatt werden Sie einen Button im oberen Bereich, im Zentrum ein vorbelegtes Texteingabefeld (multizeilenfähig, was über die Anweisung jTextArea1.setLineWrap(true) realisiert wird) und im unteren Bereich ein einzeiliges Listenfeld mit vorangestelltem Label finden.
658
Java 2 Kompendium
Swing-Komponenten im Detail
Kapitel 11 Abbildung 11.21: Das erste Registerblatt wird angezeigt.
Sowohl der Button als auch das per Klick mit der rechten Maustaste auf den Nordbereich ausgelöste Popup-Menü erlauben die Übertragung des Textes im Texteingabefeld an das Label im zweiten Registerblatt. Dabei istdie Erzeugung eines Popup-Menüs recht einfach. Ein vorher erzeugter Eintrag wird mit der üblichen add()-Methode hinzugefügt (jPopupMenu1.add- (jMenuItem1)) und das Popup-Menü dem Panel oder der Komponenten, auf der es agieren soll, hinzugefügt. In unserem Beispiel ist das das jPanel3 (jPanel3.add(jPopupMenu1)). Mit etwas grö ß erem Aufwand verbunden ist die konkrete Methode, mit der auf Klicks des Popup-Menüs reagiert werden soll. Zwar kann recht unkompliziert die Übertragung des Textes aus der TextArea auf das Label auf dem anderem Tabellenblatt realisiert werden: void jMenuItem1_actionPerformed(ActionEvent e) { jLabel1.setText(jTextArea1.getText()); }
Jedoch ist es etwas Mühe, das Menü nur beim Klick mit der rechten Maustaste anzuzeigen (standardmä ß ig wird es auch beim Klick mit der linken Maustaste aktiviert). Das lässt sich aber mit dem der aufgerufenen Methode übergebenen Event-Objekt steuern (ebenso die Koordinaten des Popups). Das Objekt enthält Informationen über die gedrückte Maustaste, die mit Klassenkonstanten von Event verglichen werden können: void jPanel3_mouseClicked(MouseEvent e) { // Nur wenn rechte Maustaste gedrückt if(e.getModifiers()==Event.META_MASK) { // Zeige Popup in angegebener Komponente // an den Koordinaten, wo Klick erfolgte
Java 2 Kompendium
659
Kapitel 11
Swing & Co jPopupMenu1.show(jPanel3,e.getX(),e.getY()); } }
Die Übertragung des Textes von dem Textfeld mit dem Button erfolgt recht einfach: void jToggleButton1_actionPerformed(ActionEvent e) { jLabel1.setText(jTextArea1.getText()); }
Der Text im Eingabefeld kann frei geändert oder per Auswahl im unten stehenden Listenfeld vorgegeben werden. Das Listenfeld erhält seine Einträge über die Anweisungen der Form: jComboBox1.addItem(makeObj("Muster 1"));
Dabei erstellt die dort aufgerufene Methode makeObj() mit dem übergebenen String-Objekt über die Anweisungen return new Object() { public String toString() { return item; } };
Die beim Klick auf das Listenfeld ausgelöste Reaktionsmethode void jComboBox1_actionPerformed(ActionEvent e) unterscheidet mit der Methode getSelectedIndex(), welcher Eintrag ausgewählt wurde: if(jComboBox1.getSelectedIndex()==0) { jTextArea1.setText("Es grünt so grün"); } else if(jComboBox1.getSelectedIndex()==1) { jTextArea1.setText("Die Antwort ist 42"); } else if(jComboBox1.getSelectedIndex()==2) { jTextArea1.setText("Wie denn, wo denn, was denn?"); } else if(jComboBox1.getSelectedIndex()==3) { jTextArea1.setText("Was unterscheidet einen Porsche von einer Ente? Eine Ente ist cooler!"); } }
660
Java 2 Kompendium
Swing-Komponenten im Detail
Kapitel 11 Abbildung 11.22: Das Popup-Menü
Auf dem zweiten Registerblatt wird der übernommene Text angezeigt, wenn die Checkbox entsprechend selektiert ist. Dazu gibt es die Anweisung jCheckBox1.setSelected(true), um die Checkbox beim Laden zu selektieren. Die Reaktionsmethode, ob die Checkbox selektiert ist oder nicht, sieht so aus: void jCheckBox1_actionPerformed(ActionEvent e) { if(jCheckBox1.isSelected()) { jLabel1.setVisible(true); } else { jLabel1.setVisible(false); } }
Der Ende-Button erlaubt den Abbruch des Programms. Abbildung 11.23: Das zweite Registerblatt und der übertragene Text werden angezeigt.
Java 2 Kompendium
661
Kapitel 11
Swing & Co Ein Swing-Taschenrechner Das abschließende Beispiel ist eine etwas komplexere Anwendung – ein Taschenrechner. Hier kommen – neben der doch etwas komplexeren Funktionalität – einige Beeinflussungen von Komponenteneigenschaften hinzu. Beachten Sie bitte die Kommentare im Quelltext, die die einzelnen Schritte ausführlich erläutern.
Listing 11.13: Ein Swing-Taschenrechner als eigenständige Applikation
662
import java.awt.*; import java.awt.event.*; import javax.swing.*; class TaschenrechnerPanel extends JPanel implements ActionListener { private JTextField anzFeld; private double aktWerBer = 0; private String oprat = "="; private boolean begBer = true; public TaschenrechnerPanel() { // Definiere ein Borderlayout setLayout(new BorderLayout()); // Definiere anzFeld zur Ausgabe von // Tasteneingaben und berechnetem Ergebnis. Beim // Start soll es mit 0 vorbelegt sein. // Hintergrundfarbe ist default weiß, // Ausrichtung rechtsbündig. anzFeld = new JTextField("0"); anzFeld.setEditable(false); anzFeld.setHorizontalAlignment( JTextField.RIGHT); anzFeld.setBackground(java.awt.Color.white); // Anzeige im Nordbereich des Borderlayouts (oben) add(anzFeld, "North"); // Definiere ein neues JPanel, das als // Subpanel dem Hauptpanel hinzugefügt wird. // Es soll die Buttons des Tastenfeldes beinhalten. // Das Layout in ein // GridLayout mit 5 Zeilen und 4 Spalten. JPanel tastFeld = new JPanel(); tastFeld.setLayout(new GridLayout(5, 4)); // Hinzufügen der einzelnen Schaltflächen mit // einer selbst definierten Methode. // erste Reihe Tastenfeld fuegeBnHinzu(tastFeld, "C"); fuegeBnHinzu(tastFeld, "F"); fuegeBnHinzu(tastFeld, "Ende"); fuegeBnHinzu(tastFeld, "%"); // zweite Reihe Tastenfeld fuegeBnHinzu(tastFeld, "7"); fuegeBnHinzu(tastFeld, "8"); fuegeBnHinzu(tastFeld, "9"); fuegeBnHinzu(tastFeld, "/");
Java 2 Kompendium
Swing-Komponenten im Detail
Kapitel 11
// dritte Reihe Tastenfeld fuegeBnHinzu(tastFeld, "4"); fuegeBnHinzu(tastFeld, "5"); fuegeBnHinzu(tastFeld, "6"); fuegeBnHinzu(tastFeld, "*"); // vierte Reihe Tastenfeld fuegeBnHinzu(tastFeld, "1"); fuegeBnHinzu(tastFeld, "2"); fuegeBnHinzu(tastFeld, "3"); fuegeBnHinzu(tastFeld, "-"); // fünfte Reihe Tastenfeld fuegeBnHinzu(tastFeld, "0"); fuegeBnHinzu(tastFeld, "."); fuegeBnHinzu(tastFeld, "="); fuegeBnHinzu(tastFeld, "+"); // füge das Tastenfeld in den Haupt-Panel add(tastFeld, "Center"); } // Definition einer Methode, die einem // Container als Argument übergebene Schaltflächen // hinzufügt und beim ActionListener registriert. private void fuegeBnHinzu(Container uebergebenerContainer, String name) { // neuer Dummybutton JButton dummyButton = new JButton(name); // Einfügen uebergebenerContainer.add(dummyButton); // Registrieren dummyButton.addActionListener(this); } // Die Aktionsreaktionsmethode public void actionPerformed(ActionEvent evt) { String eingTast = evt.getActionCommand(); // Bei Betätigen der Ende-Taste Taschenrecher // beenden if (eingTast == "Ende") System.exit(0); // anzFeld auf 0 setzen, alle Operanden // Zurücksetzen und Beginn der Berechnung // aktivieren if (eingTast == "C") { aktWerBer = 0; anzFeld.setText("" + aktWerBer); eingTast = "="; begBer = true; } // anzFeld in der Farbe umsetzen if (eingTast == "F") { if (anzFeld.getBackground() == java.awt.Color.red) { anzFeld.setBackground(java.awt.Color.white); } else {
Java 2 Kompendium
663
Kapitel 11
Swing & Co anzFeld.setBackground(java.awt.Color.red); } } // Die %-Taste wird anders als die sonstigen // arithmetischen Operatoren behandelt. Grund: // nur ein vorangestellter Operand und nicht ein // dem Operator voran- und ein ihm nachgestellter // Operand. if (eingTast == "%") { aktWerBer = Double.parseDouble(anzFeld.getText()) / 100; anzFeld.setText("" + aktWerBer); } // Kontrolle, ob eine Zahl oder ein Dezimalpunkt // eingegeben wurde. Wenn ja, unterscheide, ob das // Anzeigefeld bisher noch keinen Wert beinhaltet // bzw. ob eine neue Berechnung gestartet wird // oder ob sich im Anzeigefeld bereits ein Eintrag // befindet, der zur Berechnung verwendet werden // soll. Falls nicht, schreibe den Wert der // ausgelesenen Eingabe-Taste direkt in das // Anzeigefeld. Ansonsten hänge ihn an den // bisherigen Wert an. if ('0' <= eingTast.charAt(0) && eingTast.charAt(0) <= '9' || eingTast.equals(".")) { if (begBer) anzFeld.setText(eingTast); else anzFeld.setText(anzFeld.getText() + eingTast); begBer = false; } // Das eingegebene Zeichen war weder Zahl noch // Dezimalpunkt else { // Ein als erstes Zeichen eingegebenes Minus darf // nicht mit dem Start der Subtraktion verwechselt // werden, sondern wird dem Rechenoperanden // vorangestellt. if (begBer) { if (eingTast.equals("-")) { anzFeld.setText(eingTast); begBer = false; } else { // erstes eingegebenes Zeichen ist ein Operator oprat = eingTast; } } // Eingabe eines Operators (+, –, *, /) else { // Extrahiere einen double-Wert aus dem // Anzeigefeld
664
Java 2 Kompendium
Swing-Komponenten im Detail
Kapitel 11
double x = Double.parseDouble(anzFeld.getText()); // rufe Berechnungsmethode auf berechne(x); oprat = eingTast; // neue Berechnung begBer = true; } } } public void berechne(double n) { if (oprat.equals("+")) aktWerBer = aktWerBer + n; else if (oprat.equals("-")) aktWerBer = aktWerBer – n; else if (oprat.equals("*")) aktWerBer = aktWerBer * n; else if (oprat.equals("/")) aktWerBer = aktWerBer / n; else if (oprat.equals("=")) aktWerBer = n; // Gebe Wert der Berechnung aus anzFeld.setText("" + aktWerBer); } } // Die Klasse, um ein Javafenster zu erzeugen class TaschenrechnerFrame extends JFrame { // Konstruktor public TaschenrechnerFrame() { setTitle("Ein Swing-Taschenrechner"); setSize(300, 320); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent e) { System.exit(0); } } ); getContentPane().add(new TaschenrechnerPanel()); } } public class Taschenrechner { public static void main(String[] args) { JFrame javaFenster = new TaschenrechnerFrame(); javaFenster.show(); } }
Wenn Sie die Tasten zu schnell bedienen (wirklich sehr schnell in einer ausreichend überlasteten Umgebung), kann es zu einem »Verschlucken« der Events kommen. Da im Eventmodell 1.1 nicht garantiert werden kann, in welcher Reihenfolge die Listener reagieren, kann das in Extremfällen zu einem Abarbeiten der Tastatureingaben in der falschen Reihenfolge führen. Ein solches Verhalten ist aber im Normalfall sehr unwahrscheinlich.
Java 2 Kompendium
665
Kapitel 11
Swing & Co
Abbildung 11.24: Ein Swing-Taschenrechner
Von besonderer Bedeutung in dem Beispiel ist der WindowListener. Wenn Sie für eine eigenständige Swing-Applikation die Schnittstelle public abstract interface WindowListener extends EventListener in eine Klasse implementieren, muss die Klasse sieben Methoden implementieren: public public public public public public public
void void void void void void void
windowOpened(WindowEvent e) windowClosing(WindowEvent e) windowClosed(WindowEvent e) windowIconified(WindowEvent e) windowDeiconified(WindowEvent e) windowActivated(WindowEvent e) windowDeactivated(WindowEvent e)
Von hauptsächlichem Interesse ist dabei die Methode windowClosing(), denn darin wird normalerweise die JVM heruntergefahren und das Programm beendet.
11.6
Zusammenfassung
Die Kommunikation mit dem Anwender wird in Java hauptsächlich über das AWT realisiert, aber Swing erweitert den Umfang erheblich. Dies betrifft einmal die Möglichkeit der Anpassung des Look and Feel an die zugrunde liegende Plattform bzw. an verschiedene Layouts, aber vor allem die Vielzahl an neuen Komponenten und Panels.
666
Java 2 Kompendium
12
Debugging und Ausnahmebehandlung
Kommen wir nun zu einem Kapitel, das für Sie im idealen Fall vollkommen überflüssig ist. Sie wundern sich über die Einleitung? Nun, »ideal« ist das Stichwort in dem Satz, aber wann läuft schon einmal etwas ideal? Überflüssig ist das Kapitel nämlich dann, wenn Sie niemals Fehler machen und ebenso Ihr Programm oder Applet so perfekt ist, dass dort niemals Fehler auftreten können (weder durch Fehler im Programm, noch durch die Plattform oder gar den Anwender). Wenn Sie in einer so perfekten Umgebung leben und arbeiten, dann haben Sie wahrscheinlich die Welt der Realität verlassen und sich in die Traumwelt begeben. Leider wird in der Realität das absolute Gegenteil der Fall sein. Es gibt da die berühmten Gesetze von Murphy, die im Wesentlichen besagen, dass alles, was im Prinzip schiefgehen kann, auch in der Tat schiefgehen wird. Oder noch genauer – egal, wie schlimm es ist, es wird noch schlimmer! Die Gesetze von Murphy sind zwar recht allgemein auf Naturwissenschaft und Technik bezogen, werden aber gerade in der Welt der Computerei zur Perfektion gebracht. Sie gipfeln in einigen (unverbindlichen) Erkenntnissen: Es gibt kein Programm ohne Fehler! Jede Korrektur eines Fehlers in einem Programm zieht mindestens zwei neue Fehler nach sich! Wichtige Programmfehler werden immer erst nach der Auslieferung bemerkt! Ein Programmierer bzw. das gesamte verantwortliche Team wird immer die augenscheinlichen Fehler übersehen! Dazu gibt es eine Erweiterung, die wie folgt lautet: Jeder Außenstehende, der ungefragt ein Programm ansieht, bemerkt die Fehler sofort! Da aber Besserwisserei nirgends gemocht wird, wird dessen Meinung bewusst ignoriert. Jedes Programm wird soweit optimiert, bis es unbrauchbar ist! Jedes zur Unbrauchbarkeit optimierte Programm lässt sich unmöglich in den Zustand zurückversetzen, in dem es – noch nicht optimiert – lauffähig war! Man kann diese Aussagen (die auf unumstößlichen Erfahrungen beruhen) noch beliebig erweitern, aber Sie merken schon, um was es geht: Fehler und Java 2 Kompendium
667
Kapitel 12
Debugging und Ausnahmebehandlung deren Vermeidung (unmöglich) oder zumindest Reduzierung (sowas geht schon eher)! Und für die Fälle, wo dennoch Fehler oder Ausnahmesituationen eintreten, müssen wir unser Programm so instruieren, dass es vernünftig (vor allem nicht mit einem Absturz) reagiert. Wir müssen uns dabei mit verschiedenen Situationen auseinander setzen: 1.
Typografische Fehler beim Schreiben des Programms
2.
Syntaktische Fehler beim Schreiben des Programms
3.
Programmfehler zur Laufzeit, die auf logische Fehler im Programmaufbau zurückzuführen sind
4.
Programmfehler zur Laufzeit, die auf äußere Umstände (Situationen, die erst zur Laufzeit entstehen, die Umgebung des Programms oder den Anwender) zurückzuführen sind
Die Beseitigung der Fehlerpunkte 1-3 sind hauptsächlich das, was man unter Debugging versteht. Punkt 4 zählt teilweise ebenfalls dazu, jedoch sollte im Wesentlichen bereits vor der Entstehung eines Fehlers in extra »Fangzäunen« aufgefangen werden – mit der so genannten Ausnahmebehandlung.
12.1
Debugging
Der Begriff Debugging geht auf das englische Wort Bug zurück, was übersetzt »Wanze« bedeutet. Es gibt einige Anekdoten, warum die Fehlerbereinigung bei einem Programm Debugging genannt wird. Sie bekannteste Anekdote hat zum Inhalt, dass in Zeiten seliger Röhrengroßrechner (Vierzigerjahre) ein Programmfehler die damaligen Entwickler zur Verzweiflung getrieben haben soll. Der Fehler zur Laufzeit war absolut unlogisch, da im Quelltext kein Fehler zu finden war. Die Lösung fand sich erst, als man den Rechner selbst auseinander schraubte und zwischen den Röhren eine tote Wanze fand, die mit ihrem Körper Schaltkreise störte. So viel zur Namengebung – schauen wir uns Maßnahmen zu Fehlerbehandlung an.
12.1.1
Fehler im Vorfeld abfangen
Es ist banal, aber dennoch die wirkungsvollste Maßnahme – versuchen Sie, durch sorgfältige Vorbereitung (Programmplanung, Bereitlegung von Nachschlagewerken und genügend Infomaterial, vernünftige Entwicklungswerkzeuge (etwa ein Editor mit farblicher Unterscheidung von Schlüsselworten und Quelltextstrukturen – leider nicht im JDK vorhanden) und vor allem sorgfältige und konzentrierte Eingabe Fehler (vor allem Flüchtigkeitsfehler) 668
Java 2 Kompendium
Debugging
Kapitel 12
erst gar nicht entstehen zu lassen. Aber auch ein durchdachtes Konzept und genügend Programmiererfahrung werden die Anzahl der potenziellen Fehler in einem Programm bereits im Vorfeld reduzieren. So werden erfahrene Programmierer beispielsweise bei Divisionen immer misstrauisch und stellen sicher, dass der Teiler nie den Wert 0 annehmen kann (dazu gleich mehr bei einem Beispiel).
12.1.2
Vernünftige Sicherungsmaßnahmen
Nehmen wir uns noch einmal die (ironischen) Aussagen vor: Jedes Programm wird soweit optimiert, bis es unbrauchbar ist! Jedes zur Unbrauchbarkeit optimierte Programm lässt sich unmöglich in den Zustand zurückversetzen, wo es – noch nicht optimiert – lauffähig war! Es gibt einen ernsten Hintergrund. Oft kommt man in die Situation, dass ein Programm läuft, aber noch einige Dinge erweitert oder verändert werden müssen. Man nimmt eine Änderung oder mehrere Änderungen in einem Schritt vor, und das Programm hat einen Fehler. Leider ist es so, dass man sich normalerweise nicht jede Änderung merkt (obwohl dies – oder noch besser eine Dokumentation – sinnvoll wäre). Oft kann deshalb diese Veränderung dann nicht mehr rückgängig gemacht werden. Hat man dann den letzten lauffähigen Stand nicht gesichert (Source!), bleibt oft nur ein aufwändiges Try-and-Error-Verfahren um etwas wiederherzustellen, was man schon einmal hatte.
12.1.3
Fehler finden und beseitigen
Wenn nun aber dennoch ein Fehler aufgetreten ist und sich der fehlerfreie Zustand nicht durch einfaches Laden wiederherstellen lassen kann (oder soll, falls aufwändige Veränderungen stattgefunden haben), bleibt nur die Fehlerbeseitung. Dabei stellt sich zuerst das Problem, den Fehler überhaupt zu lokalisieren. Dies ist der grö ß te und wichtigste Teil dessen, was Debugging genannt wird – das Lokalisieren und Identifizieren eines Fehlers. Diese »Wanzenjagd« kann in einem Softwareprojekt bis zu 40 % und mehr der Entwicklungszeit in Anspruch nehmen. Dies hängt von der Art des zu entwickelnden Programms, der Grö ß e und Komplexität des Projekts, der Anzahl der beteiligten Personen (je mehr Personen, desto mehr Fehlerquellen alleine durch Missverständnisse) und nicht zuletzt von der verwendeten Programmiersprache ab. Wir wissen ja bereits, dass es Programmiersprachen gibt, die äußerst flexibel sind, aber damit Fehler extrem großzügig einladen (unser beliebtes Beispiel C/C++). Dort können mit fehlgeleiteten Pointern, Zeigerarithmetik, Array- und sonstigen Indexüberschreitungen, ungeprüfter Typumwandlung, Speicherallokation und anschließender Java 2 Kompendium
669
Kapitel 12
Debugging und Ausnahmebehandlung manueller Freigabe oder ähnlichen Techniken so viele Wanzen in ein Programm einziehen, dass sich die Zeit für deren vollständige Beseitigung gegen unendlich geht und in der Praxis nie realisiert wird (oder kennen Sie etwa keine Schreibschutzverletzung von Windows-Programmen?). Andere Programmiersprachen wie Visual Basic sind halbwegs fehlersicher, da sie systemfern auf dem Ring 3 des Prozessors arbeiten und nicht so gravierende Fehler durch den Programmierer zulassen. Java steht am oberen Ende der Fehlersicherheit durch den Verzicht auf viele extreme Systembefehle und diverse weitere Sicherungsmaßnahmen (wir sind bereits darauf eingegangen oder werden sie im Rahmen der Java-Sicherheit noch explizit behandeln). Dennoch können sogar Java-Programme Fehler enthalten. Manche Fehler kann ein Programmierer sogar grundsätzlich nicht verhindern. Die Behandlung dieser Fehler werden wir gleich anschauen, zuerst wollen wir sie suchen. Sie können mit oder ohne Hilfsmittel auf die Wanzenjagd gehen. Beide Verfahren haben Vor- und Nachteile.
12.1.4
Fehlerlokalisierung ohne Debugger
Zunächst schauen wir uns an, welche Maßnahmen Sie ergreifen können, ohne auf weitergehende Hilfsmittel – einen Debugger- zurückzugreifen. Dieses Verfahren hat die wesentlichen Vorteile, dass sie neben dem Compiler kein zusätzliches Programm benötigen und sich vor allem nicht in dessen Bedienung einarbeiten müssen. Außerdem ist die Arbeit ohne zusätzlichen Debugger manchmal schneller und auch einfacher. Wir unterteilen die Problemstellung nach den Typen möglicher Fehler, die wir oben aufgelistet haben. Da sind einmal die typografischen Fehler beim Schreiben des Programms. Sie haben sich einfach vertippt oder ein Schlüsselwort, eine Variable oder sonst etwas Entscheidendes falsch geschrieben. Nun, sofern Sie es richtig falsch geschrieben haben, ist die Situation oft leicht in der Griff zu bekommen. Aber was soll das heißen – richtig falsch? Richtig oder falsch? Was denn nun? Es soll bedeuten, dass Sie nicht zufällig einen solchen Schreibfehler gemacht haben, sodass der falsche Begriff wieder einen sinnvollen Token ergibt und keinen syntaktischen Widerspruch darstellt. Nehmen wir ein Beispiel aus der Textverarbeitung. Sie wollten beispielsweise Salz schreiben und haben Satz getippt – einen ebenfalls sinnvollen Token. In diesem Fall haben Sie ohne Hilfsmittel nur die Chance, den Fehler im Quelltext durch aufmerksames Lesen oder Analyse des Resultates von dem Programm zur Laufzeit zu finden.
670
Java 2 Kompendium
Debugging
Kapitel 12
Wenn Sie jedoch einen Schreibfehler gemacht haben, der keinen vernünftigen Token ergibt, sind Sie in der besseren Situation. Der Token wird einen syntaktischen Widerspruch innerhalb der Programmiersprache erzeugen und der Compiler wird bei seiner Arbeit den Fehler entdecken, den Quelltext nicht übersetzen und eine Fehlermeldung mit Adressangabe zurückbringen. Falls Sie direkt an dieser Adressangabe den Fehler finden, haben Sie allerdings Glück, denn leider ist diese Adressangabe der Wirkungsort des Fehlers und meist nicht der Entstehungsort. Dies muss nicht immer identisch sein. Aber zumindest ist der Fehler schon einmal eingegrenzt. Der zweite Fall sind die syntaktischen Fehler beim Schreiben des Programms. Dies sind Fehler bei der Verwendung der Programmiersprachensyntax. Der Compiler wird dies beim Übersetzen merken und eine Fehlermeldung zurückliefern. Viele syntaktische Fehler beruhen auf typografischen Fehlern (etwa, wenn Sie buplic statt public schreiben). Dieser Fehlertyp beinhaltet auch die falsche Verwendung von korrekten Token (z.B. for (i==0; i < 256;i++) – ein Vergleich statt einer Zuweisung der Zählvariable). Leider gilt auch hier, dass nicht unbedingt die Adressangabe des Fehlers der Entstehungsort des Fehlers sein muss. Der dritte Fall sind Programmfehler zur Laufzeit, die auf logische Fehler im Programmaufbau zurückzuführen sind. Hierbei haben Sie ein syntaktisch vollkommen korrektes Programm geschrieben, das aber Fehler zur Laufzeit zulässt. Dies geschieht meist, weil eine Variable einen falschen Wert bekommt. Beispielsweise setzt irgendeine Programmsituation eine Variable auf 0 und im Folgeschritt wollen Sie durch diese Variable teilen. Sie müssen zur Beseitigung der Fehlersituation die Stelle im Quelltext suchen, an der diese Variable (Methode usw.) verwendet wird und versuchen, den Aufbau und mögliche Fehler noch einmal durchzudenken. Kontrollausgaben, die im endgültigen Source beseitigt werden, erleichtern dies erheblich. Gängige Praxis ist, vor einem vermuteten Fehler eine Bildschirmausgabe der »Wanze« (also der Variable, in der man die Fehlerursache vermutet) zu erzeugen. Dies könnte so aussehen (die Variable Teiler ist die vermutete Wanze): System.out.println(Teiler);// Debug-Ausgabe a=b/Teiler;// vermutete Fehlerstelle im Source
Vor der Entstehung des Laufzeitfehlers sehen Sie auf dem Bildschirm den Wert der Variablen. Sie können selbstverständlich an den unterschiedlichsten Stellen im Programm solche Debug-Ausgaben einfügen, um eine Variable über mehrere Schritte hinweg zu verfolgen. Oft sinnvoll ist ebenso, den Programmfluss direkt vor dem vermuteten Fehler anzuhalten, also eine Schleife einfügen, die auf einen Tastendruck wartet, bis sie beendet wird und das Programm weiter läuft.
Java 2 Kompendium
671
Kapitel 12
Debugging und Ausnahmebehandlung Die Grenzen dieser Analyse ohne einen Debugger sind dann erreicht, wenn tiefergehende Informationen (etwa über den Stack bei Programmüberläufen oder den Speicherbedarf von Programmen) ausgewertet werden sollen. Der vierte Fall sind Programmfehler zur Laufzeit, die auf äußere Umstände (Umgebung des Programms oder den Anwender) zurückzuführen sind. Diese Fehlerkonstellation kann durch verschiedene Dinge entstehen. Sie greifen etwa im Programm hardcodiert auf eine bestimmte Schriftart zu, die auf der Plattform des Anwenders nicht vorhanden ist. Oder das Programm lässt Bedienfehler durch den Anwender zu (etwa Speicherversuch auf ein Diskettenlaufwerk, obwohl keine Diskette eingelegt ist). Es handelt sich also um Fehler, die mehr auf konzeptioneller Seite zu suchen sind, und weniger um technische Fehler. Aber auch hier sind die unter Punkt 3 angeführten Maßnahmen zu einer Analyse (mit sehr engen Grenzen) sinnvoll. Die beiden Fehlerpunkte 3 und 4 sind das, was man in der Literatur als Laufzeitfehler bezeichnet. Es handelt sich also um einen Fehler, den ein Compiler zum Zeitpunkt der Übersetzung nicht erkennen kann (etwa eine Division durch 0 zur Laufzeit) und auf den ein Programmierer unter Umständen nur bedingt Einfluss hat (etwa bei fehlender Diskette im Laufwerk). Einige Fehlerkonstellationen kann man recht gut umgehen, andere muss man zulassen, weil man darauf keinen Einfluss hat (fehlende Diskette), wieder andere Laufzeitfehler gelten als nicht auffangbar (so genannte untrappable Errors oder oft auch nur als Errors bezeichnet), da sie durch die Hardware verursacht werden und nicht durch Software aufgefangen werden können. Auf jeden Fall sollten alle denkbaren und technisch auffangbaren Fehler irgendwie behandelt werden, denn in der Regel führt ein unbehandelter Laufzeitfehler zu einem Programmabsturz und dabei gehen die evtl. in dem Programm erfassten und noch nicht gespeicherten Daten verloren. Zumindest ist das neue Starten eines Programms lästig. Das Abfangen von Laufzeitfehlern in Java werden wir in Kürze angehen (Ausnahmebehandlung). Wenden wir uns vorher dem Auffinden von Fehlern mit einem Debugger zu.
12.1.5
Fehlerlokalisierung mit Debugger
Sie haben an den eben beschriebenen Maßnahmen gesehen, dass man auch ohne Hilfsmittel Fehler unter Umständen lokalisieren kann. In vielen Fällen stö ß t man jedoch dabei an Grenzen. Für eine tiefergehende Analyse und komfortablere Suche (ohne Programmierung von eigenen Debug-Ausgaben) verwendet man einen Debugger, wie er etwa bei den JDK-Werkzeugen im Anhang beschrieben wird. Wenn Sie sich dort die erlaubten Befehle an den Debugger anschauen, erkennen Sie schon, was man damit machen kann. Ein Debugger ist kein Hilfsmittel, das logische Fehler in einem Programm anzeigt, sondern eine Unterstützung für Sie, diese selbst zu finden. Manche Debugger sind Teil einer IDE (also einer so genannten integrierten Umge672
Java 2 Kompendium
Debugging
Kapitel 12
bung) und können die Fenstertechnik und die Maus nutzen. Diese sind natürlich am komfortabelsten. Andere müssen sich als Teil eines Programmaufrufs quasi an diesen mit anhängen – so wie der Debugger des JDK. Diese können dann auch nur im Ausgabefenster des Systems Informationen zurückgeben. Dies ist zwar unkomfortabel, aber dennoch sehr brauchbar. Im Anhang bei der Beschreibung der erweiterten JDK-Tools finden Sie dazu eine Beschreibung des JDKProgramms samt der grundsätzlichen Schritte zur Arbeit damit). Die Arbeit mit einem Debugger lässt sich auf einige wesentliche Schritte zusammenfassen: 1.
Einen Haltepunkt (Breakpoint) setzen
2.
Einen Ausdruck analysieren
3.
Ein Programm in einzelnen Schritten durchlaufen (Steppen)
4.
Ein Programm in grö ß eren Schrittgruppen (Methodenaufrufen) durchlaufen
5.
Ein Programm bei bestimmten Bedingungen anhalten lassen
6. Tiefergehende Informationen über den Programmzustand analysieren Setzen wir uns Punkt für Punkt mit den Techniken auseinander. Einen Haltepunkt setzen bedeutet, ein Programm an einer beliebigen Stelle zu unterbrechen. Wir haben bereits oben angedeutet, wie dies mit zusätzlich programmierten Debug-Code möglich ist. In einem Debugger ist dies ohne zusätzlich programmierten Debug-Code durchführbar. Sie können an beliebigen (im Source sinnvollen) Stellen einen Haltepunkt setzen, ihn wieder löschen und einen Ausdruck im Source an dem Zeitpunkt analysieren, an dem das Programm bei Erreichen des Haltepunktes angekommen ist. Daneben werden Sie in gewissen Grenzen Änderungen in dem unterbrochenen Programm vornehmen können (etwa den Wert einer Variable von Hand ändern). Kommen wir zu Punkt zwei. Wenn Sie einen Ausdruck analysieren wollen, können Sie über zusätzlichen Debug-Code eine Ausgabe auf den Bildschirm erzeugen. Ein Debugger lässt so ein Hineinschauen in einen Ausdruck auch ohne zusätzlichen Debug-Code zu. Sie geben den Ausdruck, den Sie analysieren wollen, im Debugger an und der Debugger zeigt Ihnen dessen Wert an. Zu welchem Zeitpunkt dies im Programmablauf geschieht, hängt davon ab, wann die Ausgabe erfolgen soll. Entweder, nachdem Sie Ihr Programm angehalten haben (Punkt 1 oder 5), oder sogar permanent an vorgegebenen Stellen, wenn Sie durch Ihr Programm steppen (Punkt 3 und 4).
Java 2 Kompendium
673
Kapitel 12
Debugging und Ausnahmebehandlung Wenn Sie ein Programm in einzelnen Schritten durchlaufen (Steppen), wird der Programmablauf nach jedem einzelnen Schritt des Programms unterbrochen, und Sie können an dieser Stelle Ihre Analysen durchführen. Danach wird einen Schritt weitergegangen und ggf. wieder analysiert. Wenn Sie durch ein großes Programm von Anfang bis Ende steppen, wird es ziemlich aufwändig. Das ist praxisfern. Man nutzt diese Technik aber oft im Zusammenhang mit einem Haltepunkt. Kurz vor einer kritischen Stelle setzen Sie einen Haltepunkt und ab diesem steppen Sie dann schrittweise weiter, bis der Fehler auftritt. Ein Programm in grö ß eren Schrittgruppen (Methodenaufrufen) zu durchlaufen dient zur groben Lokalisierung eines Fehlers (wir sind bei Punkt vier). Wenn man die Anweisung entdeckt hat, die den Fehler produziert, setzt man vor deren Aufruf einen Haltepunkt, startet den Debug-Vorgang erneut und steppt ab dem Haltepunkt in Einzelschritten weiter. Wenn Sie ein Programm bei bestimmten Bedingungen anhalten lassen, bedeutet dies, dass das Programm so lange normal arbeitet, bis die angegebene Bedingung eingetreten ist (etwa eine bestimmte Ausnahme ausgeworfen wird). Dann hält das Programm an und Sie können die Situation analysieren. Ein Java-Debugger muss bei bestimmten Ausnahmen die Programmausführung unterbrechen und Ihnen die Möglichkeit zur Analyse geben (dies kann der JDK-Debugger natürlich auch). Der sechste Punkt betrifft das Auswerten von tiefergehenden Informationen über einen Programmzustand. Gute Debugger erlauben die Verfolgung von einzelnen Threads in einem Programm, Analysen von Speicherzuständen, Klassen- und Methodenaufrufen oder dem Stack. Je nach Qualität des Debuggers kann man sich da alles mögliche vorstellen, was beim Ablauf eines Programms von Interesse sein könnte. Wir wollen nun die Problemstellung erneut nach den möglichen Fehlertypen unterteilen. Dabei schauen wir uns an, wie man mit einem Debugger darauf reagieren kann. 1.
674
Bei typografischen Fehlern hilft Ihnen der Debugger nur begrenzt, da Sie zur Auswertung eines Ausdrucks dessen konkreten Namen wissen sollten und der ist ja falsch. Allerdings hilft das Steppen durch den Quelltext recht gut, um diese typografischen Fehler zu finden, sofern der Debugger eine Ausführung eines fehlerhaften Programms (bis zum Crash) unterstützt. Vor allem, wenn Sie vom Compiler eine Fehlermeldung mit Adressangabe bekommen. Dann setzen Sie in ausreichendem Abstand davor einen Haltepunkt und steppen durch oder der Debugger kann direkt die Fehlermeldung des Compilers nutzen. Wenn Sie einen richtig falschen Token verwenden und Sie – ohne den richtigen Fehler
Java 2 Kompendium
Ausnahmebehandlung und andere Fehlerbehandlungen
Kapitel 12
zu bemerken – diesen Ausdruck analysieren, wird Ihnen auffallen, dass der Ausdruck nie eine Wertveränderung erfährt, obwohl dies so sein müsste. 2.
Bei syntaktischen Fehlern gilt das Gleiche wie unter Punkt 1.
3.
Hier schlägt nun die Stunde des Debuggers – Programmfehler zur Laufzeit, die auf logische Fehler im Programmaufbau zurückzuführen sind, Haltepunkte setzen und wieder beseitigen, Steppen, Wertanalysen, Wertveränderungen, einen Ausdruck über mehrere Schritte hinweg zu verfolgen – alles, was zur Fehlerlokalisierung nötig ist, sollte ein Debugger leisten. Aber auch tiefergehende Informationen lassen sich von guten Debuggern auswerten und teilweise verändern. Wenn Sie sich die Kommandos an den JDK-Debugger jdb anschauen, werden Sie Befehle entdecken, mit denen Sie den Stack und den Speicherbedarf eines Programms, aber auch einzelne Threads oder Ausnahmesituationen analysieren und teilweise manipulieren können. Sie können sogar im Falle von Multithreading einzelne Threads oder Threadgruppen gezielt behandeln. Mit einem Debugger bekommen Sie ebenfalls die Namen und IDs der Klassen, die bereits geladen worden sind, können ganz gezielt Klassen laden und Methoden anzeigen oder erhalten Informationen über alle Felder (Werte der Variablen) einer Klasse oder der Instanz. Sie können in Java-Debuggern ebenso die direkte Ausführung des Garbage Collectors erzwingen.
4.
Programmfehlern zur Laufzeit, die auf äußere Umstände zurückzuführen sind, kann mit einem Debugger begrenzt vorgebeugt werden. Durch die Simulation solcher Fehler – Wertveränderungen von kritischen Ausdrücken im Debug-Modus nach einer Programmunterbrechnung – kann die Reaktion des Programms getestet werden. Anschießend lassen sich Gegenmaßnahmen ergreifen. Der Debugger unterstützt Sie also auch bei der Vermeidung von Fehlern, die mehr auf konzeptioneller Seite zu suchen sind. Diese Hilfe ist aber begrenzt, da Sie so gut wie nie alle denkbaren Fehlerkonstellationen im Vorfeld durchspielen können. Die technisch auffangbaren Laufzeitfehler in einem Programm sollten auch tatsächlich aufgefangen werden. Java realisiert dies im Wesentlichen durch die Ausnahmebehandlung.
12.2
Ausnahmebehandlung und andere Fehlerbehandlungen
Die Ausnahmebehandlung und auch die anderen Techniken zur Fehlerbehandlung in diesem Abschnitt betreffen ausschließlich die auffangbaren Laufzeitfehler (trappable Errors), also Fehler, die bei der Übersetzung eines Programms nicht entdeckt werden können und erst unter bestimmten Konstellationen während der Laufzeit des Programms auftreten. Fehler, die durch höhere Gewalt (Hardwarefehler, Stromausfall, mechanische Gewalt Java 2 Kompendium
675
Kapitel 12
Debugging und Ausnahmebehandlung durch einen Wutanfall des Anwenders) ausgelöst werden (die bereits angesprochenen untrappable Errors), können naturbedingt nicht abgefangen werden. Sie haben zwei grundsätzlich verschiedene Möglichkeiten zur Fehlerbehandlung: Die individuell programmierte Fehlerbehandlung Die von Java bereits zur Verfügung gestellte Fehlerbehandlung – die Ausnahmen
12.2.1
Die individuell programmierte Fehlerbehandlung
In vielen Programmiersprachen bleibt Ihnen zur Behandlung von Laufzeitfehlern kein anderer Weg, als jeden denkbaren Fehler an der Stelle zu behandeln, wo er auftreten kann. Wenn wir uns noch einmal das Beispiel von der Division durch eine Variable mit dem Wert 0 anschauen, können wir dort eine solche Fehlerbehandlung durchführen. Der einzig mögliche Fehler an dieser Stelle ist, dass die Variable den Wert 0 zugewiesen bekommt. Also müssen wir für diesen Fall vorsorgen. Dies kann mit den traditionellen Entscheidungsstrukturen (while, do, if) recht einfach erfolgen. Wir setzen die kritische Aktion – die Division durch die Variable – in eine Art Schutzanzug, indem wir eine Entscheidungsstruktur vorschalten, die den Fehler erst gar nicht zulässt. Das kann so aussehen: if (Teiler == 0) { // tue etwas, etwa eine Fehlermeldung mit anschließendem // Sprung an eine unkritische Stelle oder setze die // Variable auf einen unkritischen Wert (sofern sinnvoll) // wie hier Teiler = 1; } a=b/Teiler;
Wir haben hier einfach den kritischen Kanditaten (den Divisor) für den potenziellen Fehlerfall auf einen unkritischen Wert gesetzt, aber bereits in den Kommentarzeilen angedeutet, dass eine Sprunganweisung ebenfalls sinnvoll wäre. Und nicht nur das – fast jede Fehlerbehandlungsroutine wird normalerweise als Sprunganweisung konzipiert. Im Falle eines Fehlers soll an eine Stelle gesprungen werden, wo der Fehler behandelt wird. Wir haben dies aber absichtlich hier noch nicht in Java umgesetzt, da Java dafür einen Standardmechanismus zur Verfügung stellt, den wir gleich durchsprechen. Das Prinzip und die Struktur können Sie aber im Hinterkopf behalten. Sie können auf diese Art jeden Fehler auffangen, den Sie als potenzielle Fehlerquelle erkennen. Und da haben wir den entscheidenden Schwachpunkt – Sie müssen die potenzielle Fehlerquelle erkennen, was oft nahezu unmöglich 676
Java 2 Kompendium
Ausnahmebehandlung und andere Fehlerbehandlungen
Kapitel 12
ist. Gerade in komplexen Programmen können Sie nie alle kritischen Zusammenhänge (auch auf Grund von Verschachtelungen) übersehen. Es werden immer Fehlerquellen existieren, die an vorher kaum beachteten Stellen auftreten. Außerdem ist es sehr viel Arbeit, bei jeder Anweisung zu überlegen, ob da eine Gefahr lauert und diese dann sicher abzufangen. Es ist sogar so, dass Sie in der überwiegenden Anzahl von Fehlern das Rad neu erfinden, denn es gibt sehr oft schon Standardmaßnahmen. Zu guter Letzt setzt diese Technik der Lauffehlerbehandlung oft voraus, dass der Fehler selbst nicht eintritt, sondern vorher erkannt und umgebogen wird. Zwar kann man in Java diese Fehlerbehandlungstechnik durchaus verwenden und sie ist in Fällen wie in unserem Beispiel auch sicher sinnvoll. Java stellt jedoch einen Mechanismus zur Verfügung, der viel mächtiger und dennoch mit weniger Aufwand verbunden ist – die Ausnahmebehandlung. Sie erlaubt sowohl eine Fehlerbehandlung »vor Ort« als auch eine globale Fehlerbehandlung an einer zentralen Stelle im Programm, an die Ausnahmen »weitergereicht« werden. Dabei ist von besonderer Bedeutung, dass das JavaKonzept ein Programm bei einer solchen Ausnahmesituation dennoch stabil weiterlaufen lässt, zumindest, wenn es einen weiteren sinnvollen Ablauf gibt.
12.2.2
Die Ausnahmebehandlung von Java
Wir sind schon an verschiedenen Stellen darauf eingegangen, dass Java zur Behandlung von Fehlern (zumindest den auffangbaren) einen sehr mächtigen Mechanismus zur Verfügung stellt – die Ausnahmen oder Exceptions. Mittels dieses Ausnahmekonzepts kann Java eines seiner Grundprinzipien – die maximale Sicherheit – gewährleisten. Eine Ausnahme ist eine Unterbrechung des normalen Programmauflaufs auf Grund einer besonderen Situation, die eine isolierte und unverzügliche Behandlung notwendig macht. Der normale Programmablauf wird erst fortgesetzt, wenn diese Ausnahme behandelt wurde. Ein klassisches Beispiel, das eine solche Situation provoziert, ist der bereits angedeutete Versuch, auf ein Diskettenlaufwerk zuzugreifen, in dem keine Diskette eingelegt ist. Eine Java-Klasse, die diesen Mechanismus zur Verfügung stellt, sollte vor einem Zugriff auf ein Diskettenlaufwerk überprüfen, ob eine Diskette eingelegt ist und andernfalls mit einer Ausnahme auf den Versuch reagieren. Diese Ausnahme gibt Informationen darüber zurück, welcher Fehler genau vorliegt und auf Grund dieser Information kann der Programmierer Gegenmaßnahmen ergreifen. Eine Ausnahme ist in Java ein eigenes Objekt, entweder eine Instanz der Klasse Throwable oder einer seiner Unterklassen. Dies erlaubt es Programmierern, leicht eigene Ausnahmen zu definieren, indem einfach eine Unterklasse der Klasse Throwable oder einer bereits vorhandenen Ausnahme abgeleitet wird. Eine so als Subklasse erstellte Ausnahmeklasse oder eine Standardausnahmeklasse wird von Methoden mit der Anweisung throw ausJava 2 Kompendium
677
Kapitel 12
Debugging und Ausnahmebehandlung geworfen. Das ist eine Sprunganweisung mit Rückgabewert – eben das Ausnahme-Objekt. Verwandt ist die Situation mit einer return-Ausweisung. Eine Methode, die eine Ausnahme auswirft, muss das in einer Erweiterung der Methodenunterschrift dokumentieren, um dies dem Aufrufer der Methode kenntlich zu machen. Dies geschieht mit dem Schlüsselwort throws, was nicht mit throw verwechselt werden darf. Das ganze Verfahren werden wir noch genauer ansprechen – mit Beispiel. Ein halbwegs professionelles Java-Programm sollte flexibel auf Benutzerfehler und andere Probleme reagieren können und die erzeugten Ausnahmen mit entsprechenden Fehlerbehandlungs-Routinen behandeln. Da Java einen Mechanismus zur standardisierten Erzeugung von Ausnahmen bereitstellt, ist es naheliegend, dass es auch einen standardisierten Mechanismus zu deren Auswertung und Behandlung besitzt. Man muss dabei zwei Szenarien unterscheiden. Globales Exception-Handling Java realisiert wie gesagt das globale Handling von Ausnahmen über die Erweiterung einer Methodendeklaration um die throws-Anweisung zur Dokumentation und die throw-Anweisung zum konkreten Auswerfen. Damit wird eine Liste mit Ausnahmen dokumentiert, die die Bewältigung von diversen Laufzeitproblemen ermöglicht. Wenn eine Methode irgendetwas tun kann, was zu Fehlern oder kritischen Situationen führen kann, kann man dies normalerweise in einer Dokumentation niederlegen. Wer die Methode verwenden will, muss sicherstellen, dass die kritischen Fälle nicht eintreten. Wenn beispielsweise eine Methode eine Division durch 0 zulässt, muss derjenige, der eine Eingabemaske erstellt, die diese Methode aufruft, sicherstellen, dass keine Werte eingegeben werden, die dazu führen können. Das System selbst kann dies nicht sicherstellen, da es sich bis zum Zeitpunkt der kritischen Wertekonstellation um keinen Fehler handelt. Wir haben diese individuelle Fehlerbehandlung ohne Ausnahme-Technik eben als Beispiel behandelt. Das Fehler-Handling ohne Ausnahmen ist ein in vielen Systemen übliches Verfahren, aber sicher nicht optimal. Java bietet das bessere Verfahren – eben die throws-Klausel (throw – auswerfen). Beispiel: public class MeineErsteKlasse_mitExceptions { public void eineMethode_mitExceptions() throws MeineException { ... } }
678
Java 2 Kompendium
Ausnahmebehandlung und andere Fehlerbehandlungen
Kapitel 12
Hiermit wird dem Compiler mitgeteilt, dass der in der Methode enthaltene Code eine Ausnahme (MeineException) erzeugen kann. Damit weiß der Compiler, dass die Methode gefährliche Dinge tun kann. Er wird entsprechend reagieren, d.h. er wird diese Methode nur dann übersetzen, wenn die ausgeworfene Ausnahme an geeigneter Stelle im Programm behandelt wird. Diese Beschreibung der Gefahr ist eine Art Vertrag zwischen der Methode und demjenigen, der die Methode verwendet. Die Beschreibung enthält Angaben über die Datentypen der Argumente einer Methode und ihre allgemeine Semantik. Außerdem werden die gefährlichen Dinge, die bei der Methode auftreten können, spezifiziert. Als Anwender der Methode müssen Sie sich darauf verlassen, dass diese Angaben die Methode korrekt charakterisieren. An Stellen, wo Sie die Methode verwenden, können Sie die Ausnahmebedingungen dann explizit bezeichnen. Wenn Sie also bei einer Ihrer Methoden wissen, dass diese bestimmte Fehler- oder Ausnahmesituationen auslösen kann, müssen Sie diese entweder selbst abfangen (etwa wie wir oben durch eine Entscheidungsstruktur sichergestellt haben, dass ein Teiler nicht 0 werden kann oder wie wir gleich in der expliziten Fehlerbehandlung durchsprechen) oder eben potenziellen Aufrufern mittels der throws-Erweiterung diese Fehler- oder Ausnahmesituationen der aufrufenden Stelle mitteilen. Wenn nun in einer mit der throws-Erweiterung gekennzeichneten Methode eine Ausnahme auftritt, was passiert dann? In diesem Fall wird die aufrufende Methode nach einer Ausnahmebehandlungsmethode durchsucht (wie die aussieht, folgt gleich). Enthält die aufrufende Methode eine Ausnahmebehandlungsmethode, wird mit dieser die Ausnahme bearbeitet, ist dort keine Routine vorhanden, wird deren aufrufende Methode duchsucht und so fort. Das Spiel geht so lange weiter, bis eine Ausnahmebehandlungsmethode gefunden ist oder die oberste Ebene des Programms erreicht ist. Die Ausnahme wird von der Hierarchieebene, wo sie aufgetreten ist, jeweils eine Hierarchieebene weiter nach oben gereicht. Sofern sie nirgends aufgefangen wird, bricht der Java-Interpreter normalerweise die Ausführung des Programms ab. Dieses Weiterreichen der Behandlung über verschiedene Hierarchieebenen erlaubt sowohl die unmittelbare Behandlung ganz nahe am Ort des Problems als auch eine entferntere Behandlung, wenn dies sinnvoll ist – etwa in einer mehrere potenzielle Probleme umgebenden Struktur. Natürlich müssen nicht alle denkbaren Fehler und Ausnahmen explizit aufgelistet werden. Die Klasse Throwable besitzt zwei große Subklassen, Exception und Error. In den beiden Ausnahmeklassen sind die wichtigsten Ausnahmen und Fehler der Java-Laufzeitbibliothek bereits enthalten. Diese beiden Klassen bilden zwar zwei getrennte Hierarchien, werden aber ansonsten gleichwertig als Ausnahmen behandelt. Java 2 Kompendium
679
Kapitel 12
Debugging und Ausnahmebehandlung Die Ausnahmen der Klasse Error und ihrer Subklasse RuntimeError müssen Sie jedoch nicht extra abfangen oder dokumentieren. Dies geschieht automatisch, d.h., wenn keine Behandlung durch Sie erfolgt, wird die Ausnahme auf oberster Ebene behandelt und in der Systemausgabe erfolgt eine Meldung. In die Ausnahmentypen dieser Klassen fallen u.a. Situationen wie Speicherplatzmangel oder StackOverflowError, aber auch Array-Probleme oder mathematische Fehlersituationen. Wenn Sie sich die Dokumentation von Java anschauen, werden Sie dort Hunderte von Standard-Ausnahmen entdecken. Sie sollten diese Standard-Problemfälle auch nicht mehr explizit auflisten, denn damit zwingen Sie einen Aufrufer der Methode, diese irgendwie zu handhaben (was sonst automatisch über die Java-Umgebung gehandelt wird). Es gibt eigentlich nur fünf allgemeine Typen von Ausnahmen, die in einer throws-Klausel aufgelistet werden müssen. Man kann dies aus der Beschreibung der Klasse java.lang entnehmen. Es sind: ClassNotFoundException IllegalAccessException InstantiationException InterruptedException NoSuchMethodException
Dazu kommen diverse weitere Ausnahmen, die aus den verschiedenen JavaPaketen stammen. Sie können zum Teil der Klasse Error oder ihrer Subklasse RuntimeError zugeordnet sein und müssen nicht extra abgefangen oder dokumentiert werden (z.B. die Ausnahme ArrayStoreException aus dem Paket java.util), zum Teil werden sie jedoch der Klasse Exception zugeordnet und müssen behandelt werden (z.B. die gesamten Ausnahmen von java.io). Viele Methoden der Java-API verfügen über throws-Klauseln. Die bisherigen Erklärungen verdeutlichen Ihnen sicher, dass viele Ausnahmen irgendwie in Java behandelt werden müssen. Aber wie? Die throws-Klauseln dokumentieren nur das potenzielle Problem und verlagern es weiter. Explizit behandelt sind die Ausnahmen damit noch lange nicht. Das tun wir aber jetzt. Explizites Ausnahme-Handling Wenn Java schon einen so leistungsstarken Mechanismus zur Verfügung stellt, der Ausnahmen erzeugen und die Hierarchie nach oben weiterreichen lässt, so muss Java natürlich auch einen Mechanismus zur Verfügung stellen, um die geworfenen Ausnahme-Objekte zu behandeln. Entweder, Sie behandeln potenzielle Ausnahmen einer Methode direkt innerhalb der Methode selbst, oder Sie geben dem Aufrufer genügend Informationen, wie dieser die Ausnahmen behandeln kann, ggf. auch erst in der obersten Ebene der Hierarchie, wie gerade beschrieben. In jedem Fall erfolgt die Behand680
Java 2 Kompendium
Ausnahmebehandlung und andere Fehlerbehandlungen
Kapitel 12
lung mit der gleichen Struktur. In der Regel verwendet man zu dem Aufruf einer Ausnahme-auswerfenden Methode die umgebende try-catch-Struktur. try { // Innerhalb des try-Blocks werden diejenigen kritischen // Aktionen durchgeführt, die Ausnahmen erzeugen können. } catch (Exception e) { // Behandlung der Ausnahme // Das Ausnahmeobjekt e wird behandelt }
Kritische Aktionen in einem Java-Programm sollten immer innerhalb des try-Blocks durchgeführt werden. Falls Sie die Behandlung relativ weit oben in der Hierarchie durchführen, sollte dort die Methode stehen, die sich dann explizit um die Ausnahmebehandlung kümmert. Der Begriff »try« sagt bereits sehr treffend, was dort passiert. Es wird versucht, den Code innerhalb des try-Blocks auszuführen. Wenn ein Problem auftauchen sollte (sprich, es wird eine Ausnahme ausgeworfen), wird dieses sofort entsprechend im passenden catch-Block gehandhabt und alle nachfolgenden Schritte im try-Block werden nicht mehr durchgeführt. Wenn also eine der Anweisungen innerhalb des try-Blocks ein Problem erzeugt, wird dieses durch die passenden catch-Anweisungen aufgefangen und entsprechend behandelt (sofern die catch-Anweisung dafür die passende Behandlung enthält). Am Ende eines try-Blocks können beliebig viele catch-Klauseln mit unterschiedlichen Ausnahmen (möglichst genau die Situation beschreibend) stehen. Sie werden einfach nacheinander notiert. Ein Verschachteln von trycatch-Strukturen ist auch möglich, wobei dann auch außerhalb angesiedelte catch-Blöcke im Inneren nicht explizit aufgefangene Exceptions auffangen können. Damit können unterschiedliche Arten von Ausnahmen auch verschiedenartig – und damit sehr qualifiziert – gehandhabt werden. Anstatt alle möglichen Ausnahmen explizit aufzulisten, die eventuell erzeugt werden könnten, können Sie auch einfach einen etwas allgemeineren AusnahmeTyp auflisten (wie beispielsweise java.lang.Exception). Damit würde jede Ausnahme abgefangen, die aus java.lang.Exception abgeleitet wurde. Das lässt dann aber keine qualifizierte (d.h. der Situation genau angepasste) Reaktion zu. Eine Methode, die Ausnahmen erzeugen kann und sich selbst darum kümmert, könnte also so aussehen:
Java 2 Kompendium
681
Kapitel 12
Debugging und Ausnahmebehandlung Beispiel: public void eineMethode_mitExceptions() throws MeineException { MeineExceptionKlasse ausnahmenMöglich = new MeineExceptionKlasse(); try { ausnahmenMöglich.eineMethodeMitPotenziellenAusnahmen(); } catch (MeineException m) { // Fange die Ausnahme ab throw m;// Gibt die Exception weiter } }
Von besonderer Bedeutung ist die throw-Anweisung in dem Beispiel. Sie wird in Java immer zum direkten Auslösen einer Exception verwendet und übergibt als Argument eine Instanz der Ausnahme, die ausgelöst werden soll, an die nächsthöhere Hierarchieebene. Die throw-Anweisung ist vom Programmablauf her mit der break-Anweisung vergleichbar. Was in dem Block danach folgt, wird nicht mehr ausgeführt. Wir werden die throw-Anweisung etwas weiter unten (bei den benutzerdefinierten Ausnahmen) in einem praktischen Beispiel verwenden. Eine solche Ausnahmenbehandlung ist die sicherste Variante, weil die Methode selbst die potenziellen Ausnahmen behandelt. Wenn eine solche Ausnahme jedoch nicht direkt behandelt wird, wird sie nach oben in der Hierarchie der Methodenaufrufe weitergereicht. Falls nirgendwo eine Behandlung erfolgt, wird sich letztendlich das System selbst der Ausnahme annehmen und entweder eine Fehlermeldung ausgeben und/oder das Programm beenden. Was tun im catch-Teil? Sie haben nun eine Ausnahme in einem catch-Teil aufgefangen. Dort erfolgt die eigentliche Behandlung der Ausnahme. Wir wissen bereits, dass der catch-Teil unter gewissen Umständen (nicht immer) optional ist und auch weggelassen werden kann. Genauso ist es möglich, mehrere catch-Anweisungen zu benutzen, die dann sequenziell dahingehend überprüft werden, ob sie für die aufgetretene Ausnahme zuständig sind (wenn keine entsprechende catch-Anweisung gefunden wird, wird die Ausnahme an den nächsthöheren Programmblock weitergereicht). Aber was tun Sie damit konkret? Nun, das bleibt vollkommen Ihnen überlassen. Diese Aussage hilft Ihnen so nicht viel, soll aber deutlich machen, dass es Ihrem Konzept überlassen ist, eine Ausnahme so zu behandeln, wie es für das Programm sinnvoll erscheint. Wir werden eine potenzielle Behandlung allerdings durchsprechen – die Ausgabe einer Fehlermeldung. Und was noch erklärt werden muss, ist, wie Sie überhaupt an die Ausnahme herankommen. Schauen wir uns nochmals die Syntax einer beispielhaften catch-Klausel an: 682
Java 2 Kompendium
Ausnahmebehandlung und andere Fehlerbehandlungen
Kapitel 12
catch (ArithmeticException m) { ... }
In den runden Klammern nach dem Schlüsselwort catch steht ein Verweis auf den Ausnahmetyp, der in der betreffenden catch-Klausel behandelt werden soll. Sie können dort eine der unzähligen Standard-Ausnahmen von Java verwenden oder auch selbst definierter Ausnahmen. Das AusnahmeObjekt, das damit übergeben wird, besitzt einige Methoden, die Sie dann in der Behandlung nutzen können, etwa die Methode getMessage(). Diese Methode gibt die Fehlermeldungen der Ausnahme zurück. Sie wird von der Klasse Throwable (der Mutter aller Ausnahmen) definiert und ist daher in allen Exception-Objekten vorhanden. Wir nehmen unser Beispiel mit der potenziellen Division durch einen Wert 0 und packen es in eine try-catchStruktur. Daran sieht man, wie eine solche Ausnahme behandelt werden kann. Dabei seien a, b und Teiler irgendwelche int-Werte, die irgendwoher Werte zugewiesen bekommen: try { a=b/Teiler; } catch (ArithmeticException m) { System.out.println("Fehler bei der Berechnung: " + m.getMessage()); }
Wenden wir diese Erkenntnisse jetzt einmal in einer konkreten Übung an. Geben Sie Folgendes ein: class Division { public static void main(String args[]) { /* Die Wahl zweier Integerwerte als Divisoren ist nur für ein Beispiel zur Erzeugung einer Ausnahme sinnvoll. Sofern etwa der Zähler als float vereinbart oder ein Casting wie ergebnis = (float)n/m; durchgeführt wird, wird die hier gewünschte Ausnahme bei Division durch 0 nicht mehr ausgelöst. Statt dessen kommt der Wert Infinity zurück. */ int n; int m; float ergebnis=0; try { Integer ueber1 = new Integer(args[0]); Integer ueber2 = new Integer(args[1]); n = ueber1.intValue(); m = ueber2.intValue(); try{ ergebnis = n/m; System.out.println( "Das abgerundete Ergebnis der Division von " + n + " geteilt durch " + m + ": " +
Java 2 Kompendium
Listing 12.1: Qualifiziertes Abfangen von Ausnahmen
683
Kapitel 12
Debugging und Ausnahmebehandlung ergebnis); } /* catch-Teil der äußeren try-catch-finally-Konstruktion */ catch (ArithmeticException meineEx) { /* Die Meldung verwendet eine Standardmethode des Ausnahmeobjekts */ // – getMessage() System.out.println("Achtung Division durch 0! " + meineEx.getMessage()); } } catch (ArrayIndexOutOfBoundsException uebergabeEx) { // Keine zwei Übergabeparameter. // Beachten Sie, dass das konkrete Ausnahmeobjekt // in der Behandlung der // Ausnahme nicht verwendet wird. System.out.println( "Das Programm benoetigt zwei Uebergabeparameter."); } catch (NumberFormatException uebergabeEx) { // Keine numerischen Übergabeparameter // Beachten Sie, dass das konkrete Ausnahmeobjekt // in der Behandlung der // Ausnahme hier wieder verwendet wird. System.out.println("Das Programm benoetigt zwei numerische Uebergabeparameter. " + uebergabeEx.getMessage()); } finally { System.out.println("Bis bald."); } System.out.println("Das kommt auch noch"); } }
Das Beispiel erfordert zwei Übergabeparameter, die jeweils einem IntegerObjekt zugewiesen und über die Methode intValue() dann als int-Werte verwendet werden. Wenn Sie als Teiler (der zweite Übergabeparameter) eine 0 übergeben, wird eine ArithmeticException ausgeworfen und im catch-Teil aufgefangen. Fehlt ein Übergabewert, wird eine ArrayIndexOutOfBoundsException ausgeworfen und im entsprechenden catch-Teil behandelt. Wird keine Ganzzahl als Übergabewert angegeben, wird eine NumberFormatException ausgeworfen und qualifiziert behandelt. Ansonsten wird das Ergebnis der Division ausgegeben. Dabei sollte zur Kenntnis genommen werden, dass sowohl der Teil, der mit dem Schlüsselwort finally eingeleitet wird, als auch der danach noch folgende Teil des Programms auf jeden Fall ausgeführt werden. Das zeigt deutlich, dass eine auftretende Ausnahme ein Programm nicht beendet (es sei denn, sie programmieren es explizit) oder abstürzen lässt. Beachten Sie bitte, dass wir bewusst zwei Integerwerte teilen (auch kein Casting auf einen Nachkommatyp). Sofern Sie etwa den Zähler als float
684
Java 2 Kompendium
Ausnahmebehandlung und andere Fehlerbehandlungen
Kapitel 12
vereinbaren oder so etwas wie ergebnis = (float)n/m; durchführen, werden Sie die für unser Beispiel gewünschte Ausnahme bei Division durch 0 nicht mehr auslösen. Statt dessen wird der Wert Infinity zurückgeliefert. Abbildung 12.1: Division durch 0
Abbildung 12.2: Falsches Format der Übergabewerte
Die finally-Klausel Die finally-Anweisung erlaubt die Abwicklung wichtiger Abläufe wie zum Beispiel das Schließen von Dateien, das saubere Unterbrechen von Netzwerkverbindungen oder das Freigeben von Ressourcen, bevor die Ausführung des gesamten try-catch-finally-Blocks unterbrochen wird. Dies kann dann so aussehen: try { //Hier stehen Anweisungen, wovon ein oder mehrere // Ausnahmen werfen können } catch (ExceptionKlasse1 ausnahme1) { Java 2 Kompendium
685
Kapitel 12
Debugging und Ausnahmebehandlung
Abbildung 12.3: Fehlende Übergabewerte
Abbildung 12.4: Korrekter Programmablauf
// Behandlung der ersten Ausnahme mit Zugriff auf das // per throws-Klausel erzeugte Ausnahmeobjekt ausnahme1 } catch (ExceptionKlasse2 ausnahme2) { //Behandlung der ersten Ausnahme mit Zugriff auf das // per throws-Klausel erzeugte Ausnahmeobjekt ausnahme2 } catch (ExceptionKlasse3 ausnahme3) { // Behandlung der ersten Ausnahme mit Zugriff auf das // per throws-Klausel erzeugte Ausnahmeobjekt ausnahme3 } finally { // Der hier stehende Code wird in jedem Fall // ausgeführt. Dies ist unabhängig von potenziell // auftretenden Ausnahmen }
686
Java 2 Kompendium
Ausnahmebehandlung und andere Fehlerbehandlungen
Kapitel 12
Der Block finally ist optional und kann durchaus weggelassen werden. Unabhängig davon, ob innerhalb des try-Blocks eine Ausnahme auftritt oder nicht, werden die Anweisungen in dem Block finally ausgeführt.Tritt eine Ausnahme auf, wird der jeweilige catch-Block ausgeführt und im Anschluss daran erst der der finally-Anweisung folgende Block. Nun sollte aber aus dem letzten Beispiel hervorgegangen sein, dass eine Ausnahme ein Programm in der Regel nicht abbricht und auch Anweisungen außerhalb der gesamten Struktur auf jeden Fall ausgeführt werden. Wozu dann die finally-Klausel? Könnte man nicht alle auf jeden Fall auszuführenden Anweisungen außerhalb der gesamten Struktur notieren? Oft ist das wirklich auch möglich. Es gibt jedoch einige Situationen, wo man nicht so argumentieren kann. Wenn etwa innerhalb einer try-catch-Struktur eine Sprunganweisung wie break ausgelöst wird und damit eine umgebende Schleife verlassen werden soll, wird der finally-Block dennoch vorher ausgeführt – außerhalb der gesamten Struktur, innerhalb der Schleife notierter Quelltext jedoch noch nicht. Also hat diese Klausel immer dann ihre Bedeutung, wenn der Programmfluss umgeleitet wird und bestimmte Schritte vor der Umleitung unumgänglich sind.
12.2.3
Benutzerdefinierte Exceptions
Obwohl Java in seinen Standardbibliotheken über unzählige vorgefertigte Ausnahmen für fast alle wichtigen Standardsituationen (z.B. bei Dateioperationen auf Basis der IOException) verfügt, ist es gelegentlich der Fall, dass ein Programm selbst definierte Ausnahmen benötigt. Um eine solche Ausnahme zu erstellen, muss nur eine Unterklasse von Throwable oder eine ihrer Unterklassen wie Exception implementiert werden. Die Auslösung dieser selbst definierten Ausnahme erfolgt wie bei den Standardausnahmen über die throw-Anweisung und die Dokumentation mit throws. Benutzerdefinierte Exceptions verfügen normalerweise (Konvention) über zwei Konstruktoren. Der eine Konstruktor erzeugt eine neue Instanz mit einer Fehlermeldung, der andere ohne Fehlermeldung. Schauen wir uns ein Beispiel an, das auf Basis von ArithmeticException eine eigene Ausnahme definiert: class MeineAusnahme extends ArithmeticException { MeineAusnahme(String msg) // Konstruktor 1 { super(msg); } MeineAusnahme() // Konstruktor 2 { super(); } }
Java 2 Kompendium
687
Kapitel 12
Debugging und Ausnahmebehandlung Die immer als erste Direktive notierte Anweisung super() greift explizit auf den Konstruktor der Superklasse zu. Dazu können noch – falls notwendig – nachfolgende zusätzliche Schritte hinzugefügt werden. Wenn diese Ausnahme nun mit der throws-Anweisung von einer Methode, die diese Ausnahme implementiert hat, ausgeworfen wird, kann diese in einem catch-Teil aufgefangen werden. Etwa so: catch (MeineAusnahme m) { System.out.println("Fehler bei der Berechnung: " + m.getMessage()); }
Wir wollen die benutzerdefinierten Ausnahmen in Verbindung mit der throw-Anweisung in zwei vollständigen Beispiel üben. Das erste Beispiel fängt über eine selbst definierte Ausnahme den Start eines Programms ab. Das Programm darf nur gestartet werden, wenn als Übergabewert ein korrektes Passwort angegeben wird (dann gibt es die Lottozahlen der nächsten Woche aus – etwas Motivation muss sein ;-)). Andernfalls wird eine Ausnahme ausgeworfen und die im try-Block nachfolgenden Anweisungen nicht ausgeführt. Außerdem wird eine Standard-Ausnahme abgefangen (kein Übergabewert). Listing 12.2: Verwendung von selbst definierten Ausnahmen
688
class MeineAusnahme extends Throwable { MeineAusnahme(String msg) { super(msg); } } public class Passwort { static void werfeAusnahme2(String a) throws MeineAusnahme { MeineAusnahme m = new MeineAusnahme(a); if (!a.equals("geheim")) { System.out.println("Nur mit Passwort"); throw m; } } public static void main(String args[]){ try { werfeAusnahme2(args[0]); System.out.println( "Und hier sind die LOOOTTTTOOOO-Zahlen von naechster Woche: " + "4,5,6,16,23,43"); } catch (MeineAusnahme meineEx) { System.out.println("Nix is: " + meineEx.getMessage()); } catch(ArrayIndexOutOfBoundsException e){ System.out.println("Bitte einen Uebergabewert an das Programm eingeben"); } } }
Java 2 Kompendium
Ausnahmebehandlung und andere Fehlerbehandlungen
Kapitel 12 Abbildung 12.5: Korrekter Programmablauf
Abbildung 12.6: Falsches Passwort
Abbildung 12.7: Der Übergabewert wurde nicht angegeben.
Java 2 Kompendium
689
Kapitel 12
Debugging und Ausnahmebehandlung Testen wir nun, was passiert, wenn Sie die Methode ohne umgebenden tryKonstrukt mit passendem catch-Auffangblock verwenden wollen. Kommentieren Sie einfach das try-Schlüsselwort und den gesamten catchAnteil aus (auf der Buch-CD ist das die Datei PasswortFehler1.java). Der Compiler wird das Programm nicht übersetzen. Es kommt eine Fehlermeldung der Art: PasswortFehler1.java:19: unreported exception MeineAusnahme; must be caught or declared to be thrown werfeAusnahme2(args[0]); ^ 1 error
Grund ist, dass die Methode werfeAusnahme2() in der Deklaration eine Ausnahme der Klasse MeineAusnahme per throws auflistet. Das bedeutet nicht mehr und nicht weniger, als dass diese bei der Verwendung der Methode unbedingt aufgefangen oder an eine höhere Ebene (hier nicht vorhanden) weitergereicht werden muss. Es genügt aber auch nicht, die Methode in einen try-Block zu packen und zu hoffen, die Ausnahme würde bis zur Systemebene durchgereicht. Konsequenz wäre folgende (PasswortFehler2.java): PasswortFehler2.java:18: 'try' without 'catch' or 'finally' try { ^ 1 error
Wir arbeiten hier mit einer selbst definierten Ausnahme als Ableitung von Throwable. Ein catch-Block der Form catch(Exception e){}
funktioniert deshalb auch nicht, denn unsere selbst definierte Ausnahme ist ja direkt von Throwable abgeleitet. Es müsste schon so etwas sein: catch(Throwable e){}
Sinnvoll ist dieses maximal allgemeine Abfangen aber selten, denn man möchte ja qualifiziert auf verschiedene Ausnahme reagieren. Testen wir nun noch eine andere Form der Fehlverwendung. Lassen Sie einfach einmal die throws-Anweisung bei der Methodendeklaration weg (der Rest soll wieder in Ordnung sein – PasswortFehler2.java). Auch das wird der Compiler nicht machen, denn wenn in einer Methode eine Ausnahme ausgeworfen wird, muss sie auch dokumentiert werden. Sie erhalten folgende Fehlermeldung: 690
Java 2 Kompendium
Ausnahmebehandlung und andere Fehlerbehandlungen
Kapitel 12
PasswortFehler3.java:24: exception MeineAusnahme is never thrown in body of corresponding try statement catch (MeineAusnahme meineEx) { ^ 2 errors
Sie sehen also, dass das Exception-Konzept nicht nur ein Sicherungsverfahren ist, sondern auch Anwender einer Methode führen kann, sodass diese sinnvoll eingesetzt wird. Umgekehrt kann eine Methode keine gefährlichen Dinge mit Ausnahmen tun, ohne es zu dokumentieren. Erstellen Sie noch ein zweites Beispiel. /* Verwendung einer selbst definierten Ausnahme und der throw-Anweisung*/ // Die selbst definierte Ausnahme class MeineAusnahme extends Throwable { MeineAusnahme(String msg) // Konstruktor 1 { super(msg); } MeineAusnahme() // Konstruktor 2 { super(); } } public class TueWas { // Eine erste Methode, die die selbst definierte // Ausnahme auswirft. static void werfeAusnahme1() throws MeineAusnahme { // Erzeugen eines Ausnahmeobjekts mit Konstruktur 1 MeineAusnahme m = new MeineAusnahme(); System.out.println("Gleich wird mit throw eine Ausnahme ausgeworfen."); throw m; // Auswerfen der Ausnahme } // Eine zweite Methode, die die selbst definierte // Ausnahme auswirft. static void werfeAusnahme2(int test) throws MeineAusnahme { // Erzeugen eines Ausnahmeobjekts mit // Konstruktur 2, eine Meldung, die später mit // getMessage() abgefragt werden kann, // wird definiert. MeineAusnahme m = new MeineAusnahme("Das ist meine Meldung fuer den Fall einer Ausnahme"); System.out.println("Wenn die Bedingung fuer das Eintreten einer Ausnahme erfuellt ist,"); System.out.println("wird gleich mit throw eine Ausnahme ausgeworfen. Dieses Mal mit Meldung."); System.out.println("Index der For-Schleife: " + test); // Bedingung fuer die Ausloesung der Ausnahme if (test >1 ) throw m; System.out.println("Hier steht noch ne Menge Zeug, das ignoriert wird, wenn vorher die Bedingung");
Java 2 Kompendium
Listing 12.3: Ein weiteres Beispiel für die Verwendung von selbst definierten Ausnahmen
691
Kapitel 12
Debugging und Ausnahmebehandlung System.out.println("fuer throw erfuellt war. Sonst wird es angezeigt."); } public static void main(String args[]) { int i = 0; // 1. Try-catch-Block try { werfeAusnahme1(); // Methode 1 } catch (MeineAusnahme meineEx) { System.out.println("Wir werfen die Ausnahme hoch in die Luft: " + meineEx.getMessage()); } // 2. Try-catch-Block try { for (i=0;i<3;i++) { // Methode 2. Sie wird 3 x aufgerufen und nur // beim 3. Mal wird eine Ausnahme ausgelöst werfeAusnahme2(i); } } catch (MeineAusnahme meineEx) { System.out.println( "Ausnahme mit Konstruktor 2 und der Meldung: " + meineEx.getMessage()); } } }
Das Ergebnis dieses Programms wird so aussehen: Abbildung 12.8: Die Ausgabe des Beispiels
Als Erstes haben wir in dem Beispiel eine selbst definierte Ausnahme als direkte Ableitung der Klasse Throwable erzeugt. Diese hat zwei Konstruktoren.
692
Java 2 Kompendium
Ausnahmebehandlung und andere Fehlerbehandlungen
Kapitel 12
Beide in unserer public-Klasse definierten Methoden werfen diese selbst definierte Ausnahme aus. Die erste Methode am Ende jedes Aufrufs, die zweite Methode nur, wenn bestimmte Bedingungen erfüllt sind. In unserem Beispiel ist das der triviale Fall, dass eine Variable einen bestimmten Wert hat. Beachten Sie, dass die erste Methode ein Ausnahme-Objekt mit dem ersten Konstruktor (ohne Meldung) erzeugt, während die zweite Methode den zweiten Konstruktor (mit Meldung) verwendet. Sie sehen das Resultat, wenn Sie dann später die Methode getMessage() auf das jeweilige Ausnahme-Objekt anwenden. Der Aufruf der Methoden erfolgt jeweils innerhalb eines try-catch-Blocks. Die zweite Methode besitzt dabei einen Übergabeparameter, der mit einer for-Schleife verändert wird und die Bedingung für das Auslösen der Ausnahme festlegt.
12.2.4
Die RuntimeException
Wir haben mittlerweile gesehen, dass Anweisungen, die Ausnahmen auslösen können, entweder direkt in eine try-Anweisung eingefasst werden (mit optionalen catch-Anweisungen) oder mittels der throws-Klausel an den übergeordnenten Programmblock weitergereicht werden. Falls weder eine Behandlung vor Ort noch eine throws-Klausel verwendet wird, liefert der Compiler im Allgemeinen Fall bereits bei der Übersetzung eine entsprechende Fehlermeldung. Eine besondere Ausnahme ist jedoch die Klasse RuntimeException (public class RuntimeException extends Exception) samt ihrer Unterklassen. Sie ist die Superklasse aller Exceptions, die durch eine normale Operation von der Java Virtual Machine ausgeworfen werden können. Für sie ist weder eine lokale Fehlerbehandlung noch eine Weiterleitung mit throws notwendig (aber immer möglich). Schauen Sie sich die Liste der Unterklassen einmal an – Sie werden Einige der Exceptions kennen. Die Subklassen von RuntimeException: ArithmeticException ArrayStoreException CannotRedoException CannotUndoException ClassCastException, CMMException ConcurrentModificationException EmptyStackException IllegalArgumentException, IllegalMonitorStateException IllegalPathStateException IllegalStateException ImagingOpException, IndexOutOfBoundsException
Java 2 Kompendium
693
Kapitel 12
Debugging und Ausnahmebehandlung MissingResourceException NegativeArraySizeException, NoSuchElementException NullPointerException ProfileDataException ProviderException, RasterFormatException SecurityException SystemException UndeclaredThrowableException, UnsupportedOperationException
Wir werden im nachfolgenden Kapitel zur Ein- und Ausgabe unter Java ein paar umfangreichere Programmierbeispiele durchsprechen. In diesem Rahmen wird die Ausnahmebehandlung auch eine Rolle spielen und Ihnen anhand dieser praktischen Beispiele noch einmal vorgeführt.
12.3
Zusammenfassung
Fehlervermeidung unter Java ist nahezu unmöglich, aber man kann Fehler reduzieren. Wenn man nun aber einen Fehler gemacht hat, benötigt man zum Debuggen nicht unbedingt einen Debugger. Er ist aber hilfreich und erlaubt die Auswertung von tiefergehenden Informationen. Außerdem erspart er die Erstellung von Debug-Code. Um nun auffangbare Fehlersituationen in einem Programm zu behandeln, liefert Java ein geniales, leistungsfähiges, stabiles und sehr leicht einzusetzendens Konzept – die Ausnahmebehandlung. Um eine Methode zu erstellen, die mit Ausnahmen sinnvoll umgehen kann, muss in zwei Stufen vorgegangen werden. 1.
Festlegen der Arten von Ausnahmen, die erzeugt werden können, indem diese Ausnahmen in der Ausnahmenliste in der Methodendeklaration per throws aufgelistet werden. Davon ausgenommen sind Standard-Ausnahmen, die immer aufgefangen werden können.
2.
Das eigentliche Auslösen der Ausnahme erfolgt durch die Benutzung der throw-Anweisung, gefolgt von einer entsprechenden Ausnahme. Jede Ausnahme muss aus der Klasse java.lang.Throwable abgeleitet sein.
Jede Ausnahme kann entweder eine ursprüngliche Java-Ausnahme sein oder von Ihnen selbst erstellt werden. Anstatt alle möglichen Ausnahmen aufzulisten, die eventuell erzeugt werden könnten, können Sie auch einfach einen etwas allgemeineren Ausnahme-Typ auflisten (wie beispielsweise java.lang.Exception). Damit würde jede Ausnahme abgefangen, die aus
694
Java 2 Kompendium
Zusammenfassung
Kapitel 12
java.lang.Exception abgeleitet wurde. Dies gilt allerdings als nicht sonderlich anwenderfreundlich, da die Fehlerbehandlung recht allgemein gehalten werden muss.
Java 2 Kompendium
695
13
Ein- und Ausgabe in Java
Ein- und Ausgabeoperationen zählen zu den grundlegendsten Aktionen, die von einem Programm bewerkstelligt werden. Ob es nur um das Abspeichern und wieder Einlesen von Einstellungen geht oder gleich um ganze Dokumente von zig Seiten. Kaum ein Programm kommt noch ohne Ein- und Ausgabeoperationen aus. Ein- und vor allem Ausgabe bedeutet normalerweise, dass Daten aus einer Datei gelesen oder in eine Datei geschrieben werden. Innerhalb von Applets ist dies meist durch die Einstellungen des Containers grundsätzlich verboten. Die Klassen zur Ein- und Ausgabe sind also normalerweise eher für echte Java-Applikationen als für Java-Applets gedacht. In Java werden diese Ein- und Ausgabeoperationen mittels so genannter Datenströme realisiert. Der Begriff »Strom« geht auf Unix zurück – das Pipe-Betriebssystem. Unter einer Pipe versteht man einen nicht-interpretierten Strom von Bytes. Er wird zur Kommunikation von Programmen untereinander bzw. von Programmen und Hardwareschnittstellen verwendet. Zum Thema Ausgabe zählt ebenfalls das Drucken unter Java. Dies war in den vergangenen Versionen von Java ein gewisses Problem. Unter dem SDK 2 mit dem JDK 1.3 ist die Programmierung von Druckoperationen jedoch relativ unkompliziert.
13.1
Allgemeines zur Ein- und Ausgabe unter Java
Ein Datenstrom kann von jeder beliebigen Quelle herkommen, d.h., der Ursprungsort spielt überhaupt keine Rolle, ob Internet, lokaler Server oder lokaler Rechner ist egal. Dies mag im ersten Moment als nicht sonderlich wichtig erscheinen, ist jedoch von entscheidender Bedeutung. Nehmen wir zur Verdeutlichung einfach mal ein anderes Abstraktionsmodell. Hier muss sich der Empfänger von Daten selbst um die Abholung kümmern. Der Strom fließt also nicht einfach an ihm vorbei. Eine genaue Kenntnis des Quellortes und diverser weiterer Informationen der Quelle (Zugriffsmöglichkeiten) müssen erst beim Empfänger vorhanden sein, bevor er die Daten abholen kann, die ihn eigentlich interessieren. Java 2 Kompendium
697
Kapitel 13
Ein- und Ausgabe in Java In unserem Modell des Datenstroms hingegen steckt die Information über die Quelle in einem Strom-Argument. Außerdem gibt es noch ein weiteres Argument, in das die verarbeiteten Daten zurückgeschrieben werden können. Das Thema selbst ist sehr abstrakt und umfangreich, denn Java bietet eine Vielzahl von unterschiedlichen Strömen, die wir hier der Vollständigkeit halber ansprechen wollen. Wir werden uns aber darauf beschränken, die für Sie wahrscheinlich wichtigsten Ströme (die zur Ein- und Ausgabe auf einem Dateisystem) mit Beispielen genauer zu vertiefen. Unter Java stehen zur Behandlung des Datenstrommodells im Wesentlichen zwei abstrakte Klassen zur Verfügung: InputStream OutputStream
Die beiden Klassen gehören zu dem Paket java.io, das Sie bei jedem Programm mit Ein- oder Ausgabeoperationen importieren sollten. Das Paket java.io enthält eine relativ große und hierarchisch gut durchstrukturierte Anzahl von Klassen und Schnittstellen zur Unterstützung von Ein- und Ausgabe. Die meisten dieser Klassen leiten sich von den besagten abstrakten Klassen InputStream und OutputStream ab bzw. verwenden sie als Argumente. Es gibt aber auch Klassen, die nicht auf diese beiden abstrakten Klassen zurückgehen. Eine der wohl wichtigsten Nicht-Streamklassen ist die Klasse File, die Dateinamen und Verzeichnisnamen in einer plattformunabhängigen Weise verwaltet. Die Klasse File bietet Dienste an, um auf lokalen Systemen Dateien und Verzeichnisse aufzulisten, Dateiattribute zu erfragen und Dateinamen zu ändern oder zu löschen. Mit der Schnittstelle FilenameFilter ist es möglich, Klassen zu implementieren, die einen Filter für die Dateiverwaltung erzeugen, also zum Beispiel nur das Lesen von »*.doc«-Dateien. Diese Schnittstelle kann beispielsweise innerhalb der Klasse java.awt.FileDialog verwendet werden, um die Anzeige von Dateien einzuschränken. Über die Klasse RandomAccessFile lassen sich Zeichen an willkürlichen Positionen innerhalb von Dateien auslesen. Diese Klasse wird aber wohl eher weniger verwendet, da für das Einlesen und Auslesen von Daten aus einer Datei die Verwendung einer Streamklasse weitaus sinnvoller ist. Zwar verfügen einige Ströme über ein paar zusätzliche Methoden, aber dennoch gibt es bestimmte Methoden, die immer zur Verfügung stehen.
698
Java 2 Kompendium
Allgemeines zur Ein- und Ausgabe unter Java
Kapitel 13
Die einfachen Strom-Methoden erlauben nur das Versenden von Bytes mittels Datenströmen. Zum Senden verschiedener Datentypen gibt es die Schnittstellen DataInput und DataOutput. Sie legen Methoden zum Senden und Empfangen anderer Java-Datentypen fest. Mithilfe der Schnittstellen ObjectInput und ObjectOutput lassen sich ganze Objekte über einen Strom zu senden. Mit dem StreamTokenizer können Sie einen Strom wie eine Gruppe von Worten behandeln. Er ist dem StringTokenizer ähnlich, der das Gleiche mit Zeichenketten tut. Eine weiter Streamklasse – StringBufferInputStream – liest Daten aus dem StringBuffer und ermöglicht damit eine Cache-Funktionalität. Für die Kommunikation von einzelnen Threads gibt es die beiden Klassen PipedInputStream zum Lesen von Daten aus einem PipedOutputStream. Der PipedOutputStream dient also zum Schreiben in einen PipedInputStream. Alle Methoden, die sich mit Eingabe- und Ausgabeoperationen beschäftigen, werden in der Regel mit throws IOException abgesichert. Diese Subklasse von Exception enthält alle potenziellen I/O-Fehler, die bei der Verwendung von Datenströmen auftreten können. Von IOException abgeleitete Exceptions können (und müssen) direkt mit einem try-catch-Block aufgefangen, oder an eine übergeordnete Methode weitergegeben werden. Beginnen wir gleich mit einem kleinen Beispiel, obwohl uns noch diverse Details fehlen, die erst im Folgenden besprochen werden. Das Beispiel ist dennoch recht einfach zu verstehen. Es handelt sich um ein Programm, das eine als Aufrufparameter an das Programm übergegebene Datei ausliest und auf dem Bildschirm ausgibt. Am Ende wird dann noch die Grö ß e der Datei angezeigt. import java.io.*; public class LeseDat { int i; public void lese(String quelldatei) { // Erstellen eines Eingabestroms try { DataInput quelle = new DataInputStream(new FileInputStream(quelldatei)); while(true) { // Einlesen eines byte aus der Quelle byte zeichen = quelle.readByte(); System.out.print((char)zeichen); i++; }} // Dateiende der Quelldatei erreicht. // Explizite Verwendung einer Exception um
Java 2 Kompendium
Listing 13.1: Lesen und analysieren einer Datei auf der Festplatte
699
Kapitel 13
Ein- und Ausgabe in Java // die while()-Schleife abzubrechen catch (EOFException e) { System.out.println( "Alles klar! Lesen der Datei beendet."); System.out.println("Dateigroesse in Byte: " + i);} catch (IOException e) // allgemeiner IO-Fehler { System.out.println(e.getMessage());}} public static void main(String[] args) { LeseDat a = new LeseDat(); try { a.lese(args[0]); } catch(ArrayIndexOutOfBoundsException e) { System.out.println(e.toString()); System.out.println("Bitte eine Datei als Parameter angeben"); } } }
Wenn man als einzulesende Datei den Quellcode des Programms selbst verwendet, wird das Ergebnis dieses Programms so aussehen: Abbildung 13.1: Die Ausgabe des Beispiels bei einer korrekt angegebenen Datei
Wenn ein Parameter vergessen wird, erfolgt die entsprechende selbst erstellte Meldung in dem catch-Block. Wenn die Datei nicht gefunden wird, wird die Meldung vom System generiert. Das Kapitel beinhaltet zwar diverse Beispiele, aber zahlreiche spezielle Einund Ausgabeoperationen werden wir in anderen Kapiteln (besonders in dem zu den erweiterten Java-Techniken) im Zusammenhang mit den dazu notwendigen ergänzenden Techniken in der Praxis zeigen.
700
Java 2 Kompendium
Die Klasse InputStream
Kapitel 13 Abbildung 13.2: Datei nicht gefunden
13.2
Die Klasse InputStream
Diese Klasse ist Basis für viele der wichtigsten Leseoperationen eines Bytestroms. Woher die Bytes kommen und wie sie befördert werden, spielt keine Rolle, sie müssen dem einlesenden Objekt nur zur Verfügung stehen.
13.2.1
Methoden zum Einlesen
Zum Einlesen von Bytes aus Datenströmen dient hauptsächlich die Methode read(), von der es diverse Variationen und spezialisierte Erweiterungen gibt. Allen read()-Methoden ist gemeinsam, dass sie auf Beendigung aller angeforderten Eingaben warten. Deshalb packt man Eingabeoperationen auch meist in einen extra Thread. Die einfachste Form ist public abstract int read() throws IOException. Diese liest ein einzelnes Byte aus dem Eingabestrom und gibt es aus. Wenn der Strom das Dateiende erreicht, gibt diese Methode -1 aus. Die Rückgabe -1 bedeutet im Unterschied zu C nicht, dass ein Fehler eingetreten ist. Fehler werden immer als IOException ausgeworfen. Etwas komfortabler ist die folgende read()-Variante: public int read(byte[] buffer) throws IOException
Hier wird ein Puffer (ein Datenfeld) mit Bytes gefüllt, die aus einem Strom gelesen wurden. Die Methode gibt die Anzahl der gelesenen Bytes zurück. Diese Variante der read()-Methode kann bei Bedarf weniger Bytes lesen als Java 2 Kompendium
701
Kapitel 13
Ein- und Ausgabe in Java das Datenfeld aufnehmen kann. Dies kann dann geschehen, wenn im Datenstrom nicht genug Bytes vorhanden sind, um das Feld vollständig zu füllen. In dem Fall gibt die Methode die Anzahl der gelesenen Bytes zurück. Wenn der Strom das Dateiende erreicht, gibt diese Methode wie die erste Variante -1 aus. Beispiel: InputStream datenstrom = methodezumLeseneinerDatenquelle() byte[] buffer = new byte[42]; if (datenstrom.read(buffer) != buffer.length) { ...tue irgendwas mit den eingelesenen Daten... }
Sie können mit einer weiteren Variante von read() gezielt in einem Bereich ihres Puffers lesen: public int read(byte[] buffer, int beginn, int laenge) throws IOException
Damit wird ein Puffer beginnend mit Position beginn mit der mit laenge angegebenen Anzahl von Bytes aus dem Strom gefüllt. Es wird entweder die Anzahl der gelesenen Bytes oder -1 für das Dateiende ausgegeben. Um sämtliche Varianten der read()-Methode zu sehen, sei explizit auf die Online-Dokumentation verwiesen.
13.2.2
Blockaden vorher abfragen
Da allen read()-Methoden gemeinsam ist, dass sie auf Beendigung aller angeforderten Eingaben warten, kann es – gerade in Netzwerken – zu längeren Blockaden beim Einlesen von Daten kommen. Die read()-Methode ist unter Umständen blockiert und wartet auf Daten, ohne etwas zurückzugeben, wenn keine Daten verfügbar sind. Zwar erlaubt Multithreading, dass andere Operationen des Programms parallel ablaufen. Dennoch möchte man eventuelle Blockaden oft vermeiden. Dazu können Sie vorzeitig anfragen, wie viele Bytes ohne Blockieren gelesen werden können. Zu diesem Zweck haben Sie die Methode public int available() throws IOException zur Verfügung. Sie gibt diese Anzahl zurück: Die Methode available() arbeitet in vielen Fällen aber nicht perfekt, was in der Natur der Datenströme liegt. Einige Datenströme geben beispielsweise immer 0 aus. Sie sollten sich also nicht auf die Methode verlassen. Sie können sich zwar weitgehend darauf verlassen, dass die angegebene Anzahl von Bytes, die Ihnen mitgeteilt wird, auch ohne Blockierung gelesen werden kann (dementsprechend als untere Grenze zu verstehen). Es können aber ebenso mehr sein, was bei einige Datenströmen oft der Fall ist (als obere Grenze infolgedessen unzuverlässig). 702
Java 2 Kompendium
Die Klasse InputStream
13.2.3
Kapitel 13
Überspringen von Daten
Für den Fall, dass Sie Daten in einem Strom überspringen wollen, steht Ihnen die Methode public long skip(long n) zur Verfügung. Indem Sie die Anzahl von Bytes (in Form eines long-Datentyps) angeben, die Sie überspringen wollen, beginnen Sie erst an der darauffolgenden Stelle mit der Leseoperation. Die skip()-Methode benutzt implizit die read()-Methode, um die angegebene Anzahl von Bytes zu überspringen. Sie gibt die Anzahl der Bytes aus, die sie übersprungen hat, oder -1, wenn sie am Ende der Datei angelangt ist.
13.2.4
Positionen beim Lesen markieren
Einige Eingabeströme unterstützen eine Markierung einer Position im Strom und ein folgendes Zurücksetzen des Stroms an diese Position. Sie können sich wie eine Art Lesezeichen verhalten. Der Strom muss sich gewissermaßen an alle Bytes, die ihn durchfließen, erinnern. Es muss betont werden, dass nur einige Ströme diese Technik unterstützen und auch bei diesen nicht an jede Stelle ein Lesezeichen gesetzt werden kann, sondern nur an gewissen Stützpunkten in festgelegten Abständen. Wir werden solche Stromtypen noch explizit kennen lernen. Hier behandeln wir erst einmal die Methode, mit der Sie untersuchen können, ob der Strom Markieren unterstützt. Die Methode public boolean markSupported gibt true aus, wenn der Strom Markieren unterstützt. Wenn ein Strom Markieren unterstützt, können Sie die folgende Methode für die konkrete Markierung verwenden: public synchronized void mark(int leseLimit)
Der Parameter leseLimit legt die maximale Anzahl Bytes fest, die vor dem Zurücksetzen weitergegeben werden soll. Das Zurücksetzen auf die Markierung selbst erfolgt mit der Methode public synchronized void reset() throws IOException.
Beispiel: // Datenstrom bekommt beliebige Daten // Datenstrom unterstützt Markieren if (datenstrom.markSupported()) { ...// lese einen Teil der Daten... datenstrom.mark(42); ...// lese maximal 42 Bytes
Java 2 Kompendium
703
Kapitel 13
Ein- und Ausgabe in Java datenstrom.reset(); ...// Zurücksetzen und die letzten Bytes erneut lesen } else // Datenstrom unterstützt kein Markieren { ...// tue was anderes }
13.2.5
Ressourcen freigeben
Wenn Sie mit der Verarbeitung von einem Strom fertig sind, sollten Sie ihn mit der Methode public void close() throws IOException wieder explizit schließen. Diese Vorgehensweise ist zwar nicht unbedingt zwingend, denn die meisten Ströme werden automatisch bei der Garbage Collection oder mit einer geeigneten finalize()-Methode geschlossen. Es gibt aber einige Gründe, warum man es dennoch tun sollte. Es kann durchaus vorkommen, dass Sie den Strom wieder öffnen wollen, bevor die automatische Bereinigung stattgefunden hat, um daraus zu lesen. Außerdem ist in den meisten Betriebssystemen die Zahl der Dateien, die gleichzeitig geöffnet sein können, begrenzt. Am besten schließt man einen Strom immer in einem try-catch-finallyKonstrukt. Beispiel: InputStream datenstrom = methodezumLeseneinerDatenquelle() try { ...// versuchen, Daten zu lesen und zu verarbeiten } finally { datenstrom.close(); }
13.3
Die Klasse OutputStream
Anders als der Eingabestrom, der eine Datenquelle darstellt, ist der Ausgabestrom ein Empfänger für Daten. Man findet Ausgabeströme fast nur in Verbindung mit Eingabeströmen. Führt ein InputStream eine Operation aus, wird die zugehörige umgekehrte Operation vom OutputStream durchgeführt. Mit dieser abstrakten Klasse können viele der wichtigsten Schreiboperationen eines Bytestroms verwirklicht werden. Die Identität der Bytes und wie sie befördert werden spielt wie bei der abstrakten Klasse InputStream keine Rolle.
704
Java 2 Kompendium
Die Klasse OutputStream
13.3.1
Kapitel 13
Methoden zum Schreiben
Die grundlegendste Methode eines OutputStream-Objekts ist die write()Methode zum Erzeugung eines Ausgabestroms. Sie gibt es wie die read()Methode in diversen Variationen. Allen write()-Methoden ist wie den read()-Methoden gemeinsam, dass sie auf Beendigung des vollständigen Vorgangs (in diesem Fall des Schreibvorgangs) warten. Deshalb packt man auch Schreiboperationen meist in einen extra Thread. Die einfachste Version public abstract void write(int b) throws IOException schreibt ein einzelnes Zeichen in einen Ausgabestrom. Etwas praxisorientierter ist die folgende Variation: public void write(byte[] buffers) throws IOException
Sie schreibt den gesamten Inhalt des Datenfeldes buffers in den Ausgabestrom. Dazu sollte der Puffer natürlich vorher mit Bytes gefüllt werden. Es gibt natürlich auch ein schreibendes Gegenstück zu der Variante von read(), die gezielt in einem Bereich des Puffers liest. Die Methode public void write(byte[] buffer, int beginn, int laenge) throws IOException schreibt einen Puffer beginnend mit Position beginn bis zur mit laenge ange-
gebenen Anzahl von Bytes in den Ausgabestrom. Für sämtliche read()-Methoden werden Sie in der gleichen Klasse bzw. Schnittstelle eine passende write()-Methode finden. Mehr zu den Details finden Sie in der Online-Dokumentation. Spielen wir auch hier ein komplettes Beispiel durch, das einige der erklärten Techniken in der Praxis zeigt. Das Programm liest die als ersten Parameter angegebene Datei aus und schreibt die Daten in die als zweiten Parameter angegebene Datei. Dabei ist es gegen Fehlbedienung über eine Ausnahmebehandlung abgesichert. Es handelt sich also um eine Nachprogrammierung eines Kopierbefehls – allerdings vollkommen plattformneutral! /* Ein Kopierprogramm, das eine Datei Zeichen für Zeichen liest und dann in eine Zieldatei schreibt. */ import java.io.*; public class KopiereDatei { /* Die Kopiermethode. Als Übergabeargumente werden die Quelldatei und die zu erstellende Zieldatei verwendet. Diese Parameter müssem beim Aufruf angegeben werden.
Java 2 Kompendium
Listing 13.2: Kopieren einer Datei
705
Kapitel 13
Ein- und Ausgabe in Java Das wird über das Exception-Handling direkt in der main()-Methode sichergestellt. */ public static void kopiere(String quelldatei, String zieldatei) throws IOException { // Erstellen eines Eingabestroms // und eines Ausgabestroms DataInput quelle = new DataInputStream(new FileInputStream(quelldatei)); DataOutput ziel = new DataOutputStream(new FileOutputStream(zieldatei)); try { while(true) { // Einlesen eines Char aus der Quelle char zeichen = quelle.readChar(); // Schreibe in Zieldatei ziel.writeChar(zeichen); }} // Dateiende der Quelldatei erreicht. catch (EOFException e) { System.out.println( "Alles klar! Kopieren der Datei beendet."); } catch (IOException e) { // allgemeiner IO-Fehler System.out.println(e.getMessage()); } } public static void main (String args[]) throws IOException { // Fange fehlende Übergabeparameter ab try { kopiere(args[0], args[1]); } catch (ArrayIndexOutOfBoundsException uebergabeEx) { // Keine Übergabeparameter. System.out.println("Das Programm benoetigt zwei Uebergabeparameter."); System.out.println( "Sie muessen folgende Syntax eingeben:"); System.out.println("java KopiereDatei [Name der Quelldatei] [Name der Zieldatei]"); } } }
13.3.2
Den gepufferten Cache ausgeben
Abhängig vom Datenstrom müssen Sie den Datenstrom gelegentlich leeren, wenn Sie sicher gehen wollen, dass die Daten, die Sie in den Strom geschrieben haben, angekommen sind. Das Leeren eines Stroms zerstört keine Informationen im Strom, es soll nur sicherstellen, dass alle Daten, die in internen Puffern gespeichert sind, an diejenige Stelle geschrieben werden, an die der Strom gerade angebunden ist. Um einen Ausgabestrom zu leeren, müssen Sie einfach nur die Methode public void flush() throws IOException aufrufen.
706
Java 2 Kompendium
Byte-Datenfeldströme
13.3.3
Kapitel 13
Ressourcen freigeben
Wenn Sie mit einem Strom fertig sind, sollten Sie genau wie bei Eingabeströmen auch die Ausgabeströme mit der Methode public void close() throws IOException wieder explizit schließen, was Sie so auch in den im Kapitel verwendeten Beispielen sehen. Diese Vorgehensweise ist wieder nicht unbedingt zwingend, denn die meisten Ströme werden automatisch bei der Garbage Collection oder mit einer geeigneten finalize()-Methode geschlossen. Es gelten jedoch die gleichen Gründe wie bei Eingabeströmen, warum man es dennoch tun sollte.
13.4
Byte-Datenfeldströme
Man muss nicht unbedingt in eine Datei oder in das Netzwerk schreiben, um Ströme zu benutzen. Sie können auch unter Verwendung der ByteArrayInputStream- und ByteArrayOutputStream-Klassen Datenfelder aus Bytes lesen und schreiben. Für Byte-Datenfeldströme stehen die gleichen Standardmethoden wie bei allen Datenströmen zur Verfügung. Es handelt sich bei der Verwendung der ByteArrayInputStream-Technik um ein Beispiel für die explizite Erstellung von Eingabeströmen. Sie nutzen also nicht einen Eingabestrom, der einfach schon »da« ist, sondern Sie müssen ein Datenfeld mit Bytes haben, das als Byte-Quelle dient, die vom Strom gelesen wird. public ByteArrayInputStream(byte[] buffer) erstellt einen Byte-Eingabestrom durch die Verwendung des gesamten Inhalts der Bytes als Daten im Strom.
Die Methode public ByteArrayInputStream(byte[] buffer, int beginn, int laenge) erstellt einen Byte-Eingabestrom der bis zu laenge Bytes liest, angefangen mit der Position beginn. Beispiel: eingabeStrom meinStrom = new ByteArrayInputStream(byte[] buffer, 100, 200);
In dem Beispiel ist der Strom 200 Byte lang und enthält die Bytes 100 bis 299 aus dem Array buffer. Das Gegenstück zu dem ByteArrayInputStream ist ein ByteArrayOutputStream, ein Datenfeld mit Bytes. Der ByteArrayOutputStream gibt Bytes kontinuierlich an einen Puffer weiter, die darin gespeichert werden. Der Konstruktor der ByteArrayOutputStream-Klasse nimmt einen optionalen Parameter für die
Java 2 Kompendium
707
Kapitel 13
Ein- und Ausgabe in Java Anfangsgrö ß e, der die Anfangsgrö ß e des Datenfeldes festlegt, das die in den Strom geschriebenen Bytes speichert: public ByteArrayOutputStream() public ByteArrayOutputStream(int initialSize)
Nachdem Sie Daten in einen ByteArrayOutputStream geschrieben haben, können Sie ihn auf verschiedene Art weiter verwenden. Sie können beispielsweise den Inhalt des Stroms zu einem Datenfeld mit Bytes konvertieren, indem Sie die Methode public synchronized byte[] toByteArray() aufrufen. Eine andere Verwendung ist die Weitergabe an einen anderen Datenstrom mittels der Methode public void writeTo(OutputStream out) throws IOException. Die Methode public String toString() konvertiert den Datenstrom (wie bei allen Objekten) in eine Zeichenkette. Die ByteArrayOutputStream-Klasse besitzt mit der size()-Methode eine Möglichkeit zur Bestimmung der Anzahl an Bytes, die bis dahin in den Strom geschrieben wurden. Die reset()-Methode setzt das Array an den Anfang zurück.
13.5
Der StringBufferInputStream
Der StringBufferInputStream ist dem ByteArrayInputStream sehr ähnlich. Der einzige Unterschied zwischen den beiden liegt darin, dass der Konstruktor für den StringBufferInputStream als Quelle eine Zeichenkette anstelle eines Datenfeldes mit Bytes verwendet: public StringBufferInputStream(String zeichenkette)
Beispiel: String buffer = "Die Antwort ist ... 42"; InputStream meinEingabeStrom = new StringBufferInputStream(buffer);
13.6
Gefilterte Ströme
Die Technik der gefilterten Eingabe-Ströme bietet alle Methoden der normalen InputStream-Klasse. Eine der wichtigsten Eigenschaften von gefilterten Strömen ist die Möglichkeit des Anhängens von Strömen an das Ende eines anderen Stroms. Das bedeutet, dass die Ströme verschachtelt werden und der Filter (sinnvollerweise die äußere Verschachtelungsebene) nur die Daten des inneren Stroms durchlässt, die im Filter eingestellt sind. 708
Java 2 Kompendium
Gepufferte Ströme
Kapitel 13
Der einfache Eingabestrom hat zum Beispiel nur die read()-Methode zum Lesen von Bytes. Wenn Sie Zeichenketten und Zahlen lesen wollen, können Sie einen speziellen gefilterten Dateneingabestrom mit dem Eingabestrom verbinden. Damit haben Sie Methoden zur Verfügung, mit denen Sie Zeichenketten, ganze Zahlen und sogar Gleitkommazahlen lesen und sie nach Typ trennen können. Die Klassen FilterInputStream und FilterOutputStream ermöglichen das Verknüpfen von Strömen. Allerdings stellen sie keine neuen Methoden bereit. Ihren Nutzen erzielen sie einfach daraus, dass sie mit einem anderen Strom verbunden sind. Die Konstruktoren für den FilterInputStream und Filter-OutputStream haben deshalb InputStream- und OutputStream-Objekte als Parameter: public FilterInputStream(InputStream in) public FilterOutputStream(OutputStream out)
Da diese Klassen selbst Instanzen von InputStream und OutputStream sind, können Sie als Parameter für Konstruktoren anderer Filter dienen, was die Konstruktion langer Ketten von Ein- und Ausgabefiltern ermöglicht. Beispiel: meinEingabeStrom2 = new FilterInputStream(new FilterInputStream(meinEingabeStrom1));
Eine sehr wichtige Subklasse von FilterInputStream ist BufferedInputStream.
13.7
Gepufferte Ströme
Gepufferte Ströme fungieren als Cache für Lese- und Schreibvorgänge und tragen damit in vielen Fällen zur Beschleunigung eines Programms bei. Statt in vielen kleinen Schreib- oder Leseoperationen werden die Bytes in großen Blöcken gesammelt und dann auch bei den Schreib- oder Leseoperationen als großer Block behandelt. Gepufferte Ströme implementieren die vollen Fähigkeiten der normalen InputStream/OutputStream-Methoden. Sie unterstützen hervorragend das Markieren von Stellen im Strom (die Methoden mark() und reset()). Gepufferte Ströme werden mit den Klassen BufferedInputStream für die Eingabe und BufferedOutputStream für die Ausgabe realisiert. Wenn Sie sie erstellen, können Sie eine Puffergrö ß e (buffergroesse) angeben: public BufferedInputStream(InputStream in) public BufferedInputStream(InputStream in, int buffergroesse)
Java 2 Kompendium
709
Kapitel 13
Ein- und Ausgabe in Java public BufferedOutputStream(OutputStream out) public BufferedOutputStream(OutputStream out, int buffergroesse)
Die BufferedInputStream-Klasse versucht in einem einzigen read()-Aufruf so viele Daten wie möglich in ihren Puffer einzulesen. Die BufferedOutputStream-Klasse ruft nur dann die write()-Methode auf, wenn ihr Puffer voll ist oder wenn die flush()-Methode aufgerufen wird. Sie können analog zu gefilterten Strömen andere Ströme mit gepufferten Strömen verbinden, um diese damit in gewisser Weise »zu filtern«. Auch eine Kombination mit gefilterten Strömen ist möglich und sehr oft sinnvoll. Beispiel: meinEingabeStrom2 = new BufferedInputStream(new FilterInputStream(meinEingabeStrom1));
13.8
Datenströme
Die Filterklassen DataInputStream und DataOutputStream sind zwei der nützlichsten Filter des java.io-Paketes. Sie ermöglichen es, primitive Typen in Java auf maschinenunabhängige Art und Weise zu lesen und zu schreiben. Die Klassen DataInputStream und DataOutputStream kümmern sich selbstständig um die notwendigen Konvertierungen. Alle Methoden dieser Klassen sind in zwei separaten Schnittstellen definiert, die sowohl von DataInputStream bzw. DataOutputStream als auch von einer weiteren Klasse des java.io-Pakets – RandomAccessFile – implementiert wird. Die Schnittstellen sind so allgemein, dass sie im Prinzip von jeder Klasse benutzt werden können. Es handelt sich um die DataInput-Schnittstelle bzw. DataOutput-Schnittstelle.
13.8.1
Die DataInput-Schnittstelle
Normalerweise bieten Byteströme kein Format. Damit haben Sie Probleme, wenn Sie primitive Datentypen direkt einlesen wollen. Die Klasse DataInputStream implementiert deshalb eine DataInput-Schnittstelle, die Methoden zum Lesen von primitiven Datentypen in Java definiert. Zusätzlich gibt es noch ein paar weitere Methoden. Die
ausgeworfenen Ausnahmen sind vom Typ IOException oder EOFException. IOException gilt für alle read()-Methoden, EOFException für fast alle (außer readLine(), readUTF() und skipBytes()). Zu EOFException gibt es noch eine nützliche Bemerkung: EOFException wird ausgeworfen, wenn das Ende des Stroms erreicht ist. Diese Ausnahme lässt sich überall da ein-
710
Java 2 Kompendium
Datenströme
Kapitel 13
setzen, wo bisher auf -1 überprüft wurde. Damit können viele Sourcecodes übersichtlicher gestaltet werden. Wir werden die Ausnahme gleich in einem Beispiel einsetzen. Die DataInput-Methoden zum Lesen primitiver Datentypen sind folgende: public public public public public public public public public public public
boolean readBoolean() byte readByte() byte readUnsignedByte() char readChar() short readShort() short readUnsignedShort() int readInt() long readLong() float readFloat() double readDouble() String readUTF()
Es fällt wahrscheinlich auf, dass die Methode zum Einlesen von Zeichenketten readUTF() heißt. UTF bedeutet Unicode Transmission Format und ist das spezielle Format zum Kodieren von 16-Bit-Unicode-Werten, was unter Java verwendet wird. ASCII-Code kann aber genauso damit gelesen werden. Die beiden mit »Unsigned« bezeichneten Methoden arbeiten wie ihr Gegenstück ohne diesen Zusatz, können jedoch zu einer effizienteren Verwendung der Bits in einem Bytestrom eingesetzt werden, wenn das Vorzeichen nicht von Interesse ist. Wenn Sie Daten aus einer Textdatei lesen, wird meistens eine Zeile durch eine Zeilenschaltung begrenzt. Zum Lesen einer so begrenzten Zeile gibt es die Methode public String readLine() throws IOException. Sie ließt in einer Zeile einer Textdatei, die durch \r, \n oder das Ende der Datei terminiert wird, wobei das \r, \n oder \r\n entfernt wird, bevor die Zeile als Zeichenkette wiedergegeben wird. Wenn Sie versuchen, mit der normalen read()-Methode aus der InputStream-Klasse eine fixe Anzahl an Bytes in einem Datenfeld zu lesen, kann es vorkommen, dass Sie diese mehrmals aufrufen müssen. Dies hat in der Regel zur Folge, dass bereits eine Ausgabe beginnt, bevor alle angeforderten Bytes gelesen wurden. Denken Sie nur an den Datentransfer über ein Netzwerk wie das Internet. Wenn Sie das explizit nicht wünschen, hilft Ihnen die readFully()-Methode, die es in zwei Ausprägungen gibt. Diese wartet explizit auf alle Bytes, die Sie verlangt haben: public void readFully(byte[] buffer) throws IOException public void readFully(byte[] buffer, int beginn, int länge) throws IOException
Java 2 Kompendium
711
Kapitel 13
Ein- und Ausgabe in Java Die Methode public int skipBytes(int anzahlBytes) erfüllt eine ähnliche Funktion wie die readFully()-Methode. Sie wartet, bis die gewünschte Anzahl Bytes übersprungen wurde, bevor sie zurückkehrt.
13.8.2
Die DataOutput-Schnittstelle
Die DataOutput-Schnittstelle ist das Gegenstück zu DataInput-Schnittstelle und definiert die Ausgabemethoden, die den Eingabemethoden entsprechen, die dort definiert wurden. Die durch diese Schnittstelle definierten Methoden sind (alle werfen eine IOException aus): public public public public public public public public public
void void void void void void void void void
writeBoolean(boolean b) writeByte(int b) writeChar(int c) writeShort(int c) writeInt(int i) writeLong(long l) writeFloat(float f) writeDouble(double d) writeUTF(String s)
Die vorzeichenlosen Lesemethoden (Unsigned) haben kein direktes Gegenstück. Durch die Verwendung der Methoden public void writeBytes(String s) throws IOException und public void writeChars(String s) throws IOException können Sie eine Zeichenkette als eine Reihe von Bytes oder Zeichen schreiben. Die Schnittstelle definiert ebenso die vorher schon beschriebenen folgenden Methoden: public abstract void write(int b) throws IOException public void write(byte[] buffers) throws IOException public void write(byte[] buffer, int beginn, int laenge) throws IOException.
13.8.3
Die DataInputStream- und DataOutputStream-Klassen
Bei den Klassen DataInputStream und DataOutputStream handelt es sich einfach um Stromfilter, die die DataInput- und DataOutput-Schnittstellen implementieren. Ihre Konstruktoren sind typische Stromfilter-Konstruktoren, da sie einfach den zu filternden Strom als Parameter verwenden: public DataInputStream(InputStream in) public DataOutputStream(OutputStream out)
712
Java 2 Kompendium
Die PrintStream-Klasse
13.9
Kapitel 13
Die PrintStream-Klasse
Methoden der PrintStream-Klasse haben wir schon benutzt, ohne es direkt so zu bezeichnen. Der System.out-Strom ist beispielsweise eine Instanz von PrintStream. Die dazu gehörenden Methoden System.out.print() und System.out.println() kennen wir. Die PrintStream-Klasse ermöglicht im Allgemeinen das Schreiben ausgebbarer Versionen verschiedener Objekte in einen Ausgabestrom. Dabei verwenden Sie die Variable out der System-Klasse. System.err gehört ebenfalls zu der PrintStream-Klasse, System.in ist ein InputStream. Wenn Sie ein PrintStream-Objekt erstellen, müssen Sie es an einen bereits existierenden Augabestrom hängen, da es sich um einen FilterOutputStream handelt. Sie können einen optionalen Parameter zum automatischen Leeren angeben, der, falls true, den Strom automatisch dazu bringt, immer die flush()-Methode aufzurufen, wenn sie eine neue Zeile ausgibt: public PrintStream(OutputStream out) public PrintStream(OutputStream out, boolean autoFlush)
Die Methode flush() wird genauso wie close() und write() in PrintStream implementiert. Dazu gibt es eine Fülle von Möglichkeiten zur Ausgabe von Primitivtypen. Die PrintStream-Klasse besitzt für die Ausgabe von Objekten folgende Methoden: public public public public public public public public public public public public public public public public public public public public public
void flush() void close() abstract void write(int b) void write(byte[] buffer, int beginn, int laenge) void print(Object o) void print(String s) void print(char[] buffer) void print(boolean b) void print(char c) void print(int i) void print(long l) void print(float f) void print(double d) void println(Object o) void println(String s) void println(char[] buffer) void println(boolean b) void println(char c) void println(int i) void println(long l) void println(float f)
Java 2 Kompendium
713
Kapitel 13
Ein- und Ausgabe in Java public void println(double d) public void println()
Der einzige Unterschied zwischen den Methoden print() und println() ist der, dass die println()-Methode bei der Ausgabe immer einen Zeilenvorschub erzeugt, die reine print()-Methode nicht. Die println()-Methode ohne Parameter gibt einfach eine neue Zeile aus.
13.10
Pipe-Ströme
Die PipedInputStream und PipedOutputStream-Klassen ermöglichen Ihnen einen Eingabestrom direkt mit einem Ausgabestrom zu verbinden. Wenn Sie es normalerweise mit Strömen zu tun haben, ist die Quelle oder der Bestimmungsort von Daten eine externe Datei oder das Netzwerk. Wenn Sie Pipes verwenden, ist Ihr Programm sowohl Quelle als auch Empfangsort von Daten. Die Technik ist Unix-entlehnt und schafft eine direkte Verbindung von Strömen, aus denen ein und dasselbe Programm sowohl liest als auch Daten in sie schreibt. Das wird im Wesentlichen für die Kommunikation zwischen Threads verwendet. Eine andere denkbare Anwendung ist das Testen beider Enden eines Netzwerk-Protokolls mit einem einzelnen Programm. Wem noch das Pipe-Symbol aus DOS bekannt ist, wird vermuten, wozu diese Technik noch dienen kann. Es mag trivial erscheinen, ist jedoch in diesem Fall so wichtig, dass es noch einmal extra erwähnt werden soll: Bei der Verwendung mit verschiedenen Threads ist ein sorgfältige Synchronisation unabdingbar. Wenn Sie einen PipedInputStream oder PipedOutputStream erstellen, können Sie den Strom angeben, mit dem er verbunden werden soll. Wenn Sie im Konstruktor keinen Strom angeben, ist der Strom nicht verbunden und muss erst mit irgendeinem Strom verbunden werden, bevor er benutzt werden kann. Die Konstruktoren für PipedInputStream und PipedOutputStream sind folgende: public public public public
PipedInputStream() PipedInputStream(PipedOutputStream ausgabeStream) PipedOutputStream() PipedOutputStream(PipedInputStream eingabeStream)
Die Methode public void connect(PipedInputStream eingabeStream) throws IOException in PipedOutputStream verbindet den Strom mit einem Eingabestrom.
714
Java 2 Kompendium
Objektströme
Kapitel 13
Eine Pipe-Verbindung zwischen zwei Threads können Sie folgendermaßen herstellen: PipedInputStream inThread = PipedInputStream(); PipedOutputStream outThread = PipedInputStream(inThread);
Der eine Thread schreibt in outThread und der andere liest von inThread. Ein solches Pärchen ermöglicht eine problemlose Kommunikation zwischen Threads.
13.11
Objektströme
Die Strom-Technik erlaubt es, beliebige Objekte an einen Strom zu schicken oder sie aus ihm zu lesen. Dazu dienen die Schnittstellen ObjectInput und ObjectOutput. Sie definieren Methoden zum Lesen und Schreiben beliebiger Objekte. Die Methoden sind mit den Methode zum Lesen und Schreiben primitiver Typen eng verwandt. Die Schnittstellen ObjectInput und Object-Output erweitern sogar die Schnittstellen DataInput und DataOutput. Die Schnittstelle ObjectInput hat nur eine zusätzliche Eingabe-Methode mit zwei Typen von Ausnahmen: public abstract Object readObject() throws ClassNotFoundException public abstract Object readObject() throws IOException
Die Schnittstelle ObjectOutput hat ebenfalls eine zusätzliche AusgabeMethode: public abstract void writeObject(Object obj) throws IOException
Der ObjectOutputStream implementiert einen Stromfilter, der es Ihnen ermöglicht, sowohl jedes Objekt in einen Strom zu schreiben als auch jeden primitiven Typen. Wie bei den meisten Stromfiltern können Sie einen ObjectOutputStream durch das Angeben eines OutputStream erstellen: public OutputStream(OutputStream ausgabeStream)
Mittels der Methode writeObject (drei Ausnahmetypen) können Sie jedes Objekt in einen Strom schreiben: public final void writeObject(Object ob) throws ClassMismatchException public final void writeObject(Object ob) throws MethodMissingException public final void writeObject(Object ob) throws IOException
Da der ObjectOutputStream eine Unterklasse des DataOutputStream ist, können Sie alle Methoden der DataOutput-Schnittstelle verwenden, wie beispielsweise writeInt() oder writeUTF().
Java 2 Kompendium
715
Kapitel 13
Ein- und Ausgabe in Java
13.12
Einige spezielle Utility-Ströme
Java stellt eine Reihe von Utility-Filtern zur Verfügung. Diese Filter sind deshalb etwas Besonderes, da sie nicht paarweise existieren, was heißt, dass sie ausschließlich für Eingabe oder Ausgabe arbeiten.
13.12.1
Die LineNumberInputStream-Klasse
Der LineNumberInputStream ermöglicht es, die aktuelle Zeilenzahl eines Eingabestroms zu verfolgen. Dies ist etwa in einem Editor oder Debugger von Bedeutung. Der Filterstrom LineNumberInputStream kann sich sogar eine Zeilennummer merken und sie später in Verbindung mit mark() und reset() verwenden. Der Konstruktor sieht so aus: public LineNumberInputStream(InputStream eingabeStream)
Die Methode public int getLineNumber() gibt die derzeitige Zeilennummer des Eingabestroms aus. Die Zeilen werden mit 0 beginnend durchnummeriert. Die Zeilenzahl wird immer dann erhöht, wenn eine komplette Zeile gelesen wurde. Die aktuelle Zeilenzahl kann jedoch auch mit der Methode public void setLineNumber(int neueZeilenzahl) festgelegt werden.
13.12.2
Die SequenceInputStream-Klasse
Die SequenceInputStream-Klasse stellt Möglichkeiten zur Verfügung, eine ganze Reihe von Eingabeströmen wie einen einzigen großen Eingabestrom zu behandeln. Etwa bei umfangreichen Leseaktionen über mehrere Ströme hinweg, wenn eine Trennung von verschiedenen Eingabeströmen irrelevant ist. Einen SequenceInputStream, der zwei Ströme verbindet, können Sie erstellen, indem Sie beide Ströme im Konstruktor angeben: public SequenceInputStream(InputStream stream1, InputStream stream2)
Wenn Sie mehr als zwei Ströme haben wollen, können Sie eine Auflistung der Ströme in Form eines Vektors angeben. Dazu gibt es in Java die Klasse Vector, die natürlich auch noch allgemeiner eingesetzt werden kann. Beispiel: Vector v = new Vector(); v.addElement(stream1); v.addElement(stream2); v.addElement(stream3); v.addElement(stream4); InputStream seq = new SequenceInputStream(v.elements());
716
Java 2 Kompendium
Einige spezielle Utility-Ströme
Kapitel 13
Wenn Sie Ströme miteinander kombinieren wollen, können Sie auf diese Art eine Kette von Strömen erstellen. Beispiel: InputStream seq = new SequenceInputStream(stream1, new SequenceInputStream(stream2, stream3));
13.12.3
Die PushbackInputStream-Klasse
Der PushbackInputStream ist ein spezieller Strom, der es Ihnen erlaubt, ein einzelnes Zeichen eines Eingabestromes zu betrachten und dieses dann wieder in den Strom zurückzuschieben, um die nächste Aktion zu ermitteln. Anwendungen für diese Methode sind beispielsweise Suchroutinen in einer Indexdatenbank. Die Verwandtschaft mit einem gefilterten Eingabestrom ist naheliegend. Der Konstruktor sieht so aus: public PushbackInputStream(InputStream eingabeStream)
Die Methode public void unread(int ch) throws IOException übergibt ein Zeichen wieder zurück in den Eingabestrom. Dieses Zeichen ist dann das erste, das beim nächsten Mal eingelesen wird, wenn der Eingabestrom erneut gelesen wird.
13.12.4
Die StreamTokenizer-Klasse
Die StreamTokenizer-Klasse hat einen einfachen lexikalischen Abtaster, der einen Zeichenstrom in einen Tokenstrom zerstückelt. Wenn Sie sich einen Zeichenstrom wie einen Satz vorstellen, dann stellen die Token die einzelnen Worte und Satzzeichen dar, aus denen der Satz besteht. Sie erstellen einen StreamTokenizer-Filter, indem Sie ihm den Eingabestrom nennen, den Sie gefiltert haben wollen: public StringTokenizer(InputStream inStream)
Nachdem Sie den Filter erstellt haben, können Sie die Methode public int nextToken() throws IOException dazu verwenden, diese Token aus dem Strom zu holen. Die nextToken()-Methode gibt entweder ein einzelnes Zeichen oder eine der folgenden Konstanten wieder: StreamTokenizer.TT_WORD StreamTokenizer.TT_NUMBER StreamTokenizer.TT_EOL StreamTokenizer.TT_EOF
Java 2 Kompendium
717
Kapitel 13
Ein- und Ausgabe in Java
13.13
Die File-Klasse
Als eine der letzten in diesem Kapitel vorgestellten Strom-Klassen wollen wir zu einer der wichtigsten (und historisch ältesten) Verwendungen von Strömen kommen – das Anhängen von Strömen an Dateien im Dateisystem des Rechners. Dies dürfte auch die Ein- und Ausgabefunktionalität von Java sein, die die meisten Leser interessiert. Java stellt die File-Klasse dafür zur Verfügung. Sie kapselt Operationen in Bezug auf das Dateisystem. Darunter fallen die Auflistung des Inhalts von Verzeichnissen, die Erstellung von Verzeichnissen, das Löschen von Dateien oder deren Umbennenung. Abfragen von Datei-Informationen sind andere wichtigere Operationen. Ein File-Objekt kann sich normalerweise entweder auf eine Datei oder ein Verzeichnis beziehen. Es gibt aber auch Operationen, die sich nur entweder auf eine Datei oder ein Verzeichnis ausführen lassen. Ein File-Objekt lässt sich auf drei verschiedene Arten erstellen: public File(String pfadname) public File(String pfadname, String dateiname) public File(File verzeichnis, String dateiname)
1.
Die erste Anweisung erstellt eine File-Instanz, die in pfadname angegeben wurde.
2.
Die zweite Anweisung erstellte eine File-Instanz, die sich aus dem in dateiname angegebenen Dateinamen und dem in pfadname angegebenen Pfad zusammensetzt.
3.
Die dritte Anweisung erstellt eine File-Instanz, die sich aus dem in dateiname angegebenen Dateinamen und dem in verzeichnis angegebenen Verzeichnis zusammensetzt.
13.13.1
Überprüfung, ob Datei oder Verzeichnis
Viele Operationen der File-Klasse sind unabhängig davon, ob es sich bei dem Objekt um eine Datei oder ein Verzeichnis handelt. Bei anderen Methoden macht es jedoch nur dann Sinn, wenn sie entweder auf eine Datei oder ein Verzeichnis angewandt werden. Sie benötigen also einfache Methoden, mit welchen Sie dies überprüfen können. Dies sind die Methoden public boolean isFile() und public boolean isDirectory().
718
Java 2 Kompendium
Die File-Klasse
13.13.2
Kapitel 13
Lese- und Schreiberlaubnis überprüfen
Genauso wichtig ist die Überprüfung der Dateiattribute, insbesondere der Lese- und Schreiberlaubnis. Mit den Methoden public boolean canRead() und public boolean canWrite() können Sie ermitteln, ob eine Datei oder ein Verzeichnis lesen dürfen und/oder ob Sie dort eine Schreiberlaubnis haben.
13.13.3
Die letzte Änderung überprüfen
Ein anderes wichtiges Dateiattribut können Sie mit der Methode public long lastModified() kontrollieren. Sie gibt eine Zahl aus, die anzeigt, wann die Datei oder das Verzeichnis zuletzt geändert wurde. Da sich der von der lastModified()-Methode ausgegebene Wert in unterschiedlichen Formaten darstellen kann, ist die Methode hauptsächlich zum relativen Vergleich zweier Dateien zu gebrauchen.
13.13.4
Die Existenz eines Objekts überprüfen
Ein File-Objekt muss nach der Erstellung nicht unbedingt als Datei oder Verzeichnis physikalisch existieren. Es kann nur durch einen Dateinamen dargestellt werden. Die Methode public boolean exists() überprüft die physikalische Existenz einer Datei oder einer Verzeichnisses.
13.13.5
Pfadkontrolle
Pfadnamen können entweder relativ oder absolut sein. Ein relativer Pfad bedeutet, dass der Pfad vom aktuellen Verzeichnis aus gesehen wird, während eine absolute Pfadangabe von der Wurzel des Systems ( unter Umständen über lokale Rechner hinaus) ausgeht. Mit der Methode public boolean isAbsolute() lässt sich herausfinden, ob ein gegebenes File-Objekt einen relativen oder absoluten Pfad verwendet.
13.13.6
Namen und Pfad einer Datei oder eines Verzeichnisses ermitteln
Den Namen einer Datei oder eines Verzeichnisses ohne den davor stehenden Pfadnamen liefert die folgende Methode: public String getName()
Zur Bestimmung des Namens des Verzeichnisses, in dem das File-Objekt enthalten ist, dient die folgende Methode: public String getParent()
Java 2 Kompendium
719
Kapitel 13
Ein- und Ausgabe in Java Die Methode public String getPath() gibt den Namen des File-Objekts mit dem davor stehenden Pfadnamen aus, egal ob relativ oder absolut. Den absoluten Pfadnamen eines File-Objekts bekommen Sie mit public String getAbsolutePath().
13.13.7
Eine Datei oder ein Verzeichnis umbenennen
Um eine Datei oder ein Verzeichnis umzubenennen, können Sie in der Methode public boolean renameTo(File neuerName) ein File-Objekt angeben, das den neuen Namen enthält. Die Methode gibt true zurück, wenn die Umbenennung erfolgreich war.
13.13.8
Dateien löschen
Zum Löschen einer Datei können Sie die Methode public boolean delete() verwenden. Die Methode gibt true zurück, wenn die Beseitung erfolgreich war. In bester (?) DOS-Tradition (oder Unix, aber über DOS lässt sich besser lästern :*) ) kann ein Verzeichnis damit jedoch nicht gelöscht werden.
13.13.9
Verzeichnisse erstellen
In Java kann man über die Methode public boolean mkdir() ein Verzeichnis erstellen. Die mkdir()-Methode behandelt das aktuelle File-Objekt wie einen Verzeichnisnamen und versucht, ein Verzeichnis für diesen Namen zu erstellen. Bei erfolgreicher Erstellung eines Verzeichnisses wird true ausgegeben. Die Methode public boolean mkdirs() ist eine spezielle Variante von der mkdir()-Methode. Im Unterschied zu dieser erstellt sie alle notwendigen Verzeichnisse für den im File-Objekt benannten Pfad. Wenn in der Pfadangabe also Verzeichnisnamen auftauchen, die noch nicht vorhanden sind, werden sie erstellt und das eigentliche Zielverzeichnis existiert dann dort als Unterverzeichnis. Bei erfolgreicher Erstellung eines Verzeichnisses oder einer gesamten Struktur wird true ausgegeben.
13.13.10 Den Inhalt eines Verzeichnisses angeben Die nur in einem Verzeichnis zu gebrauchende Methode public String[] list() gibt ein Datenfeld der Namen aller Dateien wieder, die in dem Verzeichnis enthalten sind. Sie können für die list()-Methode einen Dateinamen-Filter einrichten, der Ihnen die Auswahl bestimmter Dateinamen ermöglicht: public String[] list(FilenameFilter filter)
720
Java 2 Kompendium
Dateiströme – FileInputStream und FileOutputStream
Kapitel 13
Die Schnittstelle FilenameFilter hat nur eine einzige Methode, nämlich public abstract boolean accept(File verzeichnis, String name), die true ausgibt, wenn der Liste ein Dateiname zugefügt werden soll. Wir wollen es uns an einem Beispiel ansehen. Das folgende Beispiel implementiert einen Dateinamen-Filter, der nur Dateien zulässt, die die Endung .java haben. Das Ergebnis wird auf dem Bildschirm ausgegeben und funktioniert auf beliebigen Plattformen. import java.io.*; public class ListJava extends Object { public static void main(String[] args) { // Generiert eine File-Instanz für das // aktuelle Verzeichnis File currDir = new File("."); // Eine gefilterte Liste der .java-Dateien // im aktuellen Verzeichnis String[] javaDat = currDir.list(new JavaFilter()); // Ausgabe des Inhalts des javaDat-Arrays for (int i=0; i < javaDat.length; i++) { System.out.println(javaDat[i]); } } } class JavaFilter extends Object implements FilenameFilter { public boolean accept(File verzeichnis, String name) { // Nur dann true, wenn die Datei mit java endet. return name.endsWith(".java"); } }
13.14
Listing 13.3: Ausgabe aller Dateien mit Kennung .java
Dateiströme – FileInputStream und FileOutputStream
Die Dateiströme FileInputStream und FileOutputStream ermöglichen das Lesen und Schreiben von Dateien. Genau genommen werden Ströme an Dateien im Dateisystem angehängt. Dateiströme können aus einer Dateinamen-Zeichenkette, einer File-Instanz oder einem speziellen Datei-Beschreiber erstellt werden: public public public public public public
FileInputStream(String dateiname) FileInputStream(File datei) FileInputStream(FileDescriptor dateibeschreiber) FileOutputStream(String dateiname) FileOutputStream(File datei) FileOutputStream(FileDescriptor dateibeschreiber)
Um einen Eingabestrom zu erstellen, können Sie wie in folgendem Beispiel vorgehen: InputStream meinEingabeStream = new FileInputStream("meineDatei");
Java 2 Kompendium
721
Kapitel 13
Ein- und Ausgabe in Java Um einen Ausgabestrom zu erstellen, können Sie wie in folgendem Beispiel analog vorgehen: OutputStream meinAusgabeStream = new FileOutputStream("meineDatei");
Die FileDescriptor-Klasse enthält spezielle Informationen über eine geöffnete Datei. Sie erstellen einen FileDescriptor niemals selbst, sondern beziehen ihn durch das Aufrufen der Methode public final FileDescriptor getFD() throws IOException aus einem geöffneten FileInputStream oder FileOutputStream. Im Allgemeinen gibt getFD() den Bezeichner der Datei aus, auf der der Strom basiert. Die FileDescriptor-Klasse enthält eine einzige Methode, nämlich public boolean valid(), die true wiedergibt, wenn ein Datei-Descriptor gültig ist. Die FileDescriptor-Klasse enthält ebenfalls statische Instanzvariablen für die Datei-Descriptoren für Standard-Eingabe, Standard-Ausgabe und Standard-Fehler: public final static FileDescriptor in public final static FileDescriptor out public final static FileDescriptor err
13.14.1
Die RandomAccessFile-Klasse
Eine Random-Access-Datei1 ähnelt einem Eingabestrom in der Hinsicht, dass Sie Daten aus ihr lesen können. Gleichzeitig verhält sie sich aber auch wie ein Ausgabestrom, da man Daten in sie hineinschreiben kann (das besagte wahlfreie Verhalten). Der große Unterschied zwischen einer Random Access-Datei und einer sequenziellen Access-Datei (was ein Strom eigentlich ist) liegt darin, dass Sie sofort zu jedem Abschnitt einer Random Access-Datei gehen und dort lesen und schreiben können. Wenn Sie eine Random Access-Datei erstellen wollen, müssen Sie ihr einen Modus geben. Der Modus ist entweder r (read) oder rw (read-write). Wenn Sie eine Random Access-Datei im read-only-Modus öffnen, können Sie wie üblich keine Daten in sie hineinschreiben. Es gibt keinen write-only-Modus. Konstruktoren für die RandomAccessFile-Klasse sind: public RandomAccessFile(String filename, String mode) throws IOException public RandomAccessFile(File file, String mode) throws IOException
1
722
Random Access kennen Sie wahrscheinlich aus dem Begriff RAM für den Hauptspeicher – die mehr schlechte als rechte Übersetzung für Random Access ist »wahlfreier Zugriff«.
Java 2 Kompendium
Praktische Java-Beispiele mit Datenströmen
Kapitel 13
Die RandomAccessFile-Klasse besitzt alle Methoden, die in den Schnittstellen DataInput und DataOutput verfügbar sind. Zusätzlich beinhaltet sie eine Methode public void seek(long filePosition) throws IOException, die es Ihnen ermöglicht, sofort an jede beliebige Position in der Datei zu springen. Sie können auch die aktuelle Dateiposition bestimmen, was recht nützlich ist, wenn Sie vorhaben, an diese Dateiposition zurückzuspringen, indem Sie die Methode public long getFilePointer() throws IOException verwenden. Der Dateipositionswert, der in den Methoden seek() und getFilePointer() benutzt wird, ist die Anzahl der Bytes von Anfang bis zum Ende der Datei.
13.15
Praktische Java-Beispiele mit Datenströmen
Dieses Kapitel war bisher ziemlich umfangreich und bestand im Wesentlichen aus viel Theorie (ich habe am Anfang des Kapitels gewarnt). Nun soll der Theorie wieder mehr Praxis folgen, wobei gerade das Kapitel mit den erweiterten Java-Techniken noch diverse weitere Beispielen beinhaltet. Wir wollen zuerst ein Java-Programm erstellen, das eine Datei öffnet und die Daten nach dem Einlesen verarbeitet und dann wieder in eine andere Datei schreibt. Die dazu notwendigen Techniken haben wir uns mittlerweile erarbeitet. Zudem basiert das Programm auf unserem ersten Beispiel in diesem Kapitel. Es werden nur ein paar interessante Details verändert. Aber klären wir zuerst, was das Programm leisten soll: Eine beliebige Datei lesen Eine Datei erstellen, deren Name Sie frei wählen können Die Daten auf nützliche Art und Weise verarbeiten. Wir werden einen Verschlüsselungs- und Entschlüsselungsalgorithmus implementieren. Wir werden also unser Kopierprogramm vom Anfang in ein Kodierungsprogramm umschreiben, das beliebige Binärdateien (Texte, Grafiken, Programme usw.) kodieren und wieder entschlüsseln kann. Dazu verwenden wir dieses Mal auch andere Lese- und Schreibmethoden. Das Programm muss in unserem Fall nicht einmal Multithreading-fähig sein, da wir das Einlesen eines Bytes, das Kodieren/Dekodieren und das Schreiben chronologisch abarbeiten. Wir werden auch mehrere Varianten des Programms erstellen, die verschiedene Techniken nutzen. Ansonsten ist das erste Programm recht einfach gehalten. Die Übergabeparameter müssen weitgehend korrekt eingegeben werden. Hilfe ist nur eingeschränkt vorhanden. Das Dateiende wird zwar in einer catch-Klausel aufgefangen, I/O-Ausnahmen werden jedoch einfach weitergereicht und die Ausgabe nach getaner Arbeit beschränkt sich auf die Meldung »Alles klar! Kodierung beendet.« bzw. »Alles klar! Dekodierung beendet.«. Außerdem findet keine Kontrolle statt, ob die zu erstellende Datei evtl. schon vorhanJava 2 Kompendium
723
Kapitel 13
Ein- und Ausgabe in Java den ist und überschrieben wird. Nicht zuletzt ist eine grafische Oberfläche sicher ein lohnender Ansatz. Aber warten Sie damit erst einmal. Wir erweitern das Beispiel noch um Elemente des AWTs. Sie haben nun viele Ansatzpunkte, wie das Programm verbessert werden kann. Dennoch – es ist vom Ansatz her ein vollständiges Kodierungsprogramm. Der verwendete Algorithmus ist die Cäsar-Chiffre, ein einfacher Verschiebungsalgorithmus. Diese Verschlüsselungsmethode lautet wie folgt: Für jedes im Quelltext vorkommende Zeichen setze im Chiffretext ein um einen festen Parameter versetztes Zeichen. Wenn beispielsweise im Quelltext der erste Buchstabe des Alphabets – ein »A« – auftaucht und der Verschlüsselungsparameter »3« ist (angeblich der von Cäsar benutzte Parameter), wird im Chiffretext der vierte Buchstabe des Alphabetes – ein »D« – genommen usw. Das Dekodieren funktioniert genau umgekehrt. Zugegebenermaßen ist die Technik sehr einfach, bei binären Dateien ist die einfache Cäsar-Chiffre trotzdem wirkungsvoll. Durch die bereits in der Quelldatei vorkommenden Steuerzeichen ist ein Erraten der verwendeten Konstanten extrem erschwert. Überdies kann man alleine durch eine solche Verschiebung von Zeichen automatische Spionage-Tools in einem Netzwerk (z.B. dem Internet) austricksen, die Texte nach bestimmten Kennworten (z.B. Geheimzahl oder Kreditkartennummer) scannen. Mehr zu der Arbeitsweise und Theorie von Verschlüsselungsverfahren finden Sie im Anhang. Gehen wir zum eigentlichen Java-Source über.
Listing 13.4: Ein einfaches Kodierungsprogramm in Java
724
/* Ein Kodierungsprogramm für beliebige binäre Dateien */
import java.io.*; public class Kodier { /* Die Kodierungsmethode. Als Übergabeargumente werden die Quelldatei und die zu erstellende Zieldatei verwendet. */ public static void kodiere(String quelldatei, String zieldatei) throws IOException { // Erstellen eines Eingabe- und eines // Ausgabestroms DataInput quelle = new DataInputStream(new FileInputStream(quelldatei)); DataOutput ziel = new DataOutputStream(new FileOutputStream(zieldatei)); try { while(true) { // Einlesen eines Bytes aus der Quelle byte b = (byte) quelle.readByte(); /* Hier findet die Verschiebung statt. Wir kümmern uns selbst um den Wertebereich des Datentyps byte, indem wir den Modulo-Operator verwenden.*/ b = (byte)((b + 3)%256); // Schreiben eines Bytes in die Zieldatei ziel.writeByte(b); }} // Dateiende der Quelldatei erreicht. catch (EOFException e) {
Java 2 Kompendium
Praktische Java-Beispiele mit Datenströmen
Kapitel 13
System.out.println( "Alles klar! Kodierung beendet."); } } /* Die Dekodierungsmethode */ public static void dekodiere(String quelldatei, String zieldatei) throws IOException { // Erstellen eines Eingabe- und eines // Ausgabestroms DataInput quelle = new DataInputStream(new FileInputStream(quelldatei)); DataOutput ziel = new DataOutputStream(new FileOutputStream(zieldatei)); try { while(true) { // Einlesen eines Bytes aus der Quelle byte b = (byte) quelle.readByte(); /* Hier findet die Verschiebung zurück statt. Wir kümmern uns selbst um den Wertebereich des Datentyps byte, indem wir den Modulo-Operator verwenden. */ b = (byte)((b – 3)%256); ziel.writeByte(b); }} // Dateiende der Quelldatei erreicht. catch (EOFException e) { System.out.println( "Alles klar! Dekodierung beendet."); } } public static void main (String args[]) throws IOException { if ((args[2].equals("k")) ||(args[2].equals("K"))) { kodiere(args[0],args[1]); } else if ((args[2].equals("d")) ||(args[2].equals("D"))) dekodiere(args[0],args[1]); else { System.out.println( "Sie muessen folgende Syntax eingeben:"); System.out.println("java Kodier [Name der Quelldatei] [Name der Zieldatei] [Kodier/Dekodier]"); System.out.println("Dabei bedeutet [Kodier/Dekodier] Folgendes:"); System.out.println("Wenn Sie ein k oder K eingeben, kodieren Sie."); System.out.println("Wenn Sie ein d oder D eingeben, dekodieren Sie."); } } }
Probieren Sie das Programm ruhig mit beliebigen Dateien aus. Es sollte problemlos alle Dateien verarbeiten können. Beachten Sie, dass eine Hilfe bei zu wenig Übergabeparametern nicht unterstützt wird und eine nicht abgefangene Ausnahme als Rückmeldung erscheint.
Java 2 Kompendium
725
Kapitel 13
Ein- und Ausgabe in Java Wir wollen nun das Beispiel etwas erweitern. Dabei nehmen wir uns mehrerer Punkte an: Ein anderer Datenstrom. Diesmal arbeiten wir mit RandomAccessFiles zum Erstellen eines Eingabe- und eines Ausgabestroms. In der Kodier- und der Dekodier-Methode sichert ein doppelter tryBlock (try-catch und try-finally) die Aktionen ab. Im finally-Teil werden die Quell- und Zieldatei mit der close()-Methode geschlossen. Wir fragen die Grö ß e der zu kodierenden oder dekodierenden Datei ab und geben Sie in Byte aus. Wegen der UTF-Kodierung von Java muss der mit length() ermittelte Wert durch 2 geteilt werden. Wir werden diese Angabe hier noch nicht weiter verwenden. Denkbar (und meist sinnvoll) sind Statusanzeigen, die angeben, wie weit die Aktion gediehen ist. Der Kodierungsalgorithmus wird bei Textdateien sicherer. Die hier verwendete Verschiebung ist grö ß er als in unserem ersten Beispiel. Damit wird bei Textdateien eine bessere Verschleierung erreicht, da die Zeichen in der Zieldatei keine Buchstaben mehr sind. Nicht lesbare Zeichen lassen sich ohne Hilfsmittel schlechter entschlüsseln (obwohl es immer noch ein sehr einfaches Verfahren ist). Eine ganz entscheidende Erweiterung finden Sie in der main()-Methode. Diese gibt die von der Kodier- und Dekodier-Methode weitergereichten Ausnahmen nicht einfach weiter, sondern sie werden in dieser Version der main()-Methode abgefangen. Dazu dient der try-catch-Block der main()-Methode. Er fängt sowohl I/O-Ausnahmen als auch ArrayIndexOutOfBoundsException ab. Wenden wir uns nun dem konkreten Source zu.
Listing 13.5: Ein etwas aufwändigeres Kodierungsprogramm in Java
726
/* Ein Kodierungsprogramm für beliebige binäre Dateien */ import java.io.*; public class Kodier2 { /* Die Kodierungsmethode. Als Übergabeargumente werden die Quelldatei und die zu erstellende Zieldatei verwendet. */ public static void kodiere(String quelldatei, String zieldatei) throws IOException { /* Erstellen eines Eingabe- und eines Ausgabestroms. Dieses Mal arbeiten wir mit RandomAccessFiles */ // Quelle nur zum Lesen öffnen RandomAccessFile quelle= new RandomAccessFile(quelldatei,"r"); // Zu erstellende Datei mit Lese-/Schreibzugriff // öffnen RandomAccessFile ziel = new RandomAccessFile(zieldatei,"rw"); try { try { // Abfrage der Dateigrö ß e
Java 2 Kompendium
Praktische Java-Beispiele mit Datenströmen
Kapitel 13
long laenge = quelle.length(); System.out.println("Die Groesse der zu kodierenden Datei ist " + laenge + " Byte."); while(true) { // Einlesen eines Bytes aus der Quelle byte b = (byte) quelle.readByte(); /* Hier findet die Verschiebung statt. Wir kümmern uns selbst um den Wertebereich des Datentyps byte, indem wir den Modulo-Operator verwenden. Die hier verwendete Verschiebung ist grö ß er als in unserem ersten Beispiel. Damit wird bei Textdateien eine bessere Verschleierung erreicht, da die Zeichen keine Buchstaben mehr sind.*/ b = (byte)((b + 42)%256); // Schreiben eines Bytes in die Zieldatei ziel.writeByte(b); } } catch (EOFException e) { // Dateiende der Quelldatei erreicht. // Meldung aus dem catch-Teil System.out.println("Alles klar! EOFException zeigt Dateiende an. Kodierung beendet."); } } finally { quelle.close(); //Quelldatei schließen ziel.close(); // Erstellte Datei schließen // Meldung aus dem finally-Teil System.out.println("Der finally-Abschnitt ist auch beendet."); } } /* Die Dekodierungsmethode */ public static void dekodiere(String quelldatei, String zieldatei) throws IOException { /* Erstellen eines Eingabe- und eines Ausgabestroms. Hier sind auch wieder RandomAccessFiles verwendet */ // Quelle nur zum Lesen öffnen RandomAccessFile quelle= new RandomAccessFile(quelldatei,"r"); // Zu erstellende Datei mit Lese-/Schreibzugriff // öffnen RandomAccessFile ziel = new RandomAccessFile(zieldatei,"rw"); try { try { // Abfrage der Dateigrö ß e long laenge = quelle.length(); System.out.println("Die Groesse der zu dekodierenden Datei ist " + laenge + " Byte."); while(true) { // Einlesen eines Bytes aus der Quelle byte b = (byte) quelle.readByte(); /* Hier findet die Verschiebung statt. Wir kümmern uns selbst um den Wertebereich des Datentyps byte, indem wir den Modulo-Operator verwenden. Die hier verwendete Verschiebung ist grö ß er als in unserem ersten Beispiel. Damit wird bei Textdateien eine bessere Verschleierung erreicht, da die Zeichen
Java 2 Kompendium
727
Kapitel 13
Ein- und Ausgabe in Java keine Buchstaben mehr sind.*/ b = (byte)((b – 42)%256); // Schreiben eines Bytes in die Zieldatei ziel.writeByte(b); }} catch (EOFException e) { // Dateiende der Quelldatei erreicht. // Meldung aus dem catch-Teil System.out.println("Alles klar! EOFException zeigt Dateiende an. Deodierung beendet."); } } finally { quelle.close(); //Quelldatei schließen ziel.close(); // Erstellte Datei schließen // Meldung aus dem finally-Teil System.out.println("Der finally-Abschnitt ist auch beendet."); } } public static void main (String args[]) { /* Die Ausnahmen werden in dieser Version der main()-Methode abgefangen. Dazu dient der try-catch-Block. Er fängt sowohl I/O-Ausnahmen als auch ArrayIndexOutOfBoundsException ab. Letztere tritt auf, wenn zu wenig Übergabeparameter eingegeben werden*/ try { if ((args[2].equals("k")) ||(args[2].equals("K"))) { kodiere(args[0],args[1]); } else if ((args[2].equals("d")) ||(args[2].equals("D"))) dekodiere(args[0],args[1]); else { System.out.println("Sie muessen folgende Syntax eingeben:"); System.out.println("java Kodier [Name der Quelldatei] [Name der Zieldatei] [Kodier/Dekodier]"); System.out.println("Dabei bedeutet [Kodier/Dekodier] Folgendes:"); System.out.println("Wenn Sie ein k oder K eingeben, kodieren Sie."); System.out.println("Wenn Sie ein d oder D eingeben, dekodieren Sie."); }} catch (IOException e) { System.out.println("Es gibt eine IOException: " + e.getMessage()); } catch (ArrayIndexOutOfBoundsException e) { System.out.println("ArrayIndexOutOfBoundsException!" ); System.out.println("Sie muessen folgende Syntax eingeben:"); System.out.println("java Kodier [Name der Quelldatei] [Name der Zieldatei] [Kodier/Dekodier]"); System.out.println("Dabei bedeutet [Kodier/Dekodier] Folgendes:"); System.out.println("Wenn Sie ein k oder K eingeben, kodieren Sie."); System.out.println("Wenn Sie ein d oder D eingeben, dekodieren Sie."); } } }
728
Java 2 Kompendium
Praktische Java-Beispiele mit Datenströmen
Kapitel 13
Als ein Highlight dieses Kapitels wollen wir uns noch einmal dem Kodierungsprogramm widmen und ihm eine grafische Oberfläche zur Verfügung stellen. Die Kodierungsfunktionalität war schon recht weit gediehen und soll nur sehr geringfügig überarbeitet werden. Was jedoch noch sicher ausbaufähig ist, ist die Benutzerkommunikation. Sicher – Befehlszeilen mit Übergabeparameter haben zwar ihren Reiz, sind für Experten effektiver als umständliche und langwierige Fenster, Menüs und Dialogstrukturen, aber es sitzen nicht nur Experten an den Rechnern. Kurz gesagt – über den Nutzen einer grafischen Oberfläche kann man zwar streiten, aber nur, wenn man auf der Expertenseite steht :-). Gehen wir das Projekt an. Für unser Programm genügt es, wenn wir die Möglichkeiten des AWT-1.0Modells und das Eventmodell 1.0 nutzen. Insbesondere verzichten wir auch auf JFC und Swing. Im Prinzip ist das Programm – so wie wir es erstellt haben – auch als Applet einsetzbar (auch in Browsern, die nur Java 1.0.2 verstehen). Dazu muss nur eine boolesche Variable umgesetzt werden. Beachten Sie aber, dass in der Regel die Sicherheitseinstellungen von Viewern die Dateizugriffe unterbinden. Was muss nun ein solches Kodierungsprogramm mit einer Benutzeroberfläche – neben der eigentlichen Kodierung – leisten? Eine Benutzeroberfläche soll klar machen, worum es geht. Wir werden über verschiedene Layoutmanager in verschachtelten Panels eine Oberfläche gestalten (mit Schaltflächen und Menüleiste). Es muss eine Auswahlmöglichkeit für die zu kodierende Datei und die zu schreibende Datei geben. Dazu werden wir zwei File-Dialoge zur Verfügung stellen. Das genaue Datei-Handling (etwa nur existierende Datei zum Kodieren auswählen und beim Überschreiben einer bestehenden Datei warnen) übernimmt der jeweilige File-Dialog. Es muss sichergestellt sein, dass der eigentliche Kodierungsvorgang (bzw. Dekodierungsvorgang) nur dann gestartet werden kann, wenn sowohl Quell- als auch Zieldatei vorher festgelegt wurden. Wir regeln das über Deaktivieren und Aktivieren der jeweiligen Schaltflächen je nach Kontext. Wichtige Fehlermeldungen müssen in Dialogfenstern ausgegeben werden, ebenso Hinweise. Das bedeutet, die Meldungen beim Auftreten von Ausnahmen werden nicht mehr ausschließlich auf Systemebene angezeigt, sondern auch in Fehlerfenstern. Ein Hilfe-Menü steht zur Verfügung. Dabei verwenden wir Dialogfenster und Textfelder.
Java 2 Kompendium
729
Kapitel 13
Ein- und Ausgabe in Java Beachten Sie den Aufbau des Hilfetextes und des Infotextes. Die beiden Texte gehen über mehrere Textzeilen. Ohne Eingabe eines expliziten Zeilenvorschubs wird der ganze Text jeweils in einer Zeile dargestellt. Man mü ß te also erheblich seitwärts scrollen. Wie schon bei der Behandlung der Unicode-Zeichenliterale angedeutet, werden diese schon zu einem sehr frühen Zeitpunkt vom Java-Compiler javac interpretiert. Wenn man daher die Escape-Unicode-Literale dazu verwendet, einen expliziten Zeilenvorschub zu erzeugen (\u000a), wird das Zeilenende-Zeichen vor dem schließenden einfachen Anführungszeichen erscheinen. Das Resultat ist dann ein Kompilierfehler. Wir haben also statt dessen das Escape-Literal \n (im Hilfetext) und als Alternative die oktale Darstellung \012 (im Infotext) verwendet.
Abbildung 13.3: Die Oberfläche des Kodierungsprogramms. Die beiden äußeren Buttons sind deaktiviert.
Listing 13.6: Ein Kodierungsprogramm mit grafischer Oberfläche
730
/* Ein Kodierungsprogramm für beliebige binäre Dateien mit grafischer Oberfläche. Die Aktionen werden allesamt in einer grafischen Bedienoberfläche durchgeführt. Die Ausgaben über System.out.println dienen nur dazu, den Ablauf des Programms zu verdeutlichen und können auskommentiert oder gelöscht werden. Noch sinnvoller ist es, einige dieser Meldungen in Form von Hinweisfenstern auszugeben. Für einige Fälle ist dies bereits realisiert. Sie können diese entsprechend erweitern. */ import java.io.*; import java.applet.*; import java.awt.*; public class Rjskryp extends Applet { // STANDALONE APPLICATION /* Die boolesche Variable StandAlone_ja wird auf true gesetzt, wenn das Programm als Standalone- Applikation gestartet wird. Dies ist in unserem Fall sowieso der Fall. Im Prinzip kann das Programm auch als Applet fungieren, wobei die Sicherheitsbeschränkungen des Viewers beachtet werden müssen. */
Java 2 Kompendium
Praktische Java-Beispiele mit Datenströmen
Kapitel 13
//---------------------------------------------private boolean StandAlone_ja= false; /* Die Kodierungsmethode. Als Übergabeargumente werden die Quelldatei und die zu erstellende Zieldatei verwendet. */ public static void kodiere(String quelldat, String zieldat) throws IOException { // Erstellen eines Eingabe- und eines // Ausgabestroms. Dieses Mal arbeiten wir mit // RandomAccessFiles. // Quelle nur zum Lesen öffnen RandomAccessFile quelle= new RandomAccessFile(quelldat,"r"); // Erstellende Datei mit Lese-/Schreibzugriff // öffnen RandomAccessFile ziel = new RandomAccessFile(zieldat,"rw"); try { try { while(true) { // Einlesen eines Bytes aus der Quelle byte b = (byte) quelle.readByte(); /* Hier findet die Verschiebung statt. Wir kümmern uns selbst um den Wertebereich des Datentyps byte, indem wir den Modulo-Operator verwenden. Verschiebung 111*/ b = (byte)((b + 111)%256); ziel.writeByte(b); // Schreiben eines Bytes in die Zieldatei } } catch (EOFException e) { // Dateiende der Quelldatei erreicht. // Meldung aus dem catch-Teil auf Systemebene System.out.println("Alles klar! EOFException zeigt Dateiende an. Kodierung beendet."); } } finally { quelle.close(); //Quelldatei schließen ziel.close(); // Erstellte Datei schließen // Meldung aus dem finally-Teil System.out.println("Der finally-Abschnitt ist auch beendet."); } } /* Die Dekodierungsmethode */ public static void dekodiere(String quelldat, String zieldat) throws IOException { /* Erstellen eines Eingabe- und eines Ausgabestroms. Hier sind auch wieder RandomAccessFiles verwendet */ // Quelle nur zum Lesen öffnen RandomAccessFile quelle= new RandomAccessFile(quelldat,"r"); // Zu erstellende Datei mit Lese-/Schreibzugriff // öffnen RandomAccessFile ziel = new RandomAccessFile(zieldat,"rw"); try { try { while(true) {
Java 2 Kompendium
731
Kapitel 13
Ein- und Ausgabe in Java // Einlesen eines Bytes aus der Quelle byte b = (byte) quelle.readByte(); /* Hier findet die Verschiebung statt. Wir kümmern uns selbst um den Wertebereich des Datentyps byte, indem wir den Modulo-Operator verwenden. Verschiebung 111 */ b = (byte)((b – 111)%256); ziel.writeByte(b); // Schreiben eines Bytes in die Zieldatei } } catch (EOFException e) { // Dateiende der Quelldatei erreicht. // Meldung aus dem catch-Teil System.out.println("Alles klar! EOFException zeigt Dateiende an. Deodierung beendet."); } } finally { quelle.close(); //Quelldatei schließen ziel.close(); // Erstellte Datei schließen // Meldung aus dem finally-Teil System.out.println("Der finally-Abschnitt ist auch beendet."); } } public static void main (String args[]) { alleineFensterFrame fenster = new alleineFensterFrame("Rjskryp"); fenster.resize(fenster.insets().left + fenster.insets().right + 640, fenster.insets().top + fenster.insets().bottom + 400); Rjskryp applet_Rjskryp = new Rjskryp(); fenster.add("Center", applet_Rjskryp); applet_Rjskryp.StandAlone_ja= true; // Standalone-Programm fenster.show(); } } class alleineFensterFrame extends Frame { // Festlegung diverser Panels Panel hauptPanel = new Panel(); Panel ueberschriftPanel = new Panel(); Panel beschreibPanel = new Panel(); Panel buttonPanel = new Panel(); Panel westPanel = new Panel(); Panel ostPanel = new Panel(); // Beschriftungen Label meinTitel = new Label( "RJSKRYP – Javaversion"); Label beschreib = new Label( "Kodiert und dekodiert beliebige binäre Dateien"); Label beschreiblinks = new Label("Kodieren"); Label beschreibrechts = new Label("Dekodieren"); // Einige unterschiedliche Fonts Font meinTitelFont = new Font("TimesRoman", Font.BOLD, 36); Font meinBeschreibFont = new Font("TimesRoman", Font.ITALIC, 20); Font signalFont = new Font("TimesRoman", Font.PLAIN, 24); // Das Menü MenuBar meineMenueLeiste;
732
Java 2 Kompendium
Praktische Java-Beispiele mit Datenströmen
Kapitel 13
Menu meinMenue; // Die Dialog-, Fehler- und Hinweisfenster TextDialog meinHilfeFenster; TextDialog meinInfoFenster; TextDialog meinFehlerFenster; TextDialog meinKodierFenster; TextDialog meinDekodierFenster; // Die File-Dialoge Dateizugriffe kodiereDateiFenster; Dateizugriffe dekodiereDateiFenster; // Variablen, in denen die Pfadangaben der // zu bearbeitenden Dateien gespeichert werden String zuLesendeDatei = ""; String zuSchreibendeDatei = ""; String PfadzuLesendeDatei; String PfadzuSchreibendeDatei; // Schaltflächen Button Kodier = new Button("Kodieren"); Button Dekodier = new Button("Dekodieren"); public alleineFensterFrame(String str) { super (str); // Setze BorderLayout für das Frame // als Hauptpanel this.setLayout(new BorderLayout()); // Hintergrundfarbe gesamt setBackground(Color.blue); // Füge die vier Panels hinzu this.add("North", hauptPanel); this.add("South", buttonPanel); this.add("West", westPanel); this.add("East", ostPanel); //BorderLayout im Hauptpanel hauptPanel.setLayout(new BorderLayout()); // ueberschriftPanel und beschreibPanel sind // Subpanels von hauptpanel hauptPanel.add("North", ueberschriftPanel); hauptPanel.add("South", beschreibPanel); /* Überschrift */ // Überschriftfont meinTitel.setFont(meinTitelFont); // spezielle Schriftfarbe Überschriftpanel ueberschriftPanel.setForeground(Color.white); // Überschrift in Überschriftpanel ueberschriftPanel.add(meinTitel); /* Die Beschreibung im Süden des HauptPanels */ // Beschreibungsfont beschreib.setFont(meinBeschreibFont); // spezielle Schriftfarbe Beschreibungpanel beschreibPanel.setForeground(Color.white); // Text in Beschreibungspanel
Java 2 Kompendium
733
Kapitel 13
Ein- und Ausgabe in Java beschreibPanel.add(beschreib); beschreiblinks.setFont(signalFont); // spezielle Schriftfarbe westPanel.setForeground(Color.red); westPanel.add(beschreiblinks); beschreibrechts.setFont(signalFont); // spezielle Schriftfarbe ostPanel.setForeground(Color.red); ostPanel.add(beschreibrechts); /* Die Schaltflächen */ //FlowLayout im Südpanel buttonPanel.setLayout(new FlowLayout()); // Schaltflächen hinzufügen buttonPanel.add( Kodier); buttonPanel.add( new Button( "Zu lesende Datei auswählen") ); buttonPanel.add( new Button("Ende") ); buttonPanel.add( new Button( "Zu schreibende Datei auswählen") ); buttonPanel.add( Dekodier ); Kodier.disable(); Dekodier.disable(); // Menubar erstellen meineMenueLeiste = new MenuBar(); // Menu zusammenbasteln meinMenue = new Menu("Datei"); meinMenue.add(new MenuItem("Programm Beenden")); // 1. Menu zur Menubar hinzufügen meineMenueLeiste.add(meinMenue); // Menubar an den Rahmen binden setMenuBar(meineMenueLeiste); // Das Hilfemenü Menu meineHilfe = new Menu("Hilfe"); meineMenueLeiste.setHelpMenu(meineHilfe); // Einträge im Hilfemenü meineHilfe.add(new MenuItem("Info")); meineHilfe.add(new MenuItem("Hilfe")); // Erstelle Hilfe-Dialog meinHilfeFenster = new TextDialog(this, "Hilfe zu RJSKRYP – Javaversion", true, 0,""); meinHilfeFenster.resize(500,230); // Erstelle Info-Dialog meinInfoFenster = new TextDialog(this, "Info zu RJSKRYP – Javaversion", true, 1,""); meinInfoFenster.resize(450,220); // Erstelle Kodierende-Hinweis meinKodierFenster = new TextDialog(this, "Hinweis", true, 3,""); meinKodierFenster.resize(180,150); // Erstelle Dekodierende-Hinweis meinDekodierFenster = new TextDialog(this, "Hinweis", true, 4,"");
734
Java 2 Kompendium
Praktische Java-Beispiele mit Datenströmen
Kapitel 13
meinDekodierFenster.resize(180,150); // Erstelle Kodier-Dialog kodiereDateiFenster = new Dateizugriffe(this, "Datei kodieren",FileDialog.LOAD); kodiereDateiFenster.resize(350,150); // Erstelle Dekodier-Dialog dekodiereDateiFenster = new Dateizugriffe(this, "Datei dekodieren",FileDialog.SAVE); dekodiereDateiFenster.resize(350,150); } public boolean action(Event ev, Object arg) { String label = (String)arg; if ( ev.target instanceof Button ) { // Wenn Schaltfläche "Ende" if (label.equals("Ende")) { this.dispose(); System.exit(0); } else if (label.equals( "Zu lesende Datei auswählen")) { kodiereDateiFenster.show(); PfadzuLesendeDatei = kodiereDateiFenster.getDirectory() + kodiereDateiFenster.getFile(); zuLesendeDatei = kodiereDateiFenster.getFile(); System.out.println(zuLesendeDatei); /* Im Folgeschritt wird sichergestellt, dass die Schaltflächen zum Kodieren bzw. Dekodieren nur dann aktivierbar sind, wenn sowohl eine Quell-, als auch eine Zieldatei ausgewählt sind. Falls dies gewährleistet ist, kann die Kodierungs- bzw. Dekodierungsmethode aufgerufen werden. Beachten Sie, dass dabei nicht ueberprüft wird, ob die Quelldatei so gewählt wurde, dass auch nur eine bereits kodierte Datei dekodiert wird. Dies macht das Programm zwar gefährlicher, aber andererseits auch sicherer gegen unberechtigte Anwendung. */ if ((zuLesendeDatei.equals("")) ||(zuSchreibendeDatei.equals(""))) { Kodier.disable(); Dekodier.disable(); } else { System.out.println(zuSchreibendeDatei); System.out.println(zuLesendeDatei); Kodier.enable(); Dekodier.enable(); } } else if (label.equals( "Zu schreibende Datei auswählen")) { dekodiereDateiFenster.show(); PfadzuSchreibendeDatei = dekodiereDateiFenster.getDirectory() + dekodiereDateiFenster.getFile(); zuSchreibendeDatei = dekodiereDateiFenster.getFile();
Java 2 Kompendium
735
Kapitel 13
Ein- und Ausgabe in Java System.out.println(zuSchreibendeDatei); if ((zuLesendeDatei.equals("")) ||(zuSchreibendeDatei.equals(""))) { Kodier.disable(); Dekodier.disable(); } else { System.out.println(zuSchreibendeDatei); System.out.println(zuLesendeDatei); Kodier.enable(); Dekodier.enable(); } } else if (label.equals("Kodieren")) { try { Rjskryp.kodiere(zuLesendeDatei, zuSchreibendeDatei); meinKodierFenster.show(); // Ende-Hinweis } catch (IOException e) { // Ausgabe auf Systemebene System.out.println("Es gibt eine IOException: " + e.getMessage()); // Erstelle Fehler-Dialog meinFehlerFenster = new TextDialog(this, "Fehlermeldung", true, 2, "Probleme beim Schreiben oder Schreiben: " + e.getMessage()); meinFehlerFenster.resize(450,150); meinFehlerFenster.show(); } catch (ArrayIndexOutOfBoundsException e) { // Ausgabe auf Systemebene System.out.println( "Es gibt eine ArrayIndexOutOfBoundsException: " + e.getMessage()); // Erstelle Fehler-Dialog meinFehlerFenster = new TextDialog(this, "Fehlermeldung", true, 2, "Es gibt eine ArrayIndexOutOfBoundsException: " + e.getMessage()); meinFehlerFenster.resize(450,150); meinFehlerFenster.show(); } } else if (label.equals("Dekodieren")) { try { Rjskryp.dekodiere(zuLesendeDatei, zuSchreibendeDatei); meinDekodierFenster.show(); // Endehinweis } catch (IOException e) { // Ausgabe auf Systemebene System.out.println("Es gibt eine IOException: " + e.getMessage()); // Erstelle Fehler-Dialog meinFehlerFenster = new TextDialog(this, "Fehlermeldung", true, 2,
736
Java 2 Kompendium
Praktische Java-Beispiele mit Datenströmen
Kapitel 13
"Probleme beim Schreiben oder Schreiben: " + e.getMessage()); meinFehlerFenster.resize(450,150); meinFehlerFenster.show(); } catch (ArrayIndexOutOfBoundsException e) { // Ausgabe auf Systemebene System.out.println( "Es gibt eine ArrayIndexOutOfBoundsException: " + e.getMessage()); // Erstelle Fehler-Dialog meinFehlerFenster = new TextDialog(this, "Fehlermeldung", true, 2, "Es gibt eine ArrayIndexOutOfBoundsException: " + e.getMessage()); meinFehlerFenster.resize(450,150); meinFehlerFenster.show(); } } } // Reaktionen auf reguläre Menüeinträge else if (ev.target instanceof MenuItem) { if (label.equals("Programm beenden")) { this.dispose(); System.exit(0); } else if (label.equals("Info")) { meinInfoFenster.show(); } else if (label.equals("Hilfe")) { meinHilfeFenster.show(); } } return true; } public boolean handleEvent(Event evt) { switch (evt.id) { case Event.WINDOW_DESTROY: dispose(); System.exit(0); return true; default: return super.handleEvent(evt); } } } // Dialog-Klasse class TextDialog extends Dialog { String hilfeText = "RJSKRYP für Java kodiert und dekodiert beliebige binäre Dateien." + "\nBedienung: \nSie wählen zunächst eine Datei als Quelle und eine als Zieldatei aus."
Java 2 Kompendium
737
Kapitel 13
Ein- und Ausgabe in Java + "\nAnschließend werden die Schaltflächen zum Kodieren und Dekodieren aktivierbar." + "\nDanach entscheiden Sie, ob Sie eine Datei ver- oder entschlüsseln wollen \n(entsprechende Push-Button)."; String infoText = "RJSKRYP für Java wurde entwickelt von RJS-EDV-KnowHow." + "\012Version 1.1" + "\012\012Besuchen Sie uns im Internet: " + "\012http://www.rjs.de"; TextArea meinHilfeText = new TextArea(hilfeText, 5, 42); TextArea meinInfoText = new TextArea(infoText, 3, 40); TextArea meinKodierText = new TextArea( "Kodierung beendet", 3, 40); TextArea meinDekodierText = new TextArea("Dekodierung beendet", 3, 40); TextDialog(Frame aufrufendesFenster, String title, boolean modal, int welcherText, String text) { super(aufrufendesFenster, title, modal); setLayout(new BorderLayout(10,10)); setBackground(Color.green); add("South", new Button("Alles klar")); if (welcherText==0) { meinHilfeText.setEditable(false); add("Center",meinHilfeText); } else if (welcherText==1) { meinInfoText.setEditable(false); add("Center",meinInfoText); } else if (welcherText==2) { TextArea meinFehlerText = new TextArea(text, 3, 40); meinFehlerText.setEditable(false); add("North",meinFehlerText); } else if (welcherText==3) { meinKodierText.setEditable(false); add("North",meinKodierText); } else if (welcherText==4) { meinDekodierText.setEditable(false); add("North",meinDekodierText); } } public Insets insets() { return new Insets(40,10,10,10); } // action()-Methode des Dialogfensters public boolean action(Event evt, Object arg) { String label = (String)arg; if (evt.target instanceof Button) { if (label == "Alles klar") { hide(); // Dialog schließen
738
Java 2 Kompendium
Praktische Java-Beispiele mit Datenströmen
Kapitel 13
} } else return false; return true; } // Ende action()-Methode des Dialogs public boolean handleEvent(Event evt) { switch (evt.id) { case Event.WINDOW_DESTROY: dispose(); return true; default: return super.handleEvent(evt); } } } // FileDialogklasse class Dateizugriffe extends FileDialog { Dateizugriffe(Frame aufrufendesFenster, String title, int art) { super(aufrufendesFenster, title, art); } }
Wenn Sie das Programm starten, werden die beiden Schaltflächen zur Kodierung und Dekodierung deaktiviert sein. Sie müssen, bevor sie aktiviert werden, sowohl eine Datei als Quelle als auch eine als Ziel auswählen. Dazu werden Standard-Dialoge verwendet, die über die entsprechenden DialogKonstruktoren mit entsprechenden Konstanten bereitgestellt werden: kodiereDateiFenster = new Dateizugriffe(this, "Datei kodieren",FileDialog.LOAD); dekodiereDateiFenster = new Dateizugriffe(this, "Datei dekodieren",FileDialog.SAVE); Abbildung 13.4: Java stellt einen Standard-Dialog zum Öffnen von Dateien zur Verfügung, der für das Programm genutzt wird.
Die Dialoge stellen allerdings keine weitere Funktionalität bereit, als den Namen der ausgewählten Datei zurückzugeben. Behandeln muss man diese
Java 2 Kompendium
739
Kapitel 13
Ein- und Ausgabe in Java
Abbildung 13.5: Auch zum Speichern wird ein Standard-Dialog genutzt.
Information dann selbst im Programm, sowohl den Pfad dorthin als auch die Datei selbst. PfadzuLesendeDatei = kodiereDateiFenster.getDirectory() + kodiereDateiFenster.getFile(); zuLesendeDatei = kodiereDateiFenster.getFile(); Abbildung 13.6: Wenn eine Datei beim Speichern überschrieben werden muss, warnt ein StandardDialogfenster.
Wenn zwei Dateien ausgewählt wurden2, können Sie sowohl den Button zum Kodieren als auch Dekodieren auswählen. Wenn die Aktion beendet ist, wird ein selbst programmierter Ende-Dialog angezeigt. TextDialog(Frame aufrufendesFenster, String title, boolean modal, int welcherText, String text) { super(aufrufendesFenster, title, modal); setLayout(new BorderLayout(10,10)); setBackground(Color.green); add("South", new Button("Alles klar")); if (welcherText==0) { meinHilfeText.setEditable(false); add("Center",meinHilfeText); } else if (welcherText==1) { meinInfoText.setEditable(false); 2
740
Das Programm kontrolliert nicht, ob Sie korrekt die Quelle ausgewählt haben, also ob eine zu dekodierende Datei auch wirklich kodierten Code enthält.
Java 2 Kompendium
Praktische Java-Beispiele mit Datenströmen
Kapitel 13
add("Center",meinInfoText); } else if (welcherText==2) { TextArea meinFehlerText = new TextArea(text, 3, 40); meinFehlerText.setEditable(false); add("North",meinFehlerText); } else if (welcherText==3) { meinKodierText.setEditable(false); add("North",meinKodierText); } else if (welcherText==4) { meinDekodierText.setEditable(false); add("North",meinDekodierText); } } Abbildung 13.7: Das Hinweisfenster am Ende der Kodierung ist selbst gestrickt.
Info- und Hilfedialog werden über ein Menü aufgerufen. meineHilfe.add(new MenuItem("Info")); meineHilfe.add(new MenuItem("Hilfe"));
Dabei sollte beachtet werden, dass die beiden konkreten Fenster über dieselbe selbst programmierte Klasse und denselben Konstruktor – nur mit unterschiedlichem int-Wert als viertem Parameter – erzeugt werden. meinHilfeFenster = new TextDialog(this, "Hilfe zu RJSKRYP – Javaversion", true, 0,""); meinInfoFenster = new TextDialog(this, "Info zu RJSKRYP – Javaversion", true, 1,"");
Die Klasse TextDialog unterscheidet auf Grund des vierten Parameters, welches Fenster zu erzeugen ist. class TextDialog extends Dialog { String hilfeText = "RJSKRYP für Java kodiert und dekodiert beliebige binäre Dateien." + "\nBedienung: \nSie wählen zunächst eine Datei als Quelle und eine als Zieldatei aus." + "\nAnschließend werden die Schaltflächen zum Kodieren und Dekodieren
Java 2 Kompendium
741
Kapitel 13
Ein- und Ausgabe in Java aktivierbar." + "\nDanach entscheiden Sie, ob Sie eine Datei ver- oder entschlüsseln wollen \n(entsprechende Push-Button)."; String infoText = "RJSKRYP für Java wurde entwickelt von RJS-EDV-KnowHow." + "\012Version 1.1" + "\012\012Besuchen Sie uns im Internet: " + "\012http://www.rjs.de"; TextArea meinHilfeText = new TextArea(hilfeText, 5, 42); TextArea meinInfoText = new TextArea(infoText, 3, 40); TextArea meinKodierText = new TextArea("Kodierung beendet", 3, 40); TextArea meinDekodierText = new TextArea("Dekodierung beendet", 3, 40); TextDialog(Frame aufrufendesFenster, String title, boolean modal, int welcherText, String text) { super(aufrufendesFenster, title, modal); setLayout(new BorderLayout(10,10)); setBackground(Color.green); add("South", new Button("Alles klar")); if (welcherText==0) { meinHilfeText.setEditable(false); add("Center",meinHilfeText); } else if (welcherText==1) { meinInfoText.setEditable(false); add("Center",meinInfoText); } else if (welcherText==2) { TextArea meinFehlerText = new TextArea(text, 3, 40); meinFehlerText.setEditable(false); add("North",meinFehlerText); } else if (welcherText==3) { meinKodierText.setEditable(false); add("North",meinKodierText); } else if (welcherText==4) { meinDekodierText.setEditable(false); add("North",meinDekodierText); } }
Beenden lässt sich das Programm sowohl über einen Menüeintrag als auch über den Ende-Button. Das Kodierungsprogramm ist so sicherlich keineswegs perfekt (was auch sicher nicht Anspruch eines Beispielprogramm sein kann). So fehlt beispielsweise eine Statusanzeige, die angibt, wie weit die Kodierung bzw. Dekodierung fortgeschritten ist. Bei kleinen Dateien macht sich das kaum bemerkbar, aber wenn die Kodierung bzw. Dekodierung einige Zeit braucht, kann man nicht sehen, ob das
742
Java 2 Kompendium
Praktische Java-Beispiele mit Datenströmen
Kapitel 13 Abbildung 13.8: Der Infodialog – man kann gut erkennen, dass die Schaltflächen zum Kodieren und Dekodieren aktiviert sind.
Abbildung 13.9: Der Hilfedialog
Abbildung 13.10: Menü und Button zum Beenden
Java 2 Kompendium
743
Kapitel 13
Ein- und Ausgabe in Java Programm noch arbeitet. Auch könnten die Hinweise, Meldungen und die Hilfe erweitert werden. Das Layout ist auf jeden Fall zu verbessern. Es sollte jedoch ansonsten vollständig funktionieren und als Basis für eigene Projekte gute Dienste leisten.
13.16
Drucken unter Java
Ein wesentlicher Ausgabevorgang ist natürlich das Drucken. Das ist aber unter Java gar nicht so unproblematisch. Am einfachsten war es in Java 1.0 und dem JDK 1.0. Es ging überhaupt nicht! Das JDK 1.1 erlaubte dann ein einfaches Drucken über ein mehr schlecht als recht funktionierendes DruckAPI. Gleichwohl ist es für einfache Druckaktionen immer noch sinnvoll einzusetzen. Das JDK 1.2 stellte dann endlich ein vernünftigeres Druck-API bereit. Aber auch dieses war noch nicht der Weisheit letzter Schluss. Es war insbesondere in Bezug auf Performance schlichtweg ungenügend. Ab dem JDK 1.3 hat sich das aber wesentlich verbessert, wobei die grundsätzliche Technik für den Programmierer immer noch der des JDK 1.2 entspricht. Schauen wir uns die beiden Welten an.
13.16.1
Drucken unter dem JDK 1.1
Wenn Sie unter dem JDK 1.1 drucken wollen, wird die Klasse java.awt.Toolkit und dort die Methode getPrintJob() Grundlage aller Aktionen sein. Die Methode gibt es in zwei Ausprägungen: public abstract PrintJob getPrintJob(Frame frame, String jobtitle, Properties props) public PrintJob getPrintJob(Frame frame, String jobtitle, JobAttributes jobAttributes, PageAttributes pageAttributes)
Das gesamte Verfahren ist im Prinzip relativ unkompliziert. Die Methode liefert ein Objekt des Typs PrintJob, das sämtliche Aktionen des Druckauftrags steuert. Zudem bedeutet der Aufruf dieser Methode das Öffnen eines plattformabhängigen Druckdialogs. Wenn der Anwender diesen bestätigt, wird der Druckauftrag durchgeführt. Bricht er ab, liefert die Methode den Wert null. Dieser Wert kann dann explizit verwendet werden. Die Klasse PrintJob stellt einige Methoden bereit, die Sie für einen sinnvollen Druck benötigen. Die Methode public abstract Graphics getGraphics() liefert ein GraphicsObjekt, das auf der nächsten Seite ausgegeben wird.
744
Java 2 Kompendium
Drucken unter Java
Kapitel 13
Die Methode public abstract Dimension getPageDimension() gibt die Dimension von einer Seite in Pixel zurück. Die Auflösung einer Seite wird so gewählt, dass sie der Bildschirmauflösung entspricht. Die Methode public abstract int getPageResolution() gibt die Auflösung der Seite in Pixels per Inch zurück. Das hat aber nichts mit der Auflösung des Druckers zu tun. Um in umgekehrter Reihenfolge zu drucken, gibt es die Methode public abstract boolean lastPageFirst(). Ganz wichtig ist die Methode public abstract void end(), womit jeder Druckauftrag beendet wird. Um nun die Druckausgabe in einem Programm zu platzieren, gibt es zwei sinnvolle Stellen. Sie können zum einen eine eigene Methode erstellen, die die notwendigen Anweisungen integriert. Es gibt über die Klasse java.awt.Component die Methoden public void print(Graphics g) und public void printAll(Graphics g). Die Methode print() druckt eine Komponente, printAll() eine Komponte samt den Subkomponenten. Meist ist es jedoch einfacher, die in der Regel auch für Bildschirmausgaben verwendete paint()-Methode zu gebrauchen, zumal die print()-Methoden diese implizit verwenden. Dabei werden entweder gewisse Druckaufträge automatisch ausgelöst oder so Sie rufen ein repaint() auf, wenn Sie drucken wollen. Gehen wir in die Praxis. import java.awt.*; import java.awt.event.*; import java.awt.Graphics; public class Textdruck1 extends Frame { public static void main(String[] args) { Textdruck1 wnd = new Textdruck1(); } public Textdruck1() { super("Textdruck unter dem JDK 1.1"); addWindowListener( new WindowAdapter() { public void windowClosing(WindowEvent event) { System.exit(0);}}); setSize(400,400); setVisible(true); } public void paint(Graphics g) { PrintJob pjob = getToolkit().getPrintJob(this,"Testseite",null); if (pjob != null) { g = pjob.getGraphics(); if (g != null) {
Java 2 Kompendium
Listing 13.7: Ein einfaches Druckprogramm für das Drucken unter dem JDK 1.1Modell
745
Kapitel 13
Ein- und Ausgabe in Java g.setFont(new Font("TimesRoman",Font.BOLD,24)); g.drawString( "Drucken unter dem JDK 1.1",40,100); g.dispose(); } pjob.end(); } } }
Abbildung 13.11: Ein plattformabhängiger StandardDruckdialog mit ausgewähltem Netzwerkdrucker
Das Programm ist bewusst auf die wesentlichen Aspekte reduziert. Beim Start wird einfach ein Frame geöffnet und automatisch die paint()-Methode aufgerufen. Diese ruft getPrintJob() auf und erzeugt damit ein Objekt des Typs PrintJob sowie das Öffnen eines plattformabhängigen Druckdialogs. Wenn der Anwender diesen bestätigt, wird der Druckauftrag durchgeführt. Bricht es ab, liefert die Methode den Wert null und der Druck unterbleibt. Zur konkreten Ausgabe haben wir einfach mit der auch auf dem Bildschirm verwendbaren Methode drawString() gearbeitet. Erweitern wir das Beispiel ein wenig. Wir schaffen eine grafische Oberfläche mit einem Texteingabefeld. Dessen Inhalt soll bei einem Klick auf einen Button ausgegeben werden. Außerdem soll der Druckdialog nicht ausgelöst werden, wenn das Programm startet. Das fangen wir ganz einfach ab, indem eine boolesche Variable beim Start auf true gesetzt wird. Die paint()Methode löst den Druck nur aus, wenn diese auf false gesetzt ist. Dies geschieht am Ende der paint()-Methode. Das bedeutet, ein nachfolgender Aufruf repaint() startet den Druck bzw. den Druckdialog. Die Methode wird beim Klick auf den entsprechenden Button ausgelöst. Beachten Sie aber, dass auch andere Ereignisse diese Methode aufrufen. Etwa wenn Sie
746
Java 2 Kompendium
Drucken unter Java
Kapitel 13
das Fenster in den Hintergrund und dann wieder nach vorne bringen. Das Beispiel ist also nur begrenzt praxisfähig. Das soll aber auch nicht Sinn und Zweck sein. import java.awt.*; import java.awt.event.*; import java.awt.Graphics; public class Textdruck2 extends Frame { // Variablendeklaration private Panel panel1; private Button button1; private Button button2; private TextField textFeld1; private boolean start=true; public Textdruck2() { initComponents (); pack (); } private void initComponents() { panel1 = new Panel(); button1 = new Button(); button2 = new Button(); textFeld1 = new TextField(); addWindowListener(new WindowAdapter() { public void windowClosing(WindowEvent evt) { exitForm(evt);}}); panel1.setFont(new Font ("Dialog", 0, 11)); panel1.setName("panel1"); panel1.setBackground(new Color (204, 204, 204)); panel1.setForeground(Color.black); button1.setFont(new Font ("Dialog", 0, 11)); button1.setLabel("Ende"); button1.setName("button1"); button1.setBackground(Color.lightGray); button1.setForeground(Color.black); button1.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { button1ActionPerformed(evt);}}); panel1.add(button1); button2.setFont(new Font ("Dialog", 0, 11)); button2.setLabel("Drucken"); button2.setName("button2"); button2.setBackground(Color.lightGray); button2.setForeground(Color.black); button2.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { button2ActionPerformed(evt);}}); panel1.add(button2); add(panel1, BorderLayout.SOUTH); textFeld1.setBackground(Color.yellow);
Java 2 Kompendium
Listing 13.8: Druck auf Klick
747
Kapitel 13
Ein- und Ausgabe in Java textFeld1.setName("text1"); textFeld1.setFont(new Font ("Dialog", 0, 11)); textFeld1.setForeground(Color.blue); add(textFeld1, BorderLayout.NORTH); } private void button2ActionPerformed(ActionEvent evt) { repaint(); } private void button1ActionPerformed(ActionEvent evt) { System.exit(0); } /** Exit */ private void exitForm(WindowEvent evt) { System.exit (0); } public static void main (String args[]) { new Textdruck2 ().show (); } public void paint(Graphics g) { if(!start) { PrintJob pjob = getToolkit().getPrintJob(this,"Testseite",null); if (pjob != null) { g = pjob.getGraphics(); if (g != null) { g.setFont(new Font("TimesRoman",Font.BOLD,24)); g.drawString(textFeld1.getText(),40,100); g.dispose(); } pjob.end(); } } start=false; } }
Abbildung 13.12: Der Inhalt des Textfeldes wird beim Klick auf den Button gedruckt.
13.16.2
Drucken unter dem SDK 2
Das Drucken unter dem SDK 2 funktioniert über einen neuen Ansatz. Er basiert auf dem Paket java.awt.print. Dort gibt es die Klasse PrinterJob. Trotz des ähnlichen Namens hat sie aber nicht die gleiche Bedeutung wie PrintJob. Sie ist nur für die Kontrolle des Drucks (Aufrufen von Dialogen, Seitenkontrolle, Start des Drucks usw.) verantwortlich. Der eigentliche Ausdruck erfolgt über die Schnittstellen Printable, PrinterGraphics oder Pageable. Deren Methoden müssen bei Bedarf überschrieben werden. Dazu stellt 748
Java 2 Kompendium
Drucken unter Java
Kapitel 13
das Paket noch die Klassen Book (Unterstützung verschiedener Seitenformate), PageFormat (Grö ß e und Ausrichtugn einer Seite) und Paper (physikalische Charakteristika des Papiers) zur Verfügung. Die Klasse PrinterJob stellt eine ganze Reihe von Methoden zur Verfügung, etwa die Methode public static PrinterJob getPrinterJob(), die einen PrinterJob erstellt und ihn zurückgibt. Über public abstract void setPageable(Pageable document) throws NullPointerException kann die Anzahl der Seiten und das Seitenformat gesetzt werden. Im SDK 2 stehen nunmehr zwei Konfigurationsdialoge zur Verfügung. Der eine lässt die Einstellung von der Anzahl der Kopien, die auszudruckenden Seiten und des Druckers zu. Der andere kümmert sich um Seitenparameter. Die Methoden public abstract boolean printDialog() und public abstract PageFormat pageDialog(PageFormat page) erzeugen Dialoge zum Anpassen der Eigenschaften des Druckjobs und der Seitenformate. Die Methode public abstract void print() throws PrinterException druckt einen Satz von Seiten, public abstract void setCopies(int copies) gibt die Anzahl der Kopien an, public abstract int getCopies() fragt sie ab. Über public abstract void cancel() kann man einen Druckjob abbrechen und public abstract boolean isCancelled() kontrolliert diesen Status. Wenn nun konkret eine Druckaktion laufen soll, verwendet man beispielsweise die Schnittstelle Printable und überschreibt die Methode public abstract void print() throws PrinterException. Von besonderem Interesse sind die in der Schnittstelle vorhandenen Konstanten PAGE_EXISTS und NO_SUCH_PAGE, auf die man in der print()-Methode abprüfen kann. Solange der Rückgabewert der Methode PAGE_EXISTS entspricht, wird immer wieder eine Seite gedruckt. Erst bei Übereinstimmung mit NO_SUCH_PAGE wird der Druckjob beendet. Schauen wir uns das in der Praxis an. Dazu nehmen wir ein einfaches Beispiel, das nur einen konstanten Text samt einer Seitenzahl ausgibt. Die Seite wird zweimal ausgegeben. Beachten Sie, dass die print()Methode Einstellungen des Konfigurationsdialogs und auch die Angaben über Kopien für die Seite berücksichtigt. Für den konkreten Ausdruck verwenden wir in der print()-Methode Casting eines Graphics-Objekts auf ein Graphics2D-Objekt. Damit haben wir dann auch die Möglichkeiten von Java 2D zum Drucken zur Verfügung. import java.awt.*; import java.awt.print.*; import java.io.*; public class Textdruck3 implements Printable { // Konstanten private static final int SKALIERFAKTOR = 4; // Variablen private PrinterJob pjob; private PageFormat seitenFormat; private String druckText;
Java 2 Kompendium
Listing 13.9: Ein einfaches Druckprogramm für das Drucken unter dem SDK-2-Modell
749
Kapitel 13
Ein- und Ausgabe in Java // Konstruktor public Textdruck3(String druckText) { this.pjob = PrinterJob.getPrinterJob(); this.druckText = druckText; } // Öffentliche Methoden public boolean setupPageFormat() { PageFormat defaultPF = pjob.defaultPage(); this.seitenFormat = pjob.pageDialog(defaultPF); pjob.setPrintable(this, this.seitenFormat); return (this.seitenFormat != defaultPF); } public boolean setupJobOptions() { return pjob.printDialog(); } public void drucke() throws PrinterException, IOException { pjob.print(); } // Implementierung von Printable // Die Methode print() muss überschrieben // werden public int print(Graphics g, PageFormat pf, int page) throws PrinterException { int druckErgeb = PAGE_EXISTS; // Abbruch des Druckjobs, wenn Seitenzahl // > 1, d.h. zwei Seite wurden gedruckt if(page>1) return NO_SUCH_PAGE; String line = null; Graphics2D g2 = (Graphics2D)g; g2.scale(1.0 / SKALIERFAKTOR, 1.0 / SKALIERFAKTOR); int ypos = (int)pf.getImageableY() * SKALIERFAKTOR; int xpos = ((int)pf.getImageableX() + 2) * SKALIERFAKTOR; int yd = 12 * SKALIERFAKTOR; int ymax = ypos + (int)pf.getImageableHeight() * SKALIERFAKTOR – yd; //Seitentitel ausgeben ypos += yd; g2.setColor(Color.black); g2.setFont(new Font("Monospaced", Font.ITALIC, 12 * SKALIERFAKTOR)); g.drawString("Seite " + (page + 1), xpos, ypos); g.drawString(druckText, xpos, ypos*2); // Erneuter Aufruf von print() return druckErgeb; } public static void main(String[] args) { Textdruck3 druckProgr = new Textdruck3("Hello World"); if (druckProgr.setupPageFormat()) { if (druckProgr.setupJobOptions()) { try { druckProgr.drucke(); }
750
Java 2 Kompendium
Zusammenfassung
Kapitel 13
catch (Exception e) { System.err.println(e.toString()); System.exit(1); } System.exit(0); } } } } Abbildung 13.13: Ein plattformabhängiger StandardDialog zur Einstellung der SeitenEckdaten
13.17
Zusammenfassung
Ein- und Ausgabeoperationen zählen zu den grundlegendsten Aktionen, die von einem Programm bewerkstelligt werden. In Java werden diese Ein- und Ausgabeoperationen mittels Datenströmen realisiert – einem nicht-interpretierten Strom von Bytes. Ein Datenstrom kann von jeder beliebigen Quelle kommen, d.h., der Ursprungsort spielt überhaupt keine Rolle. Die Information über die Quelle steckt in einem Strom-Argument. Außerdem gibt es noch ein weiteres Argument, das angibt, in welches Zielobjekt die verarbeiteten Daten zurückgeschrieben werden sollen.
Java 2 Kompendium
751
Kapitel 13
Ein- und Ausgabe in Java
Abbildung 13.14: Ein plattformabhängiger StandardDruckdialog zur Einstellung der konkreten Seiten, der Kopien sowie des Druckers
Unter Java stehen zur Behandlung des Datenstrom-Modells zwei abstrakte Klassen zur Verfügung: InputStream OutputStream
Die beiden Klassen gehören zu dem Paket java.io. Die meisten Klassen zur Ein- und Ausgabe leiten sich von InputStream und OutputStream ab oder verwenden sie. Es gibt aber auch Klassen, die nicht auf diese beiden abstrakten Klassen zurückgehen. Eine der wohl wichtigsten Nicht-Streamklassen ist die Klasse File, die Dateinamen und Verzeichnisnamen in einer plattformunabhängigen Weise verwaltet. Die einfachen Strom-Methoden erlauben nur das Versenden von Bytes mittels Datenströme. Zum Senden verschiedener Datentypen gibt es die Schnittstellen DataInput und DataOutput. Sie legen Methoden zum Senden und Empfangen anderer Java-Datentypen fest. Mithilfe der Schnittstellen ObjectInput und ObjectOutput lassen sich ganze Objekte über einen Strom zusenden. Mit dem StreamTokenizer können Sie einen Strom wie eine Gruppe von Worten behandeln. Er ist dem StringTokenizer ähnlich, der das Gleiche mit Zeichenketten tut. Für die Kommunikation von einzelnen Threads gibt es die beiden Klassen PipedInputStream zum Lesen von Daten aus einem PipedOutputStream. Der PipedOutputStream dient also zum Schreiben in einen PipedInputStream. Alle Methoden, die sich mit Eingabe- und Ausgabeoperationen beschäftigen, werden in der Regel mit der throw IOExceptions oder aber EOFException abgesichert.
752
Java 2 Kompendium
Zusammenfassung
Kapitel 13
Ein wesentlicher Ausgabevorgang ist das Drucken, das in Java mit zwei unterschiedlichen Konzepte realisiert wird. Ab dem JDK 1.1 wurde das Konzept mit der Klasse PrintJob aus java.awt.Toolkit realisert, ab dem SDK 2 verlagerte man die Druckfunktionalität in das Paket java.awt.print.
Java 2 Kompendium
753
14
Java-Sicherheit
Fügen wir an dieser Stelle einen kleinen Break bezüglich der konkreten Programmierung ein und wenden uns einem wichtigen Hintergrundthema zu. Das Thema Sicherheit und Java gehören eng zusammen. Nicht zuletzt, weil es auch um das Internet als offenes Netzwerk geht. Bis vor einiger Zeit wurde die Sicherheit von Computern mit dem Verhindern von Daten-Crashs und der Abwehr von Computerviren gleichgesetzt. In geschlossenen Netzwerken galt zusätzlich der Abwehr von unbefugten Eindringlingen das Augenmerk. Das Internet selbst war offen und barg außer diesen Risiken keine Gefahren. Zumindest solange das auf dem HTTP-Protokoll basierende HTML die einzige Sprache des WWW war. Da man nur mithilfe eines Browsers (Telnet, FTP & Co. mal außen vor) Daten von einem Server abrufen konnte und echte Interaktivität kaum stattfand, konnte auch außerhalb des Browsers – sprich im restlichen Computersystem – nicht viel unbemerkt passieren. Dies hat sich mit dem Auftauchen von ActiveX, aber auch mit Java geändert. Es gehört jedoch zu den zentralen Argumenten für Java, dass dort dieses Thema Sicherheit ganz groß geschrieben wird. Dennoch darf man nicht in unkritische Euphorie verfallen, nur weil Java gegenüber der Konkurrenz eine neue Welt der Sicherheitsaspekte geöffnet hat und Programmierern eine bisher kaum gekannte Möglichkeit eröffnet, sicher ausgelegte Programme zu entwickeln. Immerhin ist in der Vergangenheit mehrfach gezeigt worden, dass auch Java nicht ganz frei von Sicherheitslücken war. Dennoch – das Konzept von Java ist durch und durch auf Sicherheit ausgelegt, was auch die zahlreichen auf Java basierenden Anwendungen im Bereich Online-Banking über Internet und bei verwandten Vorgängen zeigen.
14.1
Das Java-Sicherheitskonzept
Es gibt viele Aspekte, die bei einem schlüssigen Sicherheitskonzept zu berücksichtigen sind. Statt alle erdenklichen Angriffsszenarien abzudecken, postuliert Javas Sicherheitsmodell potenzielle Ziele, die Angriffen bzw. Angriffskategorien ausgesetzt sind. Insbesondere schützt das SicherheitsmoJava 2 Kompendium
755
Kapitel 14
Java-Sicherheit dell von Java den Hauptspeicher, den Festplattenspeicher eines Clients, das Betriebssystem und den Programmzustand sowie das Netzwerk vor Angriffen folgender Art: Beschädigung der Software auf dem Client-Rechner Zugriff auf Betriebsmittel des Client-Rechners Herausschmuggeln von Informationen aus dem Client-Rechner Identitätsannahme des Client-Rechners Javas Sicherheitsmechanismen greifen – rein auf das Sprachkonzept bezogen – auf vier Ebenen des Computersystems. Die erste Ebene ist die der Sprache an sich und der zugehörige Compiler samt kompilierter Klassen. Die zweite Ebene betrifft die Verifizierung des Bytecodes zur Laufzeit. Dort wird im Wesentlichen darauf geachtet, dass zentrale Sicherheitsregeln eingehalten werden. Ebene drei ist der Classloader, der Klassen beim Laden analysiert und nach verschiedenen Kriterien mit Rechten versieht. Außerdem lässt er nur »freundliche« Klassen passieren. Die letzte Ebene ist ein so genannter SecurityManager, der als abstrakte Java-Klasse zur Laufzeit zerstörerische Verhaltensweisen von Java-Programmen verhindert.
14.1.1
Die Missbrauchsfähigkeiten einer Sprache
Ein wichtiger Ansatzpunkt für die Sicherheit eines Systems sind sprachspezifische Sicherheitslücken durch diverse Zugriffsmöglichkeiten auf lokale Rechnerressourcen. Die C/C++-Sprachmöglichkeiten sind das wichtigste Beispiel für diese Missbrauchsfähigkeiten (bewusst und unbewusst durch nicht mehr kontrollierbare Fehlerquellen) einer Programmiersprache in einer weitgehend ungeschützten Umgebung. Grundsätzlich muss man festhalten, dass der Verzicht von Java auf C/C++-typische und sehr maschinennahe Sprachelemente wie Pointer, Zeigerarithmetik, ungeprüfte Typumwandlung, die Speicherallokation und die anschließende Freigabe über free() – Java nutzt statt dessen mit der Garbage Collection ein konservatives Mark/Sweep-Verfahren – ein erhebliches Maß an Sicherheit gebracht hat. Zum einen sind Java-Programme erheblich stabiler als vergleichbare C/C++-Programme (ein nicht zu verachtender Aspekt), zum anderen ist die Sprache vieler sprachspezifischer Missbrauchsfähigkeiten beraubt. Gerade die Unterbindung von direkten Zugriffen auf Objekte vermeidet erhebliche Bedrohungen. Ganz herheblich tragen die sprachintegrierten Exceptions zur Stabilität von Java bei. Sie ermöglichen in Java definierte und kontrollierte Programmabbrüche.
756
Java 2 Kompendium
Das Java-Sicherheitskonzept
Kapitel 14
Im Detail sind folgende Aspekte des Java-Designs auf maximale Sicherheit ausgelegt: Strikte Objektorientiertheit Java ist strikt objektorientiert, da jede einzelne Datenstruktur (und aus ihr abgeleitete Datenstrukturen) ein vollkommen eigenständiges Objekt erster Klasse darstellt. Das ist nicht nur unter dem Aspekt der Programmierung ein wichtiges Detail, sondern auch aus Sicherheitsgründen. Damit ist sichergestellt, dass in Java alle theoretischen Sicherheitsvorteile der objektorientierten Programmierung (OOP) sowohl in der Syntax als auch in der Semantik bei Programmen wiederzufinden sind. Dies meint im Wesentlichen das Verstecken von Daten hinter privaten Deklarationen. Auf diese darf man entweder gar nicht oder nur über öffentliche Methoden zugreifen, die dann eine gezielte Filterung erlauben. Diese Datenkapselung können wir mit einem kleinen Beispiel demonstrieren. Das Beispiel soll eine Geschwindigkeitsänderung eines Autos (sehr vereinfacht) demonstrieren. Die Geschwindigkeit (tempo) ist in der Klasse PrivateVar als private deklariert. Nach außen wird nur eine Methode tempoAendern() bekannt gegeben, die widerum aber diese Variable nutzt. Wenn nun aus der zweiten Klasse Speed heraus diese private Information genutzt werden soll, kann das nur über die filternde Methode erfolgen, nicht direkt. class PrivateVar { private int tempo=200; int tempoAendern(int gesch) { System.out.println( "Gewuenschte Geschwindigkeit: "+gesch); if(gesch>tempo) { System.out.println("Motor wird abgeriegelt"); gesch=200; } return(gesch); } } Private Variable und öffentliche Methode, die die Variable nutzt class Speed { public static void main(String args[]) { PrivateVar a = new PrivateVar(); /* a.tempo = 300; System.out.println("Geschwindigkeit: " + a.tempo); */ System.out.println("Max: " + a.tempoAendern(300)); System.out.println("Max: " + a.tempoAendern(199)); } }
Java 2 Kompendium
Listing 14.1: Zugriff auf die private Information nur über die öffentliche Methode
757
Kapitel 14
Java-Sicherheit Der Zugriff funktioniert nur so wie in dem nicht auskommentierten Teil demonstriert.
Abbildung 14.1: So funktioniert der Zugriff
Wenn Sie den auskommentierten Teil aktivieren, wird der Compiler den Zugriff nicht gestatten. Abbildung 14.2: Der Compiler erlaubt keinen direkten Zugriff auf private Informationen von außen.
Die strenge Einhaltung von diesen Zugriffsleveln bedeuten die Unterbindung von nicht gewünschen Zugriffen auf Datenstrukturen. Finale Klassen, Variablen und Methoden In Java können Klassen, Variablen und Methoden bekanntlich als final deklariert werden, womit eine weitere Ableitung der Klasse, das Verändern von Variablenwerten und das Überschreiben von Methoden nicht mehr möglich ist. Dementsprechend kann bereits überprüfter Code nicht böswillig verändert werden.
758
Java 2 Kompendium
Das Java-Sicherheitskonzept
Kapitel 14
Strenge Typisierung und sichere Typumwandlung Java ist streng typisiert. Die Typumwandlung wird in Java sowohl statisch als auch dynamisch geprüft, um sicherzustellen, dass der deklarierte Kompilierzeit-Typ eines Objekts mit einem eventuellen Laufzeit-Typen kompatibel ist, selbst wenn das Objekt in einen anderen Typ umgewandelt wird. Keine Pointer Dies ist wahrscheinlich der beste Garant für Sicherheit in Java. Da Java keine Pointer (zumindest nicht im Sinne von C/C++) verwendet, sondern Objektreferenzen, werden sämtliche Umwandlungen und Zugriffe vorab (d.h. vor der eigentlichen Laufzeit) bereits auf Zulässigkeit geprüft. Das Ausschließen von direkten Zeigern stellt immer sicher, dass kein Teil eines Java-Progamms jemals anonym ist. Der Zugriff auf jede einzelne Datenstruktur und auf jedes Code-Fragment kann zurückverfolgt werden. Die statt Pointern verwendeten Array-Strukturen in Java gleichen nicht nur das Fehlen der Pointer aus, sondern steigern die Sicherheit, indem die Grenzen eines Arrays strikt festgelegt sind. Zudem ist jedem einzelnen Java-Objekt ist Hash-Code (Kontrollcode) zugeordnet. Das bedeutet, dass der Status eines Java-Programms jederzeit aufgezeichnet werden kann. Damit sind aber nicht nur unbewusste Fehler vermieden, ebenfalls sind die Missbrauchsfähigkeiten eingeschränkt bzw. leichter erkennbar. Thread-sichere Datenstrukturen Java ist Multithreading-fähig. Dies erzwingt in Bezug auf Sicherheit und Stabilität einen Thread-sicheren Zugriff auf Datenstrukturen und Objekte. Java besitzt einen solchen, der viel zur allgemeinen Stabilität des Systems beiträgt. Sicherer Ansatz des Methoden- und Variablen-Aufrufs Java verwendet einen besonderen Ansatz, um Funktionen aufzurufen. Normalerweise rufen PC-Programme Funktionen über eine numerische Adresse auf. Da diese Adresse eine numerische Folge ist und diese Folge vom Programmierer beliebig programmiert werden kann, kann eine beliebige Zahl genommen werden, um dem Programm zu sagen, wie es eine Funktion ausführen soll. Damit wird eine Anonymität aufrecht erhalten, mit der es unmöglich ist herauszufinden, welche Funktionen bei der Programmausführung tatsächlich verwendet werden. Java seinerseits verwendet Namen zum Aufruf von Methoden und Variablen. Auf Methoden und Variablen kann allein mit dem Namen zugegriffen werden. Zu bestimmen, welche Methode und Variablen verwendet werden, ist also einfach. Diese Verifizierung wird dazu verwendet, sicherzustellen, dass der Bytecode nicht beschädigt oder verändert wurde und die Anforderungen von Java als Sprache erfüllt. Zusammenfassend kann man festhalten, dass Java also auf der Sprach- und Compilerebene bereits extrem sicher ist. Java 2 Kompendium
759
Kapitel 14
Java-Sicherheit
14.1.2
Viren
Viren innerhalb von Fremdsoftware sind ein Aspekt, gegen den bisher kaum schlüssige Konzepte auf der Ebene der Programmiersprachen und Entwicklungsumgebungen präsentiert wurden. Seit Jahren empfehlen Experten dringend, jedes fremde Programm und jeden fremden Datenträger auf Viren zu überprüfen, bevor man damit arbeitet. Und im Internet verfuhr man in der Vergangenheit oft so, als befände man sich in Fort Knox. Java bietet nun auf Sprachebene diverse Kontrollmöglichkeiten gegen Viren im Javacode und überprüft defaultmäß ig den Bytecode. Wie diese Kontrolle im Detail funktioniert, kann am besten in Zusammenhang mit unkontrollierten Zugriffen auf Systemressourcen erklärt werden.
14.1.3
Unkontrollierte Systemzugriffe
Eine ähnliche Gefahr wie Viren sind unkontrollierte Festplattenzugriffe (Löschen oder Verändern von Dateien, Formatieren von Datenträgern) und sonstige Zugriffe auf Systemressourcen. Zwar ist die konkrete Thematik hier noch komplexer als bei Viren, die Logik ist allerdings identisch. Auch im Falle von Java-Applets (oder vor allem ActiveX-Controls) werden Objekte auf dem lokalen Rechner abgelegt, deren genaue Funktionalität nicht immer vollständig klar ist. Microsofts ActiveX-Technologie wird von vielen Experten als das derzeit grö ß te Sicherheitsrisiko der EDV-Welt angesehen. Wer sagt denn, dass das nette ActiveX-Control nur ein harmloses Spiel ist und die permanenten Festplattenzugriffe ausschließlich Spielstände abspeichern? Man muss sich immer darüber im Klaren sein, dass es sich bei jedem dieser Objekte um eine Art lauffähige Programm handelt, das durchaus ein anonymes (schädliches) Doppelleben führen kann. Wenn im Detail unbekannte Objekte Schreiboperationen auf der Festplatte ausführen wollen, sollten Alarmglocken klingeln. Bei aller Euphorie über Java-Applets und sonstiger toller Internetanimationen darf man nicht vergessen, dass über das Netz (oder ggf. von lokalen Datenträgern) fremde Anwendungen auf den Rechner gelangen, denen man erst einmal ein gesundes Misstrauen entgegenbringen sollte. Java stellt da mit seinem Sicherheitskonzept einen Meilenstein dar. Bei Java-Applets können unkontrollierte Festplattenzugriffe – sogar von vollkommen unbekannten Applets – einigermaßen beherrscht werden. Verantwortlich dafür ist im Wesentlichen der Container (also der Browser oder der Appletviewer). Dieser analysiert mithilfe des Applet-Classloaders bereits beim Laden aus dem Netz den Applet-Code unter anderem auf Viren und Festplattenzugriffe oder andere unerlaubte Befehle. Damit wird bereits eine Pforte aufgebaut, die viele subversive Klassen erst gar nicht überwinden können. Und falls doch, unterbindet der Container – je nach internen Einstellungen – diese Zugriffe mithilfe eines permanent laufenden Hintergrundprozesses. Selbst wenn es also ein »schlechtes« Applet geschafft hätte, den 760
Java 2 Kompendium
Das Java-Sicherheitskonzept
Kapitel 14
umfangreichen Verifzierungsprozess von Java zu umgehen, wäre der Schaden, den es anrichten könnte, doch ziemlich begrenzt, da Java-Applets nur in einer eingeschränkten Umgebung – dem so genannten Sandkasten (Sandbox) – ausgeführt werden und dort Restriktionen zu setzen sind. Sie können sich ja bei der Sicherheitseinstellung des Appletviewers oder eines Browsers ansehen, welche Einstellungen dort möglich sind. Innerhalb dieser Containerumgebung hat ein Java-Applet normalerweisee nicht die Erlaubnis, bestimmte gefährliche Funktionen auszuführen, es sei denn, der Endanwender gibt explizit seine Erlaubnis. Aber wie erfolgt genau die Verifizierung des Bytecodes?
14.1.4
Verifizierung des Bytecodes
Vor der Ausführung einer Applikation durch einen Browser oder den Appletviewer bzw. den Java-Interpreter wird jeder Bytecode diversen Tests unterzogen. Es wird beispielsweise überprüft, dass keine Zugriffsbeschränkungen verletzt werden, dass Methoden keine unzulässigen Argumente enthalten oder dass der Stack nicht überläuft. Typinformationen und Anforderungen Einen wesentlichen Anteil an der Verifizierung des Bytecodes bilden die Informationen über Typen. Java-Bytecode enthält mehr Typinformationen als der Interpreter eigentlich benötigt, beispielsweise Symboltabellen. Dadurch kann zur Laufzeit sichergestellt werden, dass Java-Objekte und -Daten nicht unbemerkt manipuliert werden. Vor und nach der Ausführung eines Java-Programms hat jeder Wert in dem Stackbereich und jede Variable einen bestimmten Typ. Man nennt dies den Typzustand der Ausführungsumgebung. Dieser Typzustand ist statistisch zu überwachen und kann dafür verwendet werden, Pfadangaben und Verzweigungen eines Programms zu verfolgen. Außerdem kann mithilfe der zusätzlichen Typeninformationen ein spezieller Java-Mechanismus aktiv werden, der Verifier genannt wird.
14.1.5
Der Verifier
Der Bytecode-Verifier gehört zur Laufzeitumgebung von Java, also der JVM. Man kann ihn sich als ersten Grenzposten für eine fertige Klasse vorstellen, welchen die Klasse passieren muss, um überhaupt erst in die Laufzeitumgebung zu gelangen. Nur eine Klasse, die sich bzgl. wichtiger Aktionen als unbedenklich ausweisen kann, darf diese äußere Grenze der Laufzeitumgebung überschreiten. Anhand der zusätzlichen Typeninformationen in jeder .class-Datei überprüft der Bytecode-Verifier den vollständigen Bytecode und kontrolliert die Typen hinsichtlich aller Parameter, Namensraumkonflikte, Argumente und Ergebnisse. Damit kann man garanJava 2 Kompendium
761
Kapitel 14
Java-Sicherheit tieren, dass Bytecode, der vom Verifier nicht moniert wurde, auf keinen Fall Stack-Overflows (sowohl nach oben als auch nach unten) erzeugen, ungültige Registerzugriffe ausführen, falsche Parameter, Argumente oder Returnwerte verwenden oder ungültige Konvertierungen von Daten und Typen sowie illegale Zugriffe auf Objektfelder erzeugen. Letzteres bedeutet, dass der Bytecode-Verifier auf strikte Einhaltung von public, private, package und protected bei Methoden und Variablen prüft. Der Verifier trägt also ebenfalls viel zu einer – rein technischen – Sicherheit von Java bei.
14.1.6
Der Classloader
Etwas höher als der Verifier ist der so genannte Classloader (Klassenlader) als ein weiterer Grenzposten gegen unbefugte Sicherheitsverletzungen aufgebaut. Wenn eine neue Klasse in ein System geladen wird, muss sie auch diesen Grenzposten passieren. Je nachdem, von wo die neue Klasse in das System geladen wird, wird sie vom Classloader unterschiedlich behandelt. Standardmäß ig gibt es drei unterschiedliche Szenarien, von wo eine neue Klasse geladen werden kann: 1.
Aus dem Internet
2.
Aus einem per Firewall geschützten lokalen Netzwerk
3.
Vom lokalen Rechner selbst
Denkbar sind jedoch beliebig viele Szenarien. Beim lokalen Laden (die Definition von lokal war bis zum JDK 1.1 mit dem CLASSPATH verbunden, worauf nachfolgend noch eingegangen wird) von der Festplatte lädt und überprüft übrigens defaultmäß ig nicht mehr der Applet-Classloader, sondern der File-Loader des Dateisystems, was unter Umständen ein erhebliches Sicherheitsrisiko sein kann. Prinzipiell sind Applets grö ß ere Sicherheitsrisiken als eigenständige Applikationen. Wenn ein fremdes Applet über Diskette oder CD auf den Rechner und dort in scheinbar sichere Verzeichnisse, d.h. Verzeichnisse innerhalb der ursprünglichen CLASSPATHAngabe bzw. in den als sicher empfundenen Suchpfad des SDK 2 gelangt, wird es nicht mehr so intensiv überprüft. Ein solches Applet sollte sehr vertrauenswürdig sein, denn es hat damit meist einige Rechte, die der Applet-Loader sonst nicht gestatten würde. Beispielsweise können diese Applets – bei entsprechender Einstellung – von der lokalen Festplatte lesen und auf diese schreiben, Bibliotheken auf dem Client laden, externe Programme auf dem lokalen Rechner ausführen, sich der Überprüfung des Bytecode-Verifiers entziehen und sogar im Prinzip die virtuelle Maschine von Java verlassen.
762
Java 2 Kompendium
Das Java-Sicherheitskonzept
Kapitel 14
Der Classloader unterbindet auf jeden Fall das Ersetzen einer Klasse in einem höher priorisierten Bereich (etwa in einem per Firewall geschützten lokalen Netzwerk) durch eine Klasse aus einem niedriger priorisierten Bereich (etwa aus dem Internet). Insbesondere lässt der Classloader nicht zu, dass eine Klasse auf dem lokalen System durch eine Klasse von »außen« unbemerkt ersetzt wird. Zudem unterbindet der Classloader einen Aufruf von Methoden einer höher priorisierten Klasse durch Klassen aus einem niedriger priorisierten Bereich, es sei denn, diese Klassen sind explizit als public deklariert. Und last but not least unterbindet der Classloader ein Aufruf von Methoden eines Applets durch ein anderes Applet, es sei denn, dieses erlaubt es ausdrücklich. Dahinter steckt eine Technik, die Applets der gleichen Quelle (Host oder lokaler Rechner) in einem »Namespace«-Paket zusammenfasst. Damit teilt der Classloader die Welt der Applets in kleine Gruppen auf, denen von vornherein unterschiedliche Sicherheitsrisiken zugeordnet werden. Als Defaultwert überprüft der Classloader nur solche Klassen, die von außen (also von einem Host, einem lokalen Netzwerk oder auch einem Verzeichnis außerhalb des sicheren lokalen Pfads) kommen. Die getrennten Namensräume sind die Begründung, warum es wie oben beschrieben nicht möglich ist, Klassen auszublenden und durch manipulierte Klassen von außerhalb zu ersetzen. Wird eine Klasse benötigt, sucht der Classloader immer zuerst innerhalb des eigenen lokalen Namenraums und dann erst in weiteren Namensräumen. Damit haben lokal installierte Klassen immer Vorrang gegenüber den Klassen, die aus einem Netzwerk bezogen werden. Dieser Vorgang setzt sich nach der Priorität nach unten fort.
14.1.7
Der Sicherheitsmanager
Der SecurityManager ist eine Klasse innerhalb vom Java-System (als abstract definiert), die als zentrale Entscheidungsstelle der Sicherheitspolitik eines Systems in Bezug auf Applets verstanden werden kann. Sie ist die vierte Ebene in der Sicherheitsphilosophie und die stärkste Stufe. Der SecurityManager überprüft als letzte Instanz der Sicherheitskette alle sicherheitsrelevanten Operationen eines Java-Programms zur Laufzeit. Das bedeutet im Fall von Applets, dass selbst ein Applet, das sogar den Classloader überwunden hat, sich nicht sicher fühlen darf. Der SecurityManager wird immer automatisch beim Start eines Browsers bzw. des Appletviewers gestartet und bleibt so lange im Hintergrund aktiv, bis der Browsers bzw. der Appletviewer wieder beendet wird. Diese Implementierung des SecurityManager lässt sich nicht abstellen. Wird innerhalb eines Applets eine unerlaubte Operation ausgeführt, löst dies eine SecurityException aus. Sicherheitsrelevante Operationen eines Applets zur Laufzeit sind u.a. das Lesen und Schreiben von Dateien und der Aufbau von Online-Verbindungen. Die Art der Beschränkung durch den SecurityManager hängt jedoch sehr stark von Umgebung der Applets ab. Dies bedeutet, dass der SecurityManager zwischen Applets unterscheidet, die aus dem Netz oder vom Java 2 Kompendium
763
Kapitel 14
Java-Sicherheit lokalen System geladen wurden. Des Weiteren ist es ein massiver Unterschied, ob ein Applet in einem Browser (und welchem) gestartet wird oder im Appletviewer. Ein Applet gilt übrigens nur dann als lokal, wenn es sich im CLASSPATH (bzw. JDK 1.1) bzw. im bei der Installation festgelegten Suchpfad des SDK 2 befindet. Nur auf dem lokalen System abzulegen genügt nicht. Dies bedeutet, dass Klassen, die sich in einem so spezifizierten Verzeichnis befinden, gegenüber dort nicht abgelegten Klassen erweiterte Rechte haben. Zusammengefasst werden geladene Klassen in Java in grö ß ere Gruppen, welchen dann unterschiedliche Rechte zugeordnet werden. Das dahinter liegende Konzept heißt Name Space und bedeutet, dass jede geladene Klasse nach ihrem Namen und dem Herkunftsort identifiziert werden kann. Die diversen Sicherheitseinstellungen kann man grob in Lese- bzw. Schreibzugriffe auf lokale Ressourcen, Aufbau von Online-Verbindungen über Netzwerke und die Manipulation eines Threads durch einen anderen Thread klassifizieren. In Bezug auf Lese- bzw. Schreibzugriffe auf lokale Ressourcen und Netzzugriffe kann man von der Java-Theorie her Applets in vier Sicherheitsstufen eingliedern: unrestricted! Ungehinderte Lese- bzw. Schreibzugriffe auf lokale Ressourcen und Netzzugriffe firewall! Ungehinderte Lese- bzw. Schreibzugriffe auf lokale Ressourcen und Netzzugriffe innerhalb einer Firewall source! Das Applet darf bzgl. Lese- bzw. Schreibzugriffe auf lokale Ressourcen nicht zugreifen und keine allgemeinen Netzzugriffe ausführen, jedoch innerhalb des Ursprungsort (dem Host, von dem es stammt) darf das Applet alles machen und damit auch dorthin Kontakt aufnehmen. local! Keinerlei Lese- bzw. Schreibzugriffe auf lokale Ressourcen und keinerlei Netzzugriffe. In Bezug auf Sicherheit sind die Defaulteinstellungen des Appletviewers recht offen. Der Appletviewer überlässt dem Anwender sehr viel Freiheit, welche Sicherheitseinstellungen er vornehmen möchte. Viele Browser sind bzgl. der Sicherheitsvorkehrungen bei Java-Applets sehr viel restriktiver. Bereits für lokale Applets lassen viele Browser keine Festlegung – insbesondere kein Löschen – von Lese- und Schreibverzeichnissen oder -dateien zu. Lediglich das Lesen von einigen Feldern (etwa user.name) oder Bibliotheken, Fensteröffnungen ohne Warnung und die relativ freie Online-Verbindung werden zugestanden. Nicht lokale Applets werden sogar bei einigen Browsern fast vollständig kastriert. Außer einem Hilferuf zum eigenen Host dürfen sie fast nichts (außer bunte Bilder im Browser malen). So etwas ist
764
Java 2 Kompendium
Das Java-Sicherheitskonzept
Kapitel 14
als Defaulteinstellung sicher gut, aber ob obendrein eine Freigabe durch einen kundigen Anwender zu unterbinden ist, darf diskutiert werden. Es wird aufgefallen sein, dass bisher im Zusammenhang mit dem SecurityManager fast nur von Applets die Rede ist. Dies ist kein Zufall, denn eigenständige Java-Applikationen sind dessen Restriktionen nicht automatisch unterworfen. Eine eigenständige Java-Applikation darf defaultmä ß ig innerhalb des Java-Interpreters fast alles tun. Dies ist sinnvoll, wenn man beispielsweise eine Dateiverwaltung oder eine Textverarbeitung in Java erstellen möchte. Um eigenständige Java-Applikationen der Kontrolle des SecurityManagers zu unterwerfen, muss man explizit die Klasse SecurityManager aus dem Paket System im Source implementieren. Installieren des SecurityManagers erfolgt am besten in der ersten Zeile der main()-Methode. Dazu kann die folgende Source-Zeile verwendet werden: System.setSecurityManager(new MeinSpeziellerSicherheitsChef());
Wenn der SecurityManager eine bestimmte Operation unterbinden soll, muss die dafür relevante Methode entsprechend überschrieben werden. Wenn jedoch der SecurityManager in einer Applikation implementiert wurde, wird jede nicht überschriebene Security-Methode eine SecurityException auslösen, sofern das zugehörige Ereignis ausgelöst wird. Deshalb ist hier viel Sorgfalt vom Programmierer gefordert.
14.1.8
Digitale Unterschriften eines Applets und Verschlüsselung in Java
Eine wichtige Neuerung im Sicherheitsbereich vollzog Sun mit der Version 1.1 des JDK im ersten Quartal 1997. Seit dieser Version können Applets und Daten (Bilder, Sounds usw.) mit einer digitalen Unterschrift versehen werden. Dadurch kann ein Applet eindeutig seinem Hersteller zugeordnet werden. Umgekehrt kann man bei einer fehlenden digitalen Unterschrift eine Ausführung des Applets unterbinden. Die Sicherheitsschnittstelle von Java bietet seit der Version 1.1 eine Unterstützung für die so genannte Public-Key-Verschlüsselung an, was Java in Bezug auf das Versenden von Daten über das Internet erheblich aufgewertet hat. Die Tools des SDK 2 und das dort außerordentlich erweiterte Sicherheitskonzept hat gerade in diese Richtung noch einmal erhebliche Fortschritte gebracht.
14.1.9
Native Code
Ein erhebliches Sicherheitsproblem bei der Analyse eines Java-Programms ist die Möglichkeit zur Einbindung von nativem Maschinencode in Java. Im Gegensatz zu dem Java-Zwischencode (einem relativ leicht zu analysierenden Bytecode) lässt sich Maschinencode nicht so einfach untersuchen. Java
Java 2 Kompendium
765
Kapitel 14
Java-Sicherheit versucht, dieses Problem mit dem SecurityManager in den Griff zu bekommen, der die systemkritischen Ressourcen sichert. Trotzdem bleibt hier sicher ein Restrisiko, und es ist eines der wichtigsten Argumente gegen die Verwendung von nativem Code in Java-Programmen (neben dem Verlust der Plattformunabhänigkeit).
14.1.10
Weitere unerwünschte Aktionen
Es gibt natürlich Sicherheitsfragen, die nicht ohne Weiteres in Java behandelt werden können. Es kann nicht Aufgabe der Entwicklungsumgebung sein, hier einen absoluten Schutz zu garantieren. Dazu zählen etwa die übermä ß ige Beanspruchung der Rechnerleistung oder des Hauptspeichers des Clients, etwa durch den Start einer beliebigen Menge an Threads, ein Aufrufen von Fenstern über eine Endlosschleife oder auch unerwünschte Inhalte von Applets. Eine andere Art von Sicherheitsverletzungen können aus den immer schon vorhandenen Schwächen der Internet-Protokolle entstehen. Hier hat Java keinerlei Kontrollmöglichkeiten auf technischer Ebene. Weitere denkbare Aktivitäten von Java-Applets und Programmen sind Aktionen wie Sperren von Ressourcen, unerwünschten Effekte auf Ausgabegeräte oder Diebstahl der Identität eines Benutzers, um mit dieser Aktivitäten auszuführen (etwa Online-Überweisungen). Java unterbindet solche Aktionen wie wir gesehen haben weitgehend. Vollkommen geht so etwas aber technisch nie, sonst kann man keine funktionstüchtigen Programme erstellen. Aber in diesem Bereich sind andere aus dem Internet kommende Programme (vor allem ActiveX-Controls) wahre Meister der potenziellen Schädigung, wie diverse Tests gezeigt haben. Damit diese Behauptung nicht ganz ohne Fundament im Raum steht (und die Qualität des Java-Sicherheitskonzeptes noch deutlicher wird), soll kurz das ActiveX-Sicherheitskonzept (?) angerissen werden.
14.2
ActiveX-Sicherheitskonzept im Vergleich
Um ActiveX-Controls im Vergleich mit Java-Applets zu sehen, wollen wir zuerst kurz klären, was es mit diesen ActiveX-Controls überhaupt auf sich hat.
14.2.1
Was sind ActiveX-Controls?
ActiveX-Controls werden oft als Microsoft-Alternative zu Java-Applets gesehen. Der Begriff ActiveX bezeichnet jedoch nach Aussage von Microsoft keine einzelne Technik oder SDK, sondern ist ein übergeordneter Name für Microsofts Internet und Multimedia-Technologien. Unter ActiveX ist eine Verbindung von vielen verschiedenen Entwicklungstechniken zu verste-
766
Java 2 Kompendium
ActiveX-Sicherheitskonzept im Vergleich
Kapitel 14
hen, die alle auf dem Component Object Model (COM) und/oder der Win32 API aufbauen. ActiveX ist ein ganzes Set von offenen Techniken, die die Leistungsfähigkeit von PCs auf das Internet/Intranet und Netzwerk allgemein übertragen soll. Dieses Set wird permanent von Microsoft weiterentwickelt und in Windows integriert. Insbesondere versteht Microsoft die ActiveX-Technologie nicht als direkte Konkurrenz zu Java, sondern als eine Ergänzung. Allerdings sollten sich dann die Entwickler darüber im Klaren sein, dass sie eine Haupteigenschaft von Java aufgeben, wenn sie ActiveX mit Java verbinden – die Plattformunabhängigkeit. ActiveX ist nämlich im Gegensatz zu HTML oder Java explizit nicht plattformunabhängig, sondern läuft (zurzeit) nur auf einer Computerarchitektur und einer Betriebssystemfamilie – INTEL-PCs und WINDOWS (und mit Einschränkungen auf Power Macintosh). Von der Einschränkung des potenziellen ActiveX-“Kundenkreises« auf die WINTEL-Plattform verspricht sich Microsoft ganz massive Vorteile. ActiveX-Controls sind in der Lage, alle Leistungen des Betriebssystems Windows auszuschöpfen. Sie sind eine direkte Nachfolgegeneration der OCX-Controls, die wiederum ja eine speziell für die 32-BitUmgebung von Windows 95/NT hergeleitete Weiterentwicklung der VBXControls sind. Ein ActiveX-Control kann also auf alle Möglichkeiten des Win32-API zurückgreifen und wird auch als ein in eine HTML-Seite eingebundenes Element genauso aussehen und zu bedienen sein wie ein »normales« WindowsSteuerelement. Im Mittelpunkt der Microsoft-Internetphilosophie steht die OLE-Funktionalität (Object Linking and Embedding, Objekte verbinden und einbetten) von Windows. Lange war die OLE-Technik nur als Verbindung von dynamischem Linken (der DDE = Dynamic Data Exchange) und statischem Einbetten (in etwa wie der Datenaustausch über die Zwischenablage), beziehungsweise als eine Erweiterung dieser beiden Techniken um integrierte Hinweise auf ihre Ursprungsanwendungen bekannt. Insbesondere konnten die OLE-Objekte lange nur lokal, das heißt auf einem Rechner eingesetzt werden. Als Beispiel sei die Einbindung einer EXCEL-Tabelle in WINWORD genannt. Im Rahmen der Netzwerkentwicklung von Windows wird die lokale Einschränkung aufgehoben. Die Weiterentwicklung von OLE (DCOM = Distributed Component Object Model) war schlichtweg die Übertragung der OLE-Technik auf Netzwerke und vor allem das Internet. Mit dieser Technik spielt es keine Rolle mehr, ob ein Objekt auf dem lokalen Rechner oder einem beliebigen Server im Netz beheimatet ist. Auf Grund der integrierten Hinweise auf ihre Ursprünge wird bei einer Aktivierung eines externen Elements der zugehörige Rechner kontaktiert und dort die notwendige Aktion ausgeführt.
Java 2 Kompendium
767
Kapitel 14
Java-Sicherheit Eine besondere Art von OLE-Objekten sind die Windows-Steuerelemente, die Controls. Auch bei den ActiveX-Controls handelt es sich wie schon erwähnt um eine Weiterentwicklung der herkömmlichen OLE-Controls. Vor allem sind die ActiveX-Controls gegenüber den OCX-Controls auf das Wesentliche reduziert und damit verglichen mit diesen bedeutend kleiner. Die OCX-Controls schleppen eine Unmenge an im Prinzip überflüssigem Ballast aus der MFC (Microsoft-Foundation-Classes) mit, der hier durch ein neues Konzept weggelassen werden kann. ActiveX-Controls verzichten zudem auf eigene Fenster, sondern delegieren diese Aufgabe an den Container, beispielsweise einen Webbrowser. Neben der Verkleinerung der Controls bringt dieses Verfahren einen weiteren Vorteil – die Controls sind nicht mehr an die Fensterform gebunden. Jede denkbare Form ist im Prinzip realisierbar. Allerdings hat der Container dann eine Menge zu tun, um »Löcher« oder »Flicken« im Bildschirm zu verhindern und Benutzerinteraktionen des Controls zu verarbeiten. ActiveX-Controls lassen sich wie Java-Applets in jede HTML-Seite einbinden. Die ActiveX-Controls werden wie Java-Applets bei Bedarf automatisch aus dem Internet auf den Client geladen und installiert. Das HTML-Tag bindet das ActiveX-Control in die Webseite ein. Diese kann ja seit HTML 4.0 auch zum Einbinden von beliebigen Objekten (insbesondere auch Applets) in eine Webseite genutzt werden. Ursprünglich wurde die Object-Tag-Syntax nur für ActiveX-Controls verwendet: [ActiveX-Control-Eigenschaften]
Die neuere Variante des Tags referenziert ActiveX-Controls etwas anders. Etwa so, als ob man ein Plug-In für die JVM als ActiveX-Control angeben möchte:
Bei beiden Varianten gilt aber, dass bei der Referenzierung mit CLASSID das Control über einen weltweit eindeutigen 128-Bit-Wert identifiziert und mit WIDTH=[Wert] HEIGHT=[Wert] die Grö ß e des Controls festgelegt wird. Über die CLASSID kann ein Browser entscheiden, ob ein Control bereits geladen wurde 768
Java 2 Kompendium
ActiveX-Sicherheitskonzept im Vergleich
Kapitel 14
und ob es ggf. eine neuere Version eines bereits vorhandenen Controls ist. Auf dieser CLASSID baut jedoch zugleich das Sicherheitskonzept von ActiveX auf.
14.2.2
Sicherheitskonzept von ActiveX
Für ActiveX-Controls gibt es eigentlich gar kein echtes Sicherheitskonzept, denn der Anwender muss im Wesentlichen einer Registrierungsnummer des Controls vertrauen, die als digitale Unterschrift über spezielle Tools der ActiveX-SDK in diese eingebaut wurde. Sofern ein Anwender ein ActiveXControl auf seinen Rechner lädt, wird diese digitale Unterschrift überprüft und – falls der Browser entsprechend konfiguriert ist – bei fehlender Registrierungsnummer das Laden verweigert. Diese Überprüfung lässt sich zu allem Überfluss auch noch bei manchen Browsern vollständig deaktivieren. Wer nicht glaubt, dass ActiveX-Controls böse Dinge mit dem lokalen Rechner anstellen können, wenn die Sicherheitsschranke zum Download nicht zertifizierten Codes deaktiviert ist, sollte im Internet einmal gezielt nach Demos suchen. Es gibt zahlreiche Webseiten, die als Demo für die Unsicherheit von ActiveX-Controls dienen. So gab es schon sehr früh das Control Exploder (kein Schreibfehler – http://www.halcyon.com/mclain/ActiveX), das einen PC aus einer Webeite heraus sauber herunterfährt. Wohl bemerkt, es handelt sich nicht um einen Virus, sondern ein ActiveX-Control zu Demonstrationszwecken. Auf der Seite finden Sie zahlreiche interessante Hinweise zu dem Thema. Abbildung 14.3: ActiveX oder wie man Nuklearbomben in Webseiten einbindet
Java 2 Kompendium
769
Kapitel 14
Java-Sicherheit Microsoft teilt zwar die Internetwelt in seinem Browser in vier Zonen ein, für die unterschiedliche Sicherheitstufen eingestellt werden (Internetzonen, Lokale Intranetzone, Zone für vertrauenswürdige Seiten und Zone für eingeschränkte Seiten). Je nach eingestellter Stufe (von hoch bis angepasst) werden verschiedene Maßstäbe an das Zertifikat gestellt. Die einzelnen Zonen haben jeweils voreingestellte Sicherheitslevel, sind also eigentlich nur eine andere Ausdrucksweise für die Sicherheitslevel (und bieten zusätzlich etwas Verwaltungskomfort). Nur in der niedrigen Stufe kann die Überprüfung der Zertifizierung vollkommen abgeschaltet werden. Aber noch einmal zurück zu der digitalen Unterschrift. Was bringt diese an Sicherheit und woher kommt sie überhaupt? Diese digitale Unterschrift bekommt ein ActiveX-Control, sobald es sich (d.h. sein Entwickler) bei einer von Microsoft lizensierten Organisation registrieren lässt, etwa bei der Firma Verisign. Die Funktion des ActiveX-Control wird allerdings nicht überprüft und es wird damit nicht gesagt, dass das Control harmlos ist. Aber zumindest weiß der Anwender im Schadensfall, wer ihm die Festplatte zerstört hat. Wenn das Control über das Netz geladen wird, erscheint ein Hinweis, dass das Control von der Firma xyz als vertrauenswürdig eingestuft wurde. Das hilft zwar nichts, aber es beruhigt doch ungemein, dass Microsoft zumindest glaubt, niemand bringe ein unregistriertes (d.h. ohne oder mit gefälschter ID) Control in Umlauf. Außerdem hat noch niemand in der EDV-Welt etwas von fehlerhaften Programmen gehört. Sämtliche Naturgesetze stellen doch sicher, dass Programme nie Fehler haben. Oder ist Ihnen jemals ein Fehler in einem Programm untergekommen? Wie kann da ein ActiveX-Control Fehler haben? Eine Überprüfung ist also rein naturwissenschaftlich überflüssig. Sie stimmen sicher zu, dass dies unmöglich ist, oder :-)?! Aber im Ernst, das Registry-and-Trust-Sicherheitskonzept von ActiveX-Controls liegt auf dem Niveau der Aussage »Für was brauche ich einen Sicherheitsgurt, ich habe doch ein vierblättriges Kleeblatt im Handschuhfach« (Bill Gates möge entschuldigen). Nicht umsonst lassen sich mit (zugegeben meist unregistierten) ActiveX-Controls alle möglichen Dinge anstellen, die einem Anwender schaden können. Dagegen kann man glatt sämtliche Schwachstellen im Java-Komzept fast vergessen. Wenn Sie wollen, können Sie die (Missbrauchs-)Möglichkeiten von ActiveX-Controls gerne mit der Datei activeX.html testen. Sie finden sie auf der Buch-CD oder unter http:// rjs.de/activeX.html (Groß- und Kleinschreibung beachten)1. Laden Sie die Datei im Internet Explorer mit herunter gelassenen Toren, d.h. mit aktivierten ActiveX-Controls (auch nicht-signierte) und geöffneter Internet-Verbindung. Das in der Webseite integrierte ActiveX-Control zeigt Ihnen im Internet Explorer im Rahmen der Webseite den Inhalt der Festplatte C: an. Sie 1
770
Die Webseite http://www.astalavista.de, wo diverse Hacker- und Auswertungsmöglichkeiten im Rahmen einer HTML-Seite (meist recht harmlose Techniken wie Cookies oder Scripte) gezeigt werden, verwendet das ActiveX-Control als Highlight auch.
Java 2 Kompendium
Bekannte Sicherheitslücken der Java-Vergangenheit
Kapitel 14
können beliebig Verzeichnisse öffnen, Ansichten wechseln und auch Kontextmenüs aktivieren und deren Befehle nutzen. Mehr an Schaden verursacht das Control erst einmal nicht, aber dennoch – es ist eine Einladung zum offenen Missbrauch des eigenen Rechners. Abbildung 14.4: Der Dieb klopft an (zumindest, wenn nicht alle Sicherheitseinstellungen vollkommen deaktiviert sind).
Abbildung 14.5: Der Dieb ist drin und wildert auf der Festplatte.
Es ist bezeichnend, dass die Unsicherheit von ActiveX auf der im August stattgefundenen HIP 97, dem grö ß te Hackertreffen Europas in Almere, Holland, mit 2500 Teilnehmern eines der Hauptthemen war. Mehr Informationen zum ActiveX-Sicherheitskonzept und ActiveX im Allgemeinen (FAQ) gibt es im Internet unter der bereits genannten Adresse (Contra) http://www.halycon.com/mclain/activeX/2 oder als Pro-Anwalt http://www.microsoft.com/activex/ bzw. http://www.microsoft.com/com/.
14.3
Bekannte Sicherheitslücken der Java-Vergangenheit
Selbstverständlich gab und gibt es auch in Java diverse Sicherheitslücken. Absolute Sicherheit ist eine Illusion und kann man höchstens von der Religion erwarten. Darüber wird es sicher keine Diskussionen geben. 2
oder – falls die Seite verlagert wurde – in einem anderen Verzeichnis unter diesem DNS-Namen
Java 2 Kompendium
771
Kapitel 14
Java-Sicherheit
Abbildung 14.6: Pro-ActiveXControl-Infos von Microsoft
Einige Bugs in Java wurden in der Vergangenheit ziemlich bekannt. Deshalb sollen sie angesprochen werden. Die Auswahl erhebt aber keinen Anspruch auf Vollständigkeit.
14.3.1
Der Bytecode-Bug
Manche Festplattenzugriffe durch Applets sind nicht destruktiv und es ist sicher nicht jedermanns Sache, vor dem Start eines Applets jedes Mal die Optionen des Browsers umzustellen, wenn man ein Applet verwenden will, das Systemzugriffe benötigt. Aber wer weiß schon genau, was er dann auslöst, wenn er – sogar bewusst – Festplattenzugriffe erlaubt? Zwar lassen sich viele destruktive Verhaltensweisen eines Applets abfangen, jedoch in älteren Java-Versionen konnten die Sicherheitseinstellungen von Containern von Java-Applets bzgl. Festplattenzugriffen durch Simulation eines Laufzeitfehlers ausgehebelt werden. Genauer handelte es sich dabei um einen Bug im Bytecode-Verifizierer, der eine Sicherheitslücke im Java-System bedeutete. Laut Aussagen der Entdecker des Fehlers (Drew Dean, Ed Felten und Dan Wallach) konnte ein Applet fähig sein, Maschinencode zu erzeugen, der in der Lage war, ein Applet mit gleichen Rechten auszustatten, wie sie der Anwender auf dem Rechner besitzt. Anschließend durfte ein Applet alle Befehle aufrufen, die dem Anwender erlaubt waren. Der Fehler lag in dem Teil des Interpreters, der über das Netz geladene Applets auf ungültige oder verbotene Befehle hin überprüft. Ein Applet konnte bei genauer Kenntnis dieses Fehlers den Restriktionsmechanismus außer Kraft setzen. Dieser Fehler ist seit Jahren beseitigt.
772
Java 2 Kompendium
Bekannte Sicherheitslücken der Java-Vergangenheit
14.3.2
Kapitel 14
Schwarze Witwen und der DNS-Bug
Neben der Gefahr eines destruktiven Verhaltens auf Datenträgern lauert hinter Java-Applets immer ein weiteres Schweinchen. Spionage! Wenn Sie Online arbeiten, können Sie selbstverständlich nicht nur Daten empfangen. Bewusst versenden Sie E-Mails, Dateien oder permanent Anforderungen für Webseiten oder FTP-Pakete. Applets können im Prinzip Mechanismen integriert haben, die ebenfalls – allerdings heimlich – Daten versenden, wenn eine Online-Verbindung besteht, z.B. den Inhalt Ihrer Datenträger oder die Adressen von mitprotokollierten Internetseiten. Keine sonderlich beruhigende Vorstellung. Von der Java-Theorie her darf es das zwar nicht geben, aber die ersten Spionage-Applets wurden Mitte 1996 unter dem Namen »Schwarze Witwen« bekannt. Zwar lassen sich im Prinzip von vielen Browsern bzw. dem Appletviewer sowohl Lesezugriffe auf die Festplatte als auch die Kommunikation mit Hosts unterbinden, aber Java war in der Vergangenheit in der Lage, auf einigen Plattformen über den Umweg eines DLLFunktionsaufrufs diese Hürde zu umgehen. Die ungewollten Verbindungsaufnahmen gingen in der Vergangenheit sogar so weit, dass prinzipiell beliebige Server von Java-Applets kontaktiert werden konnten. Dabei sollte von der Java-Theorie her ein Applet bei entsprechender Einstellung des Containers (Appletviewer bzw. Browser) nur maximal Kontakt zu dem Server aufnehmen können, von dem es stammt (wer unbeschränkte Kontaktaufnahme im Appletviewer oder in einen justierbaren Browser eingestellt hat, der verdient es einfach nicht besser). Ein Fehler, der sowohl Netscape 2.0 als auch den Appletviewer JDK 1.0 betroffen hat, ist der so genannte DNS-Bug. Der Fehler basierte darauf, dass ein Applet-Programmierer einem beliebigen Host im Internet eine zusätzliche IP-Adresse und einen dementsprechenden Namen zuweist. Diese Adresse muss dann aus der Domain stammen, aus der die Applets angefordert werden. Weiterhin muss sie an erster Stelle innerhalb der Liste der IPAdresse stehen. Java führt eine Kontrolle durch, ob eine Verbindung zu dem Server erlaubt ist und stellt dann direkten Kontakt zum ersten Eintrag in der Liste her, sogar wenn dies nicht die Ursprungsadresse war. Es gab also somit keine Überprüfung der IP-Adressen, sondern nur der Namen. Durch diese Lücke war es ebenso möglich, jede Firewall auszutricksen. Dieser Fehler ist jedoch offiziell seit der Netscape-Version 2.01 und der JDK-Version 1.01 behoben. Für neuere Varianten ist das Problem irrelevant.
14.3.3
Der CLASSPATH-Bug
Auch der so genannte CLASSPATH-Bug in Verbindung mit dem Navigator 2.01 machte deutlich, dass das Sicherheitskonzept von Java in der Vergangenheit Lücken hatte. Laut der Spezifikation kann ein Applet nur lokale Java-Klassen nachladen. Das waren im damaligen Konzept also Klassen, die sich innerhalb der mit CLASSPATH gesetzten Pfade befinden. Die dort Java 2 Kompendium
773
Kapitel 14
Java-Sicherheit befindlichen Klassen besitzen erweiterte Zugriffsrechte. Deshalb wurde schon von Anfang an empfohlen, dort nur die Originalklassen von Sun und eigene, getestete Klassen zu platzieren. David Hopwood von der Oxford University hat aber nun entdeckt, dass es mit dem Interpreter von älteren Netscape-Versionen trotzdem möglich ist, Klassen aus beliebigen lokalen Verzeichnissen nachzuladen. Diese haben dann ebenfalls erweiterte Rechte. Dieser Bug ist in allen Nachfolgeversionen des Navigators 2.01 behoben und hat im Prinzip nichts mit dem eigentlichen Java-Sicherheitskonzept zu tun. Zusätzlich wird ja auch im SDK 2 der CLASSPATH nicht mehr verwendet, sondern ein neues Konzept zur Freigabe von Rechten verfolgt.
14.3.4
Weitere Bugs
Den gerade explizit genannten bekannteren, frühen Bugs folgten diverse weitere. In der Hochphase der Java-Euphorie schien es zu einer Art Sport geworden zu sein, Bugs im Java-Sicherheitskonzept zu finden, so konstruiert und unwahrscheinlich die Ausgangssituationen auch waren. Hauptsache, das Konzept hatte irgendeine Lücke. Im Frühsommer 1997 gingen beispielsweise Horrormeldungen durch die Medien, dass Forscher der University of Washington im Rahmen des Kimera Project 24 neue Sicherheitsrisiken in der damals aktuellen Version des JDK 1.1 entdeckt hätten. Bestätigt wurde davon nur ein echter Fehler. Dennoch entstand der Eindruck, dass, wo Rauch ist, meistens auch Feuer ist. Warum jedoch diese Diskrepanz? Es liegt wohl im Wesentlichen an zwei Dingen. Ein Aspekt ist die Bewertung der gefundenen Bugs. Was ist ein Sicherheitsrisiko und was ein Fehler, der nur in extremen Ausnahmesituationen auftritt? Ein »Fehler« wurde beispielsweise in dem Teil der virtuellen Java Maschine entdeckt, der den Bytecode überprüft. Beim Füttern des »Bytecode Verifiers« mit zufällig gewählten Bytefolgen konnte es zu einem Stack-Überlauf kommen und damit zum Absturz der JVM (auch nach allgemeiner Einschätzung kein Sicherheitsrisiko, nichtsdestotrotz hatte SUN natürlich ein Bug-Fix entwickelt). Wer mit Windows-Software arbeitet, wird garantiert zustimmen, dass dort Abstürze und Fehlleistungen der Windowsprogramme oder des Betriebssystems unter weniger konstruierten Bedingungen zur Tagesordnung gehören. Dennoch hisst niemand gleich die Panikflagge. Ein weiterer Grund für die Diskrepanz der Fehlerzahlen lag in den unterschiedlichen Testmethoden. Grundsätzlich wurde um Java-Bugs viel Sturm in einem Wasserglas veranstaltet. Man unterscheidet bei den Bugs zwischen Security Bugs, System Bugs und Language Bugs. Echte Sicherheitsbugs sind über die Java-Zeit nur äußerst selten entdeckt worden. Und in der letzten Zeit nehmen die Meldungen rund um Sicherheitsprobleme von Java immer mehr ab. Dies darf als gutes Zeichen gewertet werden, denn das Interesse bestimmter Kreise, Sun und Java zu schaden, ist nicht geringer geworden. Wenn diese Kreise keinen Wind mehr machen, haben sie auch nichts Erwähnenswertes mehr gefunden. 774
Java 2 Kompendium
Zusammenfassung
14.3.5
Kapitel 14
Informationen zur Sicherheit
Mehr Informationen zu bisher bekannt gewordenen Java-Bugs und allgemeinen Sicherheitsfragen rund um Java gibt es unter http://java.sun.com/ security/. Dies betrifft sowohl allgemeine FAQs, aber auch eine Bug-Chronologie. Abbildung 14.7: Sicherheitsinformationen zu Java
14.4
Zusammenfassung
Es gibt keine absolute Sicherheit. Auch nicht in Java. Zwar sind mittlerweile diverse Bugs erkannt und beseitigt, aber wo eine Sicherheitslücke erkannt wird, können sich durchaus noch weitere verbergen. Darüber muss man sich im Klaren sein. Aber man sollte beachten, dass die Sicherheitslücken in Java sehr spezielle Situationen voraussetzen und von subversiven Elementen nur sehr schwierig zu nutzen sind. Auch das sicherste Verkehrsmittel der Welt, das Linienflugzeug, stürzt gelegentlich ab. Dennoch fliegen viele Leute, weil es im Verhältnis zum Risiko einfach die beste – und teilweise einzig sinnvolle – Lösung ist. So sollte man auch das Java-Sicherheitskonzept sehen. Und wem es zu ungenügend erscheint, der sollte nur noch einmal einen kurzen Blick auf das ActiveX-Control-Konzept werfen. Obwohl es noch den letzten Beweis schuldig ist, scheint das Java-Sicherheitskonzept zu den derzeit besten Systemen auf dem EDV-Markt zu zählen.
Java 2 Kompendium
775
15
Andere Sprachen in Verbindung mit Java
Wenden wir uns nun Java und seiner Verbindung anderen Programmiersprachen und Technikwelten zu. Es geht also im Kern um die Beziehung von Java und so genanntem nativen Code. Als wichtiger Vertreter soll der direkte Programmiervorfahre von Java behandelt werden, C/C++. Daneben werden wir uns mit JavaScript und anderen Scriptsprachen im Web beschäftigen und wie sie in Verbindung mit Java genutzt werden können.
15.1
C/C++ – Unterschiede und Gemeinsamkeiten sowie die Einbindung
Java hat als direkter C/C++-Ableger viele Gemeinsamkeiten mit seinem Programmierahn. Wir wollen die entscheidenden Gemeinsamkeiten und vor allem die wichtigsten Unterschiede und Vorteile von Java etwas genauer betrachten.
15.1.1
Unterschiede und Gemeinsamkeiten
Die Gemeinsamkeiten von Java und C/C++ beginnen auf der Syntaxebene. Man kann es so ausdrücken: Was in beiden Sprachen von der Funktion her vollkommen gleich ist, wird auch in der Syntax gleich sein. Sun wollte damit Java für C/C++-Progammierer schon auf den ersten Blick vertraut und verständlich machen und die Einarbeitungszeit kurz halten. Diese Vertrautheit birgt jedoch das Risiko, dass geübte C/C++-Progammierer gleichermaßen viele Dinge so in Java versuchen, wie sie es aus C/C++gewohnt sind, selbst wenn Java dafür andere Mechanismen vorgesehen hat. Bei der Java-Sicherheit haben wir ja schon gesehen, dass Java im Gegensatz zu C/C++ keine Pointer, keine Mehrfachvererbung, keine Zeigerarithmetik oder ungeprüfte Typumwandlung benutzt. Außerdem entfällt die Speicherallokation. Lassen Sie uns die wichtigsten Unterschiede jetzt im Einzelnen ein bisschen genauer beleuchten. Pointer Java besitzt keinen expliziten Pointer-Typ, sondern verwendet dafür immer Referenzen. Diese impliziten Referenzen werden für Objekte, VariablenzuJava 2 Kompendium
777
Kapitel 15
Andere Sprachen in Verbindung mit Java weisungen, an Methoden weitergegebene Argumente und Array-Elemente verwendet. Eigentlich ist es eine kleine Lüge, wenn man Referenzen und Pointer als absolut verschiedene Dinge bezeichnet, denn streng genommen sind sie im Wesentlichen das Gleiche. Auch Java muss ja auf irgendeine Weise Speicher zuweisen und zugewiesenen Speicher durch Verweise ansprechen können. Daher sind diese Referenzen, auch wenn sie anders bezeichnet werden, im Grunde genommen nur die Javaversion von Zeigern. Mit einem kleinen, aber feinen (und in Bezug auf Sicherheit extrem wichtigen) Unterschied – man kann auf Referenzen keine Pointer-Arithmetik anwenden und muss es durch das ausgefeilte Referenzkonzept auch nicht. Durch den Wegfall der Pointerarithmetik ist es ebenso nicht möglich, Objekt- und Array-Referenzen in Integerwerte zu casten oder die Grö ß en der primitiven Datentypen in Bytes zu berechnen. Dieses ausgefeilte Referenzkonzept von Java ermöglicht ebenfalls das einfache Erstellen von Strukturen wie verknüpften Listen, und zwar ohne die Verwendung von expliziten Pointern! Vielmehr wird lediglich ein verknüpfter Listenknoten mit Variablen, die auf den nächsten und vorherigen Knoten zeigen, erstellt. Um dann Elemente in die Liste einzufügen, werden diese Variablen anderen Knotenobjekten zugewiesen. Arrays Arrays sind in Java Klassenobjekte, und Referenzen auf Arrays und ihre Inhalte werden wie gerade gesehen nicht durch Pointer-Arithmetik, sondern durch explizite Referenzen realisiert. Die Array-Grenzen werden streng eingehalten, was in Hinblick auf die Sicherheit Java extrem zuverlässig in diesem Bereich macht. In der Praxis bedeutet dies, dass ein Versuch, über das Ende eines Arrays hinaus zu lesen, zu einem Kompilier- oder Laufzeitfehler führt. Wie bei allen Objekten wird durch Weitergabe eines Objekts an eine Methode eine Referenz auf das Originalobjekt erzeugt, also im Falle von Arrays eine Referenz auf das Original-Array. Verändert sich der Inhalt der Array-Referenz, ändert sich auch das Objekt des Original-Arrays. Die Beseitigung von Arrays aus dem Speicher läuft wie bei allen Objekten automatisch über den Garbage Collector. Es gibt bei Java-Arrays noch eine Besonderheit gegenüber C/C++. In Java gibt es keine multidimensionalen Arrays wie C und C++, sondern es müssen Arrays erstellt werden, die dann als Inhalt andere Arrays enthalten. Strings Strings sind in C und C++ Zeichenketten, die immer mit einem Nullzeichen (\0) enden. Strings werden in C und C++ genauso wie andere Arrays behandelt. Deshalb muss man auch dort extrem aufpassen, dass man mit Pointern
778
Java 2 Kompendium
C/C++ – Unterschiede und Gemeinsamkeiten sowie die Einbindung
Kapitel 15
das Ende einer Zeichenketten nicht überschreitet. In Java gibt es als rein objektorientierte Sprache ja nur Objekte und deshalb werden natürlich auch Strings als Objekte behandelt. Damit werden alle Methoden, die auf Strings angewandt werden, einen String nur als komplette Einheit behandeln. Strings werden nicht durch ein Nullzeichen (\0) beendet, und das Ende eines Strings kann dementsprechend nicht versehentlich überschritten werden. Speichermanagement In C/C++ muss sich ein Programmierer immerfort manuell um die Speicherverwaltung kümmern. Dies beinhaltet das Beschaffen von Speicher für Variablen, Funktionen usw.. Noch entscheidender ist, dass er sich gleichermaßen um das explizite Freigeben von Speicher für jedes Objekt kümmern muss. Hier sind neben fehlerhafter Pointerverwaltung die meisten Probleme bei C/C++-Programmen zu finden. Es wird einfach regelmäß ig vergessen, allokierten Speicher mit free() wieder freizugeben. In Java erfolgt das gesamte Speichermanagement automatisch. Speicher wird beim Erstellen eines Objekts mit dem new-Operator automatisch zugewiesen. Die Garbage Collection – ein Papierkorb, der zur Laufzeit immer als Hintergrundprozess mitläuft – gibt den jeweils zugewiesenen Speicher wieder frei, wenn auf das Objekt keine Referenz mehr vorhanden ist. C-Funktionen wie malloc() und free() existieren in Java nicht mehr. Sollte es aus irgendeinem Grund notwendig sein, manuell den Speicher freizugeben, kann man die Garbage Collection aus Java aufrufen, um die Freigabe des von einem Objekt belegten Speichers zu erzwingen. In diesem Fall werden wie bei der automatischen Freigabe alle Referenzen auf das Objekt entfernt (Zuweisung von Variablen, die es auf Null halten, Entfernen des Objekts von Arrays usw.). Bei der nächsten Ausführung gibt der Java-Papierkorb den zugeteilten Speicher zurück. Vererbung und Schnittstellen Sowohl C/C++ als auch Java arbeiten mit dem Mechanismus der Vererbung. Im Gegensatz zu C++ unterstützt Java jedoch keine Mehrfachvererbung. Das erscheint erst einmal als eine große Einschränkung, macht Java-Programme allerdings stabil und leicht wartbar. Dafür bietet Java ja einen ähnlich leistungsfähigen Mechanismus, der dennoch die Wartbarkeit erhält – das Konzept der Schnittstellen, die es in dieser Form in C++ nicht gibt. Objekte können in Java eine beliebige Anzahl an Schnittstellen oder abstrakten Klassen implementieren. Die Java-Schnittstellen sind wie IDLSchnittstellen (Interface Description Language) aufgebaut. Eine Schnittstelle ist in der Java-Sprache eine Sammlung von Methodennamen ohne konkrete Definition sowie Konstanten. Obwohl eine einzelne Java-Klasse nur genau eine Superklasse haben kann (und genau eine haben muss!), können in einer Klasse mehrere Schnittstellen implementiert werden.
Java 2 Kompendium
779
Kapitel 15
Andere Sprachen in Verbindung mit Java Datentypen Die Standardmenge der Datentypen aus C/C++ wird innerhalb von Java um die Datentypen boolean und byte erweitert. In Java haben alle primitiven Datentypen auf allen Plattformen feste Grö ß en und Eigenschaften. In Java wird also die Grö ß e einfacher Datentypen genau spezifiziert, und auch, wie sich die Arithmetik (Berechnungsvorschriften) gegenüber diesen Datentypen verhält. Fast alle wichtigen Prozessoren unterstützen diese Java-Spezifikation und außerdem haben die Java-Bibliotheken portierbare Schnittstellen für die wichtigsten Plattformen integriert. Unter C/C++ gibt es vorzeichenlose Datentypen. Java kennt sie mit Ausnahme von char, einer vorzeichenlosen 16-Bit-Ganzzahl, nicht. Datentypen entstehen in Java ausschließlich durch Verwendung von Klassendefinitionen. Kombinationen von Datentypen wie short int gibt es in Java nicht. Die C/C++-Schlüsselwörter struct, union und typedef gibt es nicht mehr, nur noch Klassendefinitionen. Bzgl. der Umwandlung von Datentypen in andere Datentypen ist Java bedeutend restriktiver als C/C++. Besonders, wenn es um automatische Umwandlung geht, wo unter C/C++ dabei Informationsgehalt verloren gehen kann. Automatisch wird in Java nur umgewandelt, wenn kein Risiko eines Informationsverlustes besteht (also im Wesentlichen nur dann, wenn der Zieltyp aus gleich vielen oder mehr Byte besteht). Alle anderen Umwandlungen müssen explizit angefordert werden. Insbesondere sind die primitiven Datentypen und Objekte gegen eine direkt Umwandlung ineinander abgeschottet. Dafür gibt es in Java Methoden und spezielle WrapperKlassen, um Werte zwischen Objekten und Primitivtypen zu konvertieren. In Java werden einfache Datentypen immer als Call-by-Value und Referenztypen immer als Call-by-Reference behandelt. Java kennt keine nicht-initialisierten Variablen, sondern jede Variable erhält einen wohldefinierten Anfangszustand. Die C/C++-typischen Probleme mit nicht initialisierten Variablen können nicht vorkommen. In Java erhalten alle Referenzdatentypen den Wert null. Dieses reservierte Schlüsselwort weist darauf hin, dass eine Variable nicht auf ein Objekt verweist. Boolesche Werte In C/C++ beruhen Entscheidungen auf Grund boolescher Werte innerhalb von Programmsteuerungsanweisungen wie if, while, for oder do genauso wie in Java (wo sie syntaktisch vollkommen gleich sind) auf den beiden möglichen Ausprägungen true und false dieser booleschen Werte. Der entscheidende Unterschied ist, dass Java einen tatsächlichen booleschen Wert (true oder false) als Typ benötigt, während für C und C++ true oder false »0« oder »ungleich 0« sind. Dabei bedeutet »ungleich 0« eine beliebige
780
Java 2 Kompendium
C/C++ – Unterschiede und Gemeinsamkeiten sowie die Einbindung
Kapitel 15
Ganzzahl. Somit ist es in Java nicht möglich, ein Casting von einem boolean-Typ in einen nicht-boolean-Typ durchzuführen. Der Unicode-Standard Java benutzt für die Darstellung eines char-Typs immer 16 Bit, C/C++ wie die meisten konventionellen Programmiersprachen nur 8 Bit. Dies ist darauf zurückzuführen, dass C/C++ den ASCII-Zeichensatz verwendet und Java die neuere Unicode-Codierung, die eine 16-Bit-Codierung für Zeichen benötigt. Auf dieser Unicode-Codierung beruht die Fähigkeit von Java, landesspezifische Zeichen darzustellen, denn selbstverständlich lassen sich in 16 Bit mehr Zeichen codieren als in 8 Bit. package und import Das Packet-Konzept von Java gibt es nicht in C/C++. Dementsprechend gibt es dort die Anweisungen package und import nicht. In Java dient die Anweisung package dazu, mehrere Klassen zu einem Paket zusammenzufassen. Durch die Anweisung import werden einzelne Pakete in einem Programm verfügbar gemacht. Die C/C++-Anweisung include zur Einbinden von Klassen/Dateien wird durch das Statement import und die Paketstruktur in Java völlig überflüssig. Der Compiler und der Interpreter wissen immer exakt, wo sich die einzelnen Klassen befinden. Der new-Operator Die meisten Operatoren werden in Java genauso wie in C/C++ verwendet. Eine Besonderheit ist der Operator new, um die Instanz einer Klasse zu erzeugen. Diesen new-Operator gibt es in C/C++ und in Java. Das Schlüsselwort new zum Erstellen eines neuen Objekts wird jedoch in Java viel besser in das Gesamtkonzept integriert. Der Befehl new hat in Java die gleiche Bedeutung wie der Befehl malloc() unter C. Mit malloc() wird in C der Speicher für die Instanz einer struct allokiert. new hat in Java ebenfalls die Aufgabe einer dynamischen Allokierung von Speicher. Wie wir bei der Objektorientierung von Java gesehen haben, definiert man zur Einhaltung des streng objektorientierten Konzeptes so genannte Metaklassen. Die einzigen Objekte von Metaklassen sind Klassen, und sie vererben die Klassenmethoden. Diese werden nicht an das Objekt selbst, sondern an die Objektklasse vererbt (Methoden zum Erzeugen von Objekten oder zur Initialisierung von Klassenvariablen). Neben der Konsistenz ist ein weiterer Vorteil des Metaklassenkonzepts, dass die Klassenmethoden in den Klassen überladen werden können. Gibt es für alle Klassen nur eine einzige Metaklasse, dann gibt es für alle Klassen dieselben Klassenmethoden.
Java 2 Kompendium
781
Kapitel 15
Andere Sprachen in Verbindung mit Java C++ verfügt über keine Klassenmethoden und unterstützt das Metaklassenkonzept nicht. An ihre Stelle treten Systemfunktionen, wie etwa besagter new-Operator. Er fällt jedoch unter dem objektorientierten Denkansatz in C++ einfach vom Himmel, während er in Java bedeutend besser in das logische Konzept integriert ist. Operatoren im Allgemeinen Java unterstützt fast alle Standard-C-Operatoren. Diese Operatoren haben in Java die gleichen Prioritäten und Verknüpfungen wie in C. Es gibt jedoch Ausnahmen. So unterstützt Java nicht den Kommaoperator, wie er in C existiert, um zwei Ausdrücke in einem Ausdruck zusammenzufassen. Da Java keine Pointer-Arithmetik ermöglicht, fehlen die zugehörigen Operatoren zur Referenzierung * und Dereferenzierung &, sowie der Operator sizeOf. Weiterhin sind die Zeichen ‘[]’ und ‘.’ keine Operatoren in Java. Dafür gibt es in Java einige Operatoren, die eine spezielle Bedeutung haben oder sogar gänzlich neu sind: Wenn der Operator + in Verbindung mit Zeichenketten verwendet wird, bewirkt er in diesem Fall die Verknüpfung der Zeichenketten zu einer neuen Zeichenkette. Wenn dahingegen nur einer der beiden zu verbindenden Operanden ein String ist, wird der andere Operand automatisch in einen String verwandelt. Die Priorität des + Operators ist identisch mit der Priorität des arithmetischen +. Für die Bearbeitung von Strings gibt es ebenso den Operator +=. Auch dieser ist von der Funktionalität identisch mit der arithmetischen Version. Beim Operator >>> handelt es sich um einen Shift-Operator. Da in Java alle Datentypen außer char als Datentyp mit Vorzeichen definiert sind, bewirkt der konventionelle Operator >> einen Shift nach rechts mit Vorzeichenerweiterung. Der Operator >>> ermöglicht, dass der Wert ohne Vorzeichen nach rechts geschoben wird. Das bedeutet, dass die Bits mit einer 0-Erweiterung nach rechts geschoben werden. Der Operator >>>= ist die dazu passende Übertragung für >>=. Die Operatoren & und | zeigen den in Java gemachten Unterschied zwischen den integralen Datentypen und dem boolean-Datentyp. Bei dem bitweisen UND- und ODER-Operator muss dementsprechend gleichermaßen unterschieden werden. Wenn die beiden Operatoren & oder | auf die integralen Datentypen angewendet werden, bewirken sie die bitweisen UND- und ODER-Operationen. Dies ist analog zu C/C++. Wenn die beiden Operationen allerdings auf den boolean-Datentyp angewendet werden, verhalten sie sich mit einer kleinen Einschränkung wie die logischen UND- bzw. ODER-Operatoren. Die logische Operation in dieser Situation vergleicht in jedem Falle beide Seiten des Ausdrucks, auch wenn das Ergebnis durch die linke Seite bereits festgelegt worden ist. Wenn diese komplette Auswertung des Ausdrucks jedoch
782
Java 2 Kompendium
C/C++ – Unterschiede und Gemeinsamkeiten sowie die Einbindung
Kapitel 15
nicht erwünscht wird, müssen die normalen logischen Operatoren && beziehungsweise || angewendet werden, die nur dann eine rechtsseitige Auswertung durchführen, wenn sie nötig ist. Der Operator instanceof gehört zu der gleichen Prioritätsebene wie die Operatoren <, <=, > und >=. Er ist true, falls der Operand auf der linken Seite ein Objekt derselben Klasse ist, wie die Klasse des Operanden auf der rechten Seite. Ansonsten gibt instanceof den Wert false zurück. Der Operator instanceof funktioniert ebenso mit Schnittstellen. Überladung von Operatoren Im Gegensatz zu C++ können in Java keine Operatoren überladen werden. Länge der Argumentenliste In C/C++ gibt es Mechanismen für wahlweise Argumente oder Argumentlisten mit variabler Länge für Funktionen. In Java müssen alle Methodendefinitionen eine festgelegte Anzahl von Argumenten haben. Befehlszeilenargumente Auch Befehlszeilenargumente (Programmaufruf-Parameter) verhalten sich in Java anders als in C/C++. Das erste Element im Argumentenvektor (argv[0]) ist in C/C++ immer der Name des Programms. In Java ist das erste Argument das erste der zusätzlichen Argumente. Der Argumentvektor ist also in Java um ein Feld verschoben, und das erste Element im Argumentenvektor (argv[0]) kann leer sein. Kommentare Java hat neben den zwei in C/C++ vorhandenen Kommentartypen /* Eingeschlossener Kommentar */ und // noch einen weiteren Kommentartyp, der vom Dokumentationstool javadoc verwendet wird. Er beginnt mit /** und endet mit */. Präprozessor In Java gibt es keinen Präprozessor. Dementsprechend gibt es in Java die C/C++-Anweisung #defines, #include oder #ifdef nicht mehr. Makros Java besitzt keine Makros. Konstanten Java realisiert Konstanten ausschließlich mit dem Schlüsselwort final. Jede Variable, die mit dem Schlüsselwort final deklariert wird, wird damit zu einer Konstanten. Der Wert dieser Variablen muss direkt bei der Initialisie-
Java 2 Kompendium
783
Kapitel 15
Andere Sprachen in Verbindung mit Java rung mit angegeben werden und kann während des Programmverlaufs nicht mehr geändert werden. Das C/C++-Schlüsselwort const gibt es nicht mehr. Exceptions Die Ausnahmebehandlung ist zwar eine aus C/C++ stammende Spracheigenschaft von Java. Jedoch ist sie dort nicht integraler Bestandteil der Basissprache (also C), sondern nur unter C++ aufgesetzt. In Java gehören Exceptions zum grundlegenden Sprachkonzept. Objektorientiertheit Java ist kompromisslos objektorientiert, während man das von C/C++ nicht behaupten kann. Bereits beim new-Operator haben wir gesehen, dass Java durch das Metaklassenkonzept im Kontrast zu C++ das objektorientierte Konzept einhält. Es gibt keine globalen Variablen oder Methoden. Der Verzicht auf globale Variablen und Methoden oder den Typ von Konstanten, die unter C/C++ durch das Schlüsselwort const realisiert werden – in Java ist das Schlüsselwort nicht mehr vorhanden – und die Unterbindung von Funktionen, die nicht mit einer Klasse verbunden sind (gibt es in C/C++), lassen die Aussage zu, dass Java im Gegensatz zu C++ kompromisslos objektorientiert durchkonzipiert ist.
15.2
Die Verbindung von Java und nativem Code
Java besitzt ein Verfahren, das als die Einbindung von nativem Code bezeichnet wird. Das Wort nativ bezieht sich dabei auf Sprachen wie C und C++. Allerdings können auch andere Sprachen in Java eingebunden werden, was jedoch seltener geschieht. Im Prinzip werden alle Java-Methoden, die in einer anderen Sprache als Java implementiert sind, als native Methoden bezeichnet. Wir werden uns auf C/C++ als Beispiel beschränken, denn das Einbinden von nativem Code ist nicht unproblematisch. Es gibt zwar einige Argumente dafür: Bereits existierende Codes können schnell umgearbeitet und eingebunden werden oder sogar über die Methodenschnittstelle als komplettes Programm einer anderen Sprache in Java eingebunden werden, die Ausnutzung anwendungsspezifischer Stärken anderer Sprachen, Performance oder in anderen Sprachen vorhandenes Know-how der Programmierer. Die Nachteile sind jedoch genauso unzweifelhaft und überwiegen. Einige wesentliche Nachteile einer Verwendung von nativem Code sind die folgenden: Native Methoden werden nicht von den Java-Laufzeit-Sicherheitsmechanismen, wie zum Beispiel dem Klassenlader und den Bytecode-Überprüfer, verifiziert. Es entsteht ein erhebliches Sicherheitsrisiko. Native Methoden sind plattformabhängig.
784
Java 2 Kompendium
Die Verbindung von Java und nativem Code
Kapitel 15
Die Bereinigung der sprachspezifischen Mängel wie unter C/C++ durch Java wird unterlaufen. Das Verfahren ist recht kompliziert und setzt auf jeden Fall gute Kenntnisse der anderen Sprache voraus. Nicht zuletzt gibt es zusätzlichen Code. Falls trotz der massiven Bedenken native Code-Elemente mit Java verbunden werden sollen, werden Funktionen, die in anderen Sprachen geschrieben wurden, in dynamisch ladbare Objekte kompiliert (shared libraries auf UNIX-Plattformen und DLLs auf Windows-Plattformen) und in einen Methodenaufruf von Java eingebaut. Zur Laufzeit werden diese Funktionen dann genau wie alle Java-Methoden aufgerufen.
15.2.1
Konventioneller Einbau von C-Code in Java
Wir wollen in diesem Abschnitt die Vorgänge beleuchten, wie C-Code auf einem konventionellen Weg – wir werden das JNI als Alternative gegenüberstellen – über die Schnittstelle der nativen Methode in Java eingebaut wird. Man kann den Einbau eines C-Programms mittels der Schnittstelle der nativen Methode in Java in sieben hintereinander folgende Schritte gliedern. 1.
Schreiben Sie den Java-Code mit dem Aufruf der nativen Methoden.
2.
Kompilieren Sie den Java-Code wie gewohnt mit dem Java-Compiler.
3.
Erstellen Sie die .h-Datei mit dem JDK-Tool javah.
4.
Erstellen Sie mit javah eine Stub-Datei.
5.
Schreiben Sie die C-Funktion als eine .c-Datei.
6.
Erstellen Sie aus der .h-Datei, der Stub-Datei und der .c-Datei, die Sie in den Schritten 3, 4 und 5 erstellt haben, ein dynamisch ladbares Objektmodul (Shared Libraries auf UNIX-Plattformen und DLLs auf Microsoft Windows 95/NT-Plattformen).
7.
Starten Sie das Programm.
Schritt 1: Schreiben des Java-Codes Um eine native C-Funktion zu deklarieren und aufzurufen, stellen Sie das Schlüsselwort native einer nativen Methode voran. Das Schlüsselwort native signalisiert dem Klassenlader, dass die betreffende Methode der Klasse in einer anderen Programmiersprache geschrieben ist. Diese Definition gibt nur die Signatur der nativen Methode an, jedoch nicht ihre Implementierung. Die konkrete Implementierung wird in einer separaten C-Quelldatei vorgenommen.
Java 2 Kompendium
785
Kapitel 15
Andere Sprachen in Verbindung mit Java Beispiel: class TestNativeMethode { public native void NativeMethode(); ..... weitere Methoden ......... }
Der C-Code, der die native Methode implementiert, muss zu einem dynamisch ladbaren Objekt kompiliert und in die Klasse geladen werden, die dieses benötigt. Erst das Laden des Objekts in die Java-Klasse verbindet die Implementierung der nativen Methode mit ihrer Definition. Das folgende Codestück lädt eine entsprechende Bibliothek namens nativeMethodenbibliothek: static { System.loadLibrary("nativeMethodenbibliothek"); }
Dieses Codestück muss nach der Definition der nativen Methode in der Klasse platziert werden. Als Nächstes erstellen Sie in einer separaten Quelldatei namens Main.java eine Java-Klasse Main, die die Klasse TestNativeMethode instanziert und die native Methode NativeMethode() aufruft. Beispiel für das Hauptprogrammklasse für die Klasse TestNativeMethode: class Main { public static void main(String args[]) { new TestNativeMethode().NativeMethode(); } }
Aus dem obigen Codestück ist ersichtlich, dass Sie eine native Methode genau wie eine echte Java-Methode aufrufen. Alle Parameter, die ggf. an die Methode weitergegeben werden sollen, können in Klammern hinter dem Methodennamen eingefügt werden. Schritt 2: Das Kompilieren des Java-Codes Hierzu gibt es nicht viel zu sagen. Benutzen Sie den Compiler wie gewohnt, um den Code, den Sie in Schritt 1 geschrieben haben, zu kompilieren.
786
Java 2 Kompendium
Die Verbindung von Java und nativem Code
Kapitel 15
Schritt 3: Erstellen der .h-Datei Wir werden das JDK-Tool javah in Anhang A noch besprechen. Der javahGenerator erstellt C-Header (Erweiterung .h) und C-Quelldateien für die angegebenen Klassen. Diese so generierten Dateien enthalten alle notwendigen Informationen zur Implementierung von nativen Methoden, beispielsweise #include- und #define-Anweisungen, typedef-Konstrukte u.ä. Normalerweise generiert javah nur ein Headerfile für die angegebenen Klassen. Innerhalb dieses Headerfiles wird eine C-struct deklariert, die alle notwendigen Felder enthält, die mit den Instanzfeldern der ursprünglichen JavaKlassen korrespondieren. Innerhalb dieser Header-Datei wird ebenfalls bereits eine Funktion für jede native Methode definiert, die in der zugehörigen Quelldatei implementiert werden muss. Verwenden Sie also in diesem Schritt das javah-Werkzeug, um aus der Klasse mit der nativen Methode (in unserem Beispiel TestNativeMethode.java) eine C-Header-Datei (.h-Datei) zu erstellen. Die Header-Datei enthält wie gesagt dann eine Struktur, die auf der C-Seite die Java-Klasse und eine C-Funktionsdefinition für die Implementation der in der Java-Klasse definierten nativen Methode einbindet. Für die genauen Befehlsparameter sei auf den Anhang mit der Besprechung der JDK-Tools und die Online-Dokumentation verwiesen. javah platziert normalerweise die resultierende .h-Datei in das gleiche Verzeichnis wie die Klassendatei und erstellt die Header-Datei mit demselben Namen wie der Klassenname mit der Erweiterung .h. In unserem Beispiel wird also eine Datei namens TestNativeMethode.h erzeugt. Der Inhalt dieser Datei sieht dann ungefähr so aus: /* DO NOT EDIT THIS FILE – it is machine generated */ #include /* Header for class TestNativeMethode */ #ifndef _Included_ TestNativeMethode #define _Included_ TestNativeMethode typedef struct ClassTestNativeMethode { char PAD;/* ANSI C requires structures to have a least one member */ } ClassTestNativeMethode; HandleTo(TestNativeMethode); extern void TestNativeMethode _ NativeMethode(struct HTestNativeMethode *); #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif
Die Header-Datei enthält die Definition für eine Struktur mit der Bezeichnung der Klasse. Die Glieder dieser Struktur spiegeln die Glieder der entsprechenden Java-Klasse wider. Die Felder in der Struktur entsprechen den
Java 2 Kompendium
787
Kapitel 15
Andere Sprachen in Verbindung mit Java Instanzvariablen in der Klasse. Da aber in unserem Beispielfall keine Instanzvariablen vorhanden sind, ist das Feld in der Struktur leer. Die Glieder der Struktur ermöglichen der C-Funktion den Zugriff auf die Instanzvariablen der Java-Klasse. Die Header-Datei enthält zusätzlich zur C-Struktur, die die Java-Klasse widerspiegelt, auch noch den Prototyp einer C-Funktion (in unserem Beispiel extern void TestNativeMethode_NativeMethode(struct HTestNativeMethode *);). Dies ist der Prototyp der C-Funktion, die Sie in Schritt 5 implementieren müssen. Wenn Sie die eigentliche C-Funktion schreiben, müssen Sie genau diesen Funktions-Prototyp benutzen. Wenn in unserem Beispiel noch weitere native Methoden in der Klasse enthalten wären, so würden auch deren Funktionsprototypen hier stehen. Der Name der C-Funktion wird aus dem JavaPaketnamen, dem Klassennamen und dem Namen der nativen Methode hergeleitet. Daher wird in unserem Beispiel aus der nativen Methode NativeMethode() in der Klasse TestNativeMethode der Funktionsname TestNativeMethode_ NativeMethode(). Da kein Paketname vorhanden ist, fehlt dieser beim Namen des Prototyps im Beispiel. Die C-Funktion besitzt einen Parameter, den die in der Java-Klasse definierte native Methode nicht hat. Dieser Parameter ist in C++ in etwa mit der Variablen this in Java zu vergleichen. Schritt 4: Erstellen einer Stub-Datei Verwenden Sie nun javah mit den entsprechenden Optionen, um aus der Java-Klasse eine Stub-Datei zu erstellen. Stub-Dateien sind C-Dateien, die neben der Header-Datei zusätzliche, notwendige Rumpffunktionen für die Einbindung von nativen Methoden in der Java-Umgebung enthalten. Das Tool generiert standardmäß ig eine C-Datei in dem aktuellen Verzeichnis, deren Name identisch zu dem im Aufruf spezifizierten Klassennamen ist. Wenn dieser Klassenname ein Paket enthält, enthalten die C-Dateien sämtliche Komponenten des Paketnamens. Allerdings werden diese nicht durch Punkte, sondern durch Unterstriche getrennt. Bei Angabe der entsprechenden Option zur Erzeugung von Stub-Dateien generiert in unserem Beispiel eine C-Datei mit Namen TestNativeMethode.C, die schematisch wie folgt aussieht: /* DO NOT EDIT THIS FILE – it is machine generated */ #include <StubPreamble.h> /* Stubs for class TestNativeMethode */ ... weitere Angaben ...
Auf das genaue Aussehen der Stub-Datei braucht nicht unbedingt detaillierter eingegangen zu werden, denn sie wird ja automatisch generiert und Sie müssen sie nur zu einer dynamischen Bibliothek in Schritt 6 kompilieren. 788
Java 2 Kompendium
Die Verbindung von Java und nativem Code
Kapitel 15
Schritt 5: Schreiben der C-Funktion Für diesen Schritt muss natürlich vorausgesetzt werden, dass Sie sich soweit in C auskennen, um eine entsprechende C-Datei zu erstellen. Es ist nicht Sinn eines Java-Buchs, auf C-Programmierung einzugehen1. An dieser Stelle finden Sie die wichtigsten Informationen zum Schreiben des eigentlichen Code für die native Methode in einer C-Quelldatei: Die Funktion muss den gleichen Namen wie der Funktionsprototyp haben, der in Schritt 3 durch javah in der .h-Datei erstellt wurde. Der C-Code für diese Funktion enthält als wesentliche und immer vorhandene Angaben neben einer Funktion mit dem gleichen Namen wie der Funktions-Prototyp zwei #include-Anweisungen. Zum einen #include <StubPreamble.h>, zum anderen den #include-Befehl für die Header-Datei (also in unserem Beispiel #include "TestNativeMethode.h"). StubPreamble.h stellt dem C-Code Eingriffe für die Interaktion mit dem Laufzeitsystem von Java zur Verfügung, die zweite Datei enthält die C-Struktur, die die Java-Klasse widerspiegelt, für die Sie die native Methode schreiben. Sie enthält auch den Funktionsprototyp für die native Methode, die Sie in diesem Schritt erstellen. Jeder weitere Code der C-Datei ist optional an die Bedingungen der C-Funktion gekoppelt. Schritt 6: Erstellen einer dynamisch ladbaren Bibliothek Dieser Schritt ist betriebssytemspezifisch und nutzt zudem die entsprechenden Befehle Ihrer Plattform, um den C-Code, den Sie in Schritt 5 erstellt haben, in eine DLL (dynamisch gelinkte Bibliothek) oder Objektdatei zu verwandeln. Es sprengt natürlich den Rahmen dieses Buchs, auf die diversen plattformspezifischen Besonderheiten und vor allem die vielfältigen C/C++Entwicklungstools einzugehen. Der Befehl zum Erstellen eines dynamisch ladbaren Objektmoduls sollte aber auf jeden Fall sowohl die C-Datei mit der Implementierung der nativen Methode als auch die in Schritt 4 erstellte Stub-Datei umfassen. Schritt 7: Der Programmstart Hierzu gibt es wieder nicht viel zu sagen. Benutzen Sie den Java-Interpreter wie gewohnt, um Ihr Programm zu starten. Sollte Sie eine ähnliche Liste von Fehlermeldungen wie die folgende erhalten, dann haben Sie ihre Bibliothek nicht in Ihren Pfad mit aufgenommen: java.lang.NullPointerException at java.lang.Runtime.loadLibrary(Runtime.java) 1
Deshalb soll und kann auch das gesamte Verfahren nur sehr theoretisch besprochen werden.
Java 2 Kompendium
789
Kapitel 15
Andere Sprachen in Verbindung mit Java at java.lang.System.loadLibrary(System.java) at ListDirJava.(TestNativeMethode.java:5) at java.lang.UnsatisfiedLinkError ListDirCat Main.main(Main.java:3)
Der Pfad für die Bibliotheken ist eine Liste mit Verzeichnissen, die das Laufzeitsystem von Java beim Laden von dynamisch gelinkten Bibliotheken durchsucht. Das Verzeichnis, in dem sich die Bibliothek befindet, muss in diesem Pfad enthalten sein.
15.2.2
Das Java Native Interface (JNI)
Wenn man von der Einbindung von nativem Quelltext in Java redet, muss man seit der Version 1.1 das Java Native Interface (JNI) erwähnen. Wir werden es der konventionellen Verbindung von Java und C-Code gegenüberstellen und sehen, dass es viele Gemeinsamkeiten gibt. Es handelt sich um die Standard-Programmierschnittstelle zum Schreiben von nativen JavaMethoden und der Verbindung von der JVM und nativen Applikationen bzw. Sourcequellen. Das primäre Ziel dabei ist die binäre Kompatibilität von nativen Methoden-Libraries über alle JVM-Implementationen hinweg. Das JNI-Konzept vereinfacht die Verwendung von nativem Code gegenüber der frühen Einbindung von nativem Code in Java. Es gilt jedoch immer noch, dass diese Einbindung kompliziert ist und wesentliche Kenntnisse der Programmiersprache voraussetzt, deren nativen Code man verwenden möchte. Außerdem gelten auch unter JNI im Wesentlichen die gleichen Argumenten gegen die Verwendung von nativem Code wie bei einer konventionellen Einbindung. Das JNI-Design Wir wollen hier kurz wesentlichen Aspekte des JNI-Designs anreißen. Native Codezugriffe erfolgen im JNI-Konzept über die Verwendung von JNI-Funktionen. Diese sind über einen Schnittstellenpointer verfügbar. Ein solcher Schnittstellenpointer im JNI-Konzept ist ein Pointer auf einen Pointer. Dieser Pointer zeigt auf ein Array von Pointern, die wiederum jeweils auf eine Schnittstellenfunktion zeigen. Das JNI-Interface ist wie eine virtuelle C++-Funktionstabelle oder ein COM-Interface organisiert. Der wesentliche Vorteil dieses Konzepts ist, dass JNI-Namesräume unabhängig von dem nativen Code werden. Native Methoden empfangen die JNI-Schnittstellenpointer als ein Argument. Weitergehende Informationen finden Sie unter /guide/jni/index.html in der JDK-Dokumentation.
790
Java 2 Kompendium
Die Verbindung von Java und nativem Code
15.2.3
Kapitel 15
JNI-Grundlagen
Um mit dem JNI-Konzept eine Verbindung zwischen Java und C-Code (oder auch anderem nativem Code) zu erstellen, sind im Wesentlichen fünf Schritte erforderlich: 1.
Erstellen der Java-JNI-Funktionsdeklaration
2.
Generierung der C-Header-Datei
3.
Erstellen der C-JNI-Methoden
4.
Laden der JNI-Bibliothek in Java
5.
Linken der dynamischen Bibliothek
Erstellen der Java-JNI-Funktionsdeklaration Wenden wir uns dem ersten Schritt zu – dem Erstellen der Java-JNI-Funktionsdeklaration. Wir kennen diesen Schritt im Prinzip schon, denn die Ähnlichkeit zu der bisher besprochenen Technik ist groß. Um native Methoden in Java-Klassen zu benutzen, muss man in der Deklaration der Methode zuerst das Schlüsselwort native einfügen. Beispiel: public native void meineNativeMethode(); Anschließend wird die Methode System.loadLibrary() in den Java-Source eingebunden. Das Schlüsselwort native signalisiert sowohl dem Java-Compiler als auch dem Java-Interpreter, dass sich der Rumpf der Methode in einer DLL befindet. Die Methode System.loadLibrary() dient zum konkreten Aufrufen dieser dynamische Bibliothek. In dem folgenden Beispiel lädt die Initialisierungmethode der Klasse eine plattformspezifische native Bibliothek, in der die native Methode definiert ist: package pkg; class Cls { native double f(int i, String s); static { System.loadLibrary("pkg_Cls"); } }
Das Argument der Methode System.loadLibrary() ist ein vorgegebener Library-Name. Alternativ stehen die Methoden Runtime.getRuntime().loadLibrary() und System.load() zum Laden einer Bibliothek zur Verfügung. Damit können auch Bibliotheken außerhalb des Suchpfades benutzt werden, denn sie werden anhand eines vollständigen Pfadnamens gesucht. Die JNI-Methode
Java 2 Kompendium
791
Kapitel 15
Andere Sprachen in Verbindung mit Java RegisterNatives() kann ebenfalls zur Registrierung einer nativen Methode verwendet werden. Die JNI-Methode FindClass() dient zur Suche von Klassen.
Wenn eine native Bibliothek respektive Methode eingebunden ist, wird anschließend der Java-Code wie üblich kompiliert. Generierung der C-Header-Datei Der nächste Schritt besteht darin, aus der .class-Datei eine entsprechende Header-Datei mit der Erweiterung .h zu erzeugen. Dazu wird wie gehabt das javah-Tool aus dem JDK eingesetzt. Allerdings müssen Sie die Option -jni verwenden. Der Aufruf sieht also so aus: javah -jni Datei Erzeugt wird dadurch die C-Header-Datei Datei.h. In der Header-Datei wird durch die typedef-struct-Anweisung der nativen C-Routine mitgeteilt, wie die Daten in der Java-Klasse angeordnet sind. Die einzelnen Variablen in der Struktur können benutzt werden, um die Klassen und Instanzvariablen von Java zu benutzen. Weiterhin wird in der HeaderDatei ein Prototyp angegeben, um die Methode aus dem objektorientierten Namensraum der Java-Klasse in einen C-Namensraum zu überführen. Der Name einer Funktion, der eine native Methode implementiert, ergibt sich dabei immer aus dem Paket-Namen, dem Klassen-Namen und dem Namen der nativen Methode von Java, getrennt durch einen Unterstrich (wir kennen das bereits). Erstellen der C-JNI-Methoden In diesem Schritt kommen wir zu einer Neuerung, denn nun müssen die nativen JNI-Methoden implementiert werden. Üblicherweise werden die Implementationsdateien mit dem Klassennamen und einer eindeutigen Endung versehen. Die Implementationsdatei muss die Datei jni.h mittels #include-Anweisung einbinden. jni.h ist im include-Verzeichnis des JDK enthalten. Ebenfalls muss die mittels javah -jni erzeugte Header-Datei eingebunden werden. Der Funktionskopf in der Implementationsdatei muss den generierten Prototypen aus der Headerdatei entsprechen. Erstellung der dynamischen Bibliothek Nachdem nun alle benötigten Dateien vorhanden sind, muss die Implementierungsdatei nur noch übersetzt und mit der Java-Bibliothek zu einer dynamischen Bibliothek zusammen gelinkt werden. Dies entspricht unseren Schritten 4 und 5 bei der konventionellen Technik.
792
Java 2 Kompendium
Die Verbindung von Java und nativem Code
15.2.4
Kapitel 15
Datenaustausch zwischen Java und C
Der Datenaustausch zwischen Java und C ist – wie wir bereits wissen – nicht ganz unproblematisch. Jedem elementaren Typ in Java wird ein Typ in der Prototyp-Funktion und damit auch in C zugeordnet. Ein gewisses Problem ist dabei der Typ char, der in Java nach dem Unicode-Standard kodiert ist und in C seinem ASCII-Gegenstück zugeordnet werden muss. Dem JavaTyp String kann sogar nur eine Struktur zugeordnet werden. Wenn Variablen nicht als Parameter einer Methode, sondern als Klassenvariable angelegt werden, ermöglicht dies einen einfachen Zugriff von C aus. Dieser Zugriff wird als »Call by Reference« bezeichnet. Übergibt man die Variablen als Parameter, nennt man diesen Zugriff einen »Call by Value«. Alle Klassenvariablen werden in der .h-Datei zu einem struct zusammengefasst. Auf diesen erhält die aufgerufene C-Funktion als Parameter ein Handle-Pointer. Das in StubPreamble.h definierte unhand()-Makro ermöglicht den Zugriff auf die einzelnen Klassenvariablen. Das unhand()-Makro übernimmt einen Pointer auf das Handle einer Klasse und gibt einen Pointer auf die im .h-File erzeugte Klassenstruktur zurück. Über den Rückgabewert des Makros lassen sich die Instanzvariablen der Java-Klasse direkt auswerten und verändern. Strings Wie wir wissen, sind Strings in Java eine eigene Klasse. Möchte man einen String von Java nach C oder umgekehrt übergeben, muss man javaString.h in der C-Implementationsdatei mit einbinden. In javaString.h finden sich die Typdefintionen und Funktionen, um Strings von Java nach C und umgekehrt zu transformieren. Es gibt noch weitere Methoden, um Strings zu bearbeiten, wie z.B. MoveString_GetString(), CString() oder makeJavaString(). Java-Strings bilden eine eigene Klasse, somit wird bei der Konvertierung nicht nur ein elementarer Typ, sondern eine ganze Klasse an die native Methode durchgereicht. Dieses Konzept lässt sich auch auf andere Klassen erweitern. Felder Felder werden von Java nach C übergeben, indem vom javah-Tool ein Funktionsparameter vom Typ struct HArrayOf * erzeugt wird. Dieser enthält einen Element-Body, mit dem auf die Feldelemente zugegriffen werden kann. Der Zugriff auf das Feld erfolgt mit dem unhand()-Makro und dem body -Element. Allgemeines zu JNI-Typen und Datenstrukturen Es gibt zu jedem Java-Typ ein passendes JNI-Äquivalent, das dem Namen in der Regel nur ein j voranstellt. Um unter JNI-Javatypen in native C-Typen zu mappen, benötigen Sie grundlegende Informationen über die primitiven Java 2 Kompendium
793
Kapitel 15
Andere Sprachen in Verbindung mit Java Typen. Die nachfolgende Tabelle beschreibt die primitiven Java-Typen und ihre maschinenabhängigen nativen Äquivalente:
Tabelle 15.1: Die JNI-Typen
Java Typ
Native Typ
Beschreibung
boolean
jboolean
unsigned 8 bits
byte
jbyte
signed 8 bits
char
jchar
unsigned 16 bits
short
jshort
signed 16 bits
int
jint
signed 32 bits
long
jlong
signed 64 bits
float
jfloat
32 bits
double
jdouble
64 bits
void
void
N/A
Speichermanagement In Java wird die Speicherverwaltung automatisch vom Garbage Collector erledigt. Wird in einer C-Funktion einer nativen Methode Speicher angelegt, muss sich der Programmierer selbst um die Freigabe kümmern. Da der JavaInterpreter von diesem manuellen Speichermanagement normalerweise nichts mitbekommt, müssen eigene native Methoden implementiert werden, um Speicher freigeben zu können. Unter gewissen Umständen können die Speicherbereiche auch aus Java heraus freigegeben werden. Eine einfache Variante der Freigabe ist der explizite Aufruf der Methode dispose().
15.2.5
Java-C++-Verbindung
Die Ähnlichkeit von Java und C++ legt die Vermutung nahe, dass es relativ einfach ist, C++-Code in Java einzubetten. Leider ist das Gegenteil der Fall. Aus Sicht der Schnittstelle betrachtet unterscheiden Java und C++ die folgenden Charakteristika: Die Laufzeit-Schnittstelle für native Methoden in Java ruft das eigentliche dynamische Objekt über eine C-(nicht C++)-Funktions-Aufrufsequenz auf. In C++ ist der Name einer Funktion in der Objektdatei nicht mit dem Namen identisch, der in der C++-Quelldatei verwendet wird. Daher kann eine C++-Methode in einer Objektdatei einen ganz anderen Namen haben. Dies wird als Name Mangling bezeichnet und ist eine Eigenschaft, die alle C++-Compiler besitzen. 794
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Kapitel 15
Aus der Konsequenz dieser beiden Faktoren ergibt sich, dass Sie, wenn Sie eine C++-Methode aus Java aufrufen, nicht denselben Namen benutzen können, den Sie der Methode in C++ zugewiesen haben. Derzeit besteht die einzige Möglichkeit, C++ in Java einzubinden, darin, dass Sie einen C-Wrapper für die C++-Methoden definieren und diese C-Wrapper-Funktionen dann über die native Methodenschnittstelle aus Java aufrufen. Dieser Umweg hat neben Performanceverlusten einige weitere Nachteile. Zum einen können C++-Ausnahmen nicht ohne Weiteres behandelt werden, zum anderen muss der Parameteraustausch zwischen Java und dem eigentlichen C++-Code exakt gehandhabt werden. Ferner müssen Ihnen die wichtigen zusätzlichen Unterschiede zwischen Java und C++ bekannt sein, die am Anfang des Kapitels besprochen wurden, und natürlich von Ihnen beim Programmieren berücksichtigt werden.
15.2.6
Alternativen zum direkten Einbinden von nativen Methoden
Native Methoden sind nicht die einzige Möglichkeit, C- oder C++-Programme mit Java zu verbinden. Es gibt in der Tat Alternativen, um JavaProgramme mit anderen Programmen zu verbinden. Zwei auch in vielen anderen Sprachpaarungen praktizierte Varianten sind die folgenden: Die beiden Programme können so aufgebaut werden, dass sie als Client/Server-System funktionieren. Das bedeutet, ein Programm ruft das andere über einen Shell-Befehl auf. Die Kommunikation läuft über ein Socket oder mittels Übergabeparameter (in eine Richtung). Dazu gibt es in Java im Wesentlichen die Klassen Socket und ServerSocket. Sie sind die Repräsentation eines Clients bzw. Servers bei einer so aufgebauten Client-Server-Beziehung (wir kommen im Abschnitt zu Netzwerkprogrammierung im Kapitel über die erweiterten Java-Techniken darauf zurück). Strukturieren Sie die beiden Programme so, dass Sie Daten über das Lesen und Schreiben von Dateien austauschen können. Es gibt in dem Fall also Schnittstellen auf Datenträgern, die abwechselnd geschrieben und gelesen werden können. Dazu muss dann in einem Programmpart nur noch ein Aufruf des anderen Programmparts und dessen Synchronisation realisiert werden.
15.3
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Die Verbindung von Java zu Scriptsprachen im Rahmen einer Webseite ist gut möglich. Wir wollen hier zu einem der beiden wichtigsten Vertreter von Scriptsprachen kommen. In Zusammenhang mit Java wird immer wieder
Java 2 Kompendium
795
Kapitel 15
Andere Sprachen in Verbindung mit Java der Begriff JavaScript genannt. Die Ähnlichkeit im Namen suggeriert aber eine Verwandtschaft, die so gar nicht vorhanden ist. Insbesondere handelt es sich bei JavaScript nicht um eine von der Syntax an C/C++ angelehnte, objektorientierte Programmiersprache. JavaScript ist viel einfacher als Java und hat seine Existenzberechtigung da, wo Java überdimensioniert ist, vor allem aufseiten des Clients im WWW. Dies gilt für viele Aktionen im Rahmen einer Webseite oder der Steuerung eines Browsers. JavaScript (in der Betaversion noch LiveScript genannt) wurde in Zusammenarbeit von Sun und Netscape als Java-Ableger entwickelt und sollte als Steuerungstechnik für den Netscape-Navigator 2.0 agieren. Im Dezember 1995 haben die Netscape Communications Corporation und Sun Microsystems JavaScript angekündigt und recht bald als eine offene, plattformunabhängige Scriptsprache eingeführt. Obwohl JavaScript viel einfacher als Java ist, lehnt sich die Syntax an Java an. Bei den so genannten Scriptsprachen (JavaScript oder auch Microsofts VBScript) handelt es sich um Interpretersprachen. Sie sind allgemein von relativ einfacher Struktur. Scriptsprachen müssen – server- oder clientseitig – als Interpretersprachen in irgendeinen Interpreter implementiert werden, damit sie zu Leben erwachen. Auf einem Web-Client realisiert man die Verwendung von Scriptsprachen, indem man die Scriptelemente in HTML einbindet. Anschließend werden sie dann von dem Browser (der natürlich diese Scriptsprache verstehen muss) interpretiert. JavaScript zum Beispiel ist so als eine unmittelbare Ergänzung und Erweiterung zu HTML zu sehen und nur als eingebundener Bestandteil eines HTML-Gerüsts zu verwenden. JavaScript-Programme werden im Gegensatz zu Java-Applets direkt in der HTML-Datei geschrieben oder zumindest als Klartext-Dateien dort referenziert. Sie sind damit in gewisser Weise normaler Bestandteil des HTMLDokuments, ähnlich wie Überschriften, Verweise oder andere Referenzen. Sie werden auch nicht wie Java-Applets als vorkompilierte Module in die HTML-Datei integriert, sondern analog HTML als Klartext zur Laufzeit interpretiert. Trotzdem handelt es sich bei JavaScript im Gegensatz zu HTML nicht nur um eine Beschreibungssprache, sondern um eine vollwertige Programmiersprache, wenn auch auf reiner Interpreterbasis. Damit hat JavaScript alle durch das Interpreter-Prinzip bedingten Vorteile, leider aber auch die Nachteile. JavaScript bietet sich daher für kleine und einfache Programmabläufe an, die Aktionen erfordern, die unter HTML nicht oder nur umständlich zu bewerkstelligen sind und Java als zu schweres Geschütz outen.
796
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Kapitel 15
Scriptsprachen können wie gesagt bei einem Client-Server-System sowohl auf Server, als auch dem Client eingesetzt werden. Wir wollen uns hier nur mit dem clientseitigen Einsatz von JavaScript beschränken. Eigentlich ist es so, dass im Browser eingesetzte Scriptsprachen im Wesentlichen den relativ dummen Browser intelligenter machen. Dazu müssen auf der Client-Seite Fähigkeiten hinzugefügt werden, die dem angefragten Dokument oder dem anfragenden Browser eine Veränderung des Dokuments ermöglichen. Diese Fähigkeit bringt die Möglichkeiten von serverseitigen Programmen zum Client, sie macht den Browser also leistungsstärker und intelligenter. Obwohl ein Browser normalerweise außer dem Anfordern und Versenden von Daten höchstens noch als Darstellungssoftware für Multimedia-Effekte dient, ist in modernen Browsern die Nutzung dieser Scriptsprache(n) integriert, um sie mit weitergehenden Fähigkeiten auszustatten. Scripte erlauben einem Webbrowser, auf eine intelligente Weise mit Situationen, die sonst ein Programm auf dem Webserver erforderlich machen würden, umzugehen. Darüber hinaus hat der Anwender den Eindruck, dass die Situation viel schneller abgehandelt wird, da der Browser keine Anfrage an den Server schicken und die Antwort nicht anzeigen muss. Mit Sprachen wie JavaScript wird also eigentlich der Browser programmiert.
15.3.1
Kleiner Exkurs zu JavaScript-Interna
Wir sprechen hier zwar hauptsächlich über Java, aber als ein direkter JavaAbleger macht es Sinn, auch JavaScript ein bisschen eingehender zu beleuchten. Deshalb nun ein kleiner Exkurs zu den JavaScript-Interna. Wir werden als Erstes ohne lange Vorrede mit einem kleinen Beispiel einsteigen. Ein kleines und sehr bekanntes Beispiel in JavaScript ist die Laufschrift in der Statuszeile des Browsers. Der Source dafür ist ziemlich einfach und leicht nachzuvollziehen, wenn man Java und Objekte kennt. Die einzelnen Details werden etwas weiter hinten im Kapitel erläutert. <script language="JavaScript"> 100) { seed--; var cmd="scrollit_r2l(" + seed + ")"; timerTwo=window.setTimeout(cmd,100); }
Java 2 Kompendium
Listing 15.1: Eine JavaScriptLaufschrift in der Statuszeile des Browsers
797
Kapitel 15
Andere Sprachen in Verbindung mit Java else if (seed <= 100 && seed > 0) { for (c=0 ; c < seed ; c++) { out+=" "; } out+=msg; seed--; var cmd="scrollit_r2l(" + seed + ")"; window.status=out; timerTwo=window.setTimeout(cmd,100); } else if (seed <= 0) { if (-seed < msg.length) { out+=msg.substring(-seed,msg.length); seed--; var cmd="scrollit_r2l(" + seed + ")"; window.status=out; timerTwo=window.setTimeout(cmd,100); } else { window.status=" "; timerTwo = window.setTimeout("scrollit_r2l(100)",75); } } } function lade() { timerONE=window.setTimeout('scrollit_r2l(100)',500); } //--> Willkommen auf meiner Homepage
Abbildung 15.1: Laufschrift in der Statuszeile per JavaScript
Die Einbindung von JavaScript (oder auch anderen Scripten) in HTML-Seiten erfolgt in der Regel über das <SCRIPT>-Tag.
798
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Kapitel 15
<SCRIPT language="JavaScript">
Das <SCRIPT>-Tag kann an einer beliebigen Stelle innerhalb der Webseite platziert werden. Wenn das Script im -Bereich platziert wird, wird es bereits interpretiert, bevor der Textkörper des Dokuments vollständig geladen wurde. Deshalb sollten Sie Funktionen und Initialisierungscode in den -Abschnitt oder zumindest vor den Body schreiben. Direkt nach dem einleitenden <SCRIPT>-Tag sollte ein HTML-Kommentar wieder geschlossen wird. Dadurch steht der gesamte JavaScript-Code innerhalb eines HTML-Kommentars. Es können ebenso mehrere in Kommentar-Tags eingeschlossene Scriptelemente hintereinander notiert werden, auch mit verschiedenen Scriptsprachen. JavaScripts können auch in einer Extra-Datei abgelegt werden. Sie erreichen dies mit dem Attribut src, das ein Tag erweitert, welches externe Dateien nutzen möchte. Beispiel: <SCRIPT language="JavaScript" src="meineFunktion.js" type="text/JavaScript">
In Anführungszeichen wird hinter dem Attribut src der Name der separaten Datei angegeben. Dabei gelten beim Referenzieren von separaten JavaScript-Dateien die üblichen Regeln für URLs. Der Container selbst bleibt leer. Die Datei mit dem Quellcode muss wie HTML-Dateien auch eine reine ASCII-Datei sein und ausschließlich JavaScript-Code enthalten. Üblich ist die Dateierweiterung .js, aber das ist nicht zwingend. Der Aufruf von JavaScripten erfolgt im Wesentlichen über so genannte Event-Handler. Diese sind Bestandteil von HTML und werden als Attribute in HTML-Tags notiert, die auf entsprechend spezifizierte Ereignisse reagieren sollen. Es gibt im Wesentlichen drei Situationen, wann beispielsweise eine JavaScript-Funktion unter HTML aufgerufen wird: 1.
Beim Start der HTML-Datei
2.
Bei einem Klick auf eine Referenz
3.
Beim Überstreichen mit dem Mauszeiger
Java 2 Kompendium
799
Kapitel 15
Andere Sprachen in Verbindung mit Java Der Aufruf beim Start der HTML-Datei erfolgt mit einer Erweiterung des -Tags, dem Attribut onload.
Der Funktionsname der JavaScript-Funktion wird in Anführungszeichen gesetzt. Der Aufruf bei einem Klick auf eine Referenz erfolgt mit einer Erweiterung des Referenz-Tags, dem Attribut onclick. ...
Der Aufruf bei Überstreichen mit dem Mauszeiger erfolgt mit einer anderen Erweiterung des Referenz-Tags, dem Attribut onMouseOver. Dabei wird beim Überstreichen eines Verweis-sensitiven Bereichs mit dem Mauszeiger die JavaScript-Funktion direkt aufgerufen. ...
Neben den hier genannten Methoden gibt es noch diverse weitere Event Handler (onAbort, onBlur, onError, onSubmit usw.), die wir allerdings hier nicht alle durchsprechen wollen. Insbesondere muss beachtet werden, dass die Unterstützung von Event-Handlern in den verschiedenen Browsern sehr unterschiedlich realisiert ist. Für mehr Informationen gibt es viele weiterführende Quellen zu JavaScript. Alle Anweisungen, die zu HTML zählen, können sowohl klein als auch groß geschrieben werden. JavaScript selbst unterscheidet jedoch wie Java Groß- und Kleinschreibung. Variablen, Arrays und Datentypen in JavaScript JavaScript besitzt genauso wie andere Programmiersprachen Variablen. Aber bei konventionellen Programmiersprachen (wie C und C++ und Java) muss der Programmierer den Datentyp festlegen, der mit der Variable abgespeichert wird. Das wird statische Typisierung von Variablen genannt. JavaScript verfolgt bei der Zuweisung von Werten zu Variablen das genaue Gegenteil – das Konzept der so genannten losen Typisierung (loose typing). Dadurch werden Variablentypen niemals deklariert, sondern impliziert. Dies bedeutet, dass zwar die Namen von Variablen festgelegt, dabei jedoch niemals bereits die Variablentypen deklariert werden. Dadurch werden Variablen erst dann zu einem bestimmten Datentyp, wenn ihnen Werte eines bestimmten Typs zugewiesen wurden. Ein wesentlicher Vorteil ist, dass Sie den Datentyp jederzeit ohne großen Aufwand ändern können. Explizites Casting ist nicht notwendig. Wenn Sie den Wert einer Zeichenkette einer Variable zuweisen, können Sie sie später in einen booleschen 800
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Kapitel 15
Variablentyp ändern. Welcher Wert auch immer in der Variable enthalten ist, er definiert den Variablentyp. Die lose Typisierung von JavaScript hat aber nicht nur Vorteile, was Sie ja mit Ihrer Java-Erfahrung durchaus einschätzen können. Sonst würden die viel mächtigeren Programmiersprachen nicht den Aufwand mit der statischen Typisierung betreiben. Sie können beispielsweise leicht vergessen, welcher Typ einer Variablen zugeordnet ist. Sie sollten Variablen möglichst nie im Typ verändern – sofern es sich vermeiden lässt – und tunlichst sprechende Namen verwenden. Ein großes Problem in JavaScript ist es, Variablen anzulegen. Nicht etwa, weil es schwer ist. Im Gegenteil – es ist viel zu leicht. Im Allgemeinen werden Variablen in JavaScript über das Schlüsselwort var angelegt. Beispiel: var Weizenkeim; Die im Beispiel angelegte Variable können Sie dann im Laufe Ihres Scripts verwenden. Sie hat noch keinen Typ und auch noch keinen Wert. Ihr kann jedoch später – irgendwann im Ablauf Ihres Scriptes – ein Wert zugewiesen werden und damit erhält sie gleichzeitig ihren Variablentyp. Wenn Sie sie jedoch verwenden wollen, bevor sie einen Wert bekommen hat, wird der JavaScript-Interpreter einen Fehler melden. Sicherer ist es auf jeden Fall, wenn Sie beim Anlegen der Variablen ihr gleich einen Wert – einen Defaultwert zur Vorbelegung – zuweisen. Sie können in JavaScript Werte von Variablen verändern, indem Sie einer Variablen einfach den neuen Wert über ein Gleichheitszeichen (einen der Zuweisungsoperatoren zuweisen. Dies funktioniert wie im nachfolgenden Beispiel: var Weizenkeim= 42;
Die Variable Weizenkeim ist damit als Zahl festgelegt und hat den Wert 42. Wo ist dann aber bisher das Problem? Nun, Variablen können in JavaScript »on the flight« entstehen. Das bedeutet, ganz ohne Verwendung von var. Wenn Sie unbeabsichtigt bei einer Zuweisung einen neuen Variablennamen einführen (z.B. weil Sie sich verschrieben haben), wird diese Variable automatisch erzeugt und die Variable, der Sie eigentlich einen Wert zuweisen wollten, wird weiter mit dem alten Wert geführt. Obwohl Datentypen bei JavaScript nicht explizit festgelegt werden, gibt es implizit natürlich einige Grundtypen von Variablen. Der Typ Boolean enthält nur einen der beiden Werte wahr oder falsch (true oder false). Datentyp Number kann sowohl einen Ganzzahl als auch einen Gleitkommawert (Kommaanteil wird mit Punkt getrennt) enthalten. Neben der normalen Zahlenschreibweise gibt es die wissenschaftliche Notationsmöglichkeit über die Angaben e oder E. Der Typ String kann eine Reihe von alphanumeri-
Java 2 Kompendium
801
Kapitel 15
Andere Sprachen in Verbindung mit Java schen Zeichen (sprich normalen Text, der mit Zahlen gemischt sein kann) enthalten. Zu guter Letzt gibt es noch Object. Dieser allgemeine Datentyp kann einen Wert eines beliebigen Typs enthalten. Er wird normalerweise für das Speichern von Klassen-Instanzen verwendet. Beispiel: heute_ist = new Date(); Zusätzlich unterstützt JavaScript Datenfelder (Arrays) jeglicher Datentypen. Ein JavaScript-Array wird anders erzeugt als eine normale Variable. Sie müssen neben dem Namen noch die Anzahl von Elementen angeben, die darin enthalten sein können. Außerdem wird nicht mehr das Schlüsselwort var zur Erzeugung verwendet, sondern das Schlüsselwort new. Das ist dem Vorgang in Java recht ähnlich, aber es fehlt die Typfestlegung. Beispiel: mein_Array = new Array(29); Zu beachten ist aber auch, dass JavaScript in keiner Weise die vorgegebenen Grenzen des Arrays beachtet. Sie können also einen Array dynamisch erweitern, indem Sie einfach eine nicht vorhandene Dimension bei einer Zuweisung angeben. Operatoren unter JavaScript JavaScript kennt die meisten der unter Java vorkommenden Operatoren. Wenn sie vorhanden sind, haben sie auch die gleiche Bedeutung wie in Java. Allerdings verzichtet JavaScript natürlich auf die Operatoren, die auf Grund des einfacheren Konzepts keinen Sinn machen. Etwa diejenigen, die mit der OO-Theorie direkt in Verbindung stehen. JavaScript kennt folgende Operatoren (der Priorität nach geordnet):
Tabelle 15.2: JavaScriptOperatoren
802
Priorität
Operatoren
1
() []
2
! ~ -a ++ -- typeof
3
* / %
4
+ -b
5
<< >> >>>
6
< <= > >=
7
== !=
8
&
9
^
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Priorität
Operatoren
10
|
11
&&
12
||
13
?:
14
= += -= <<= >>= &= ^= |=
15
,
Kapitel 15 Tabelle 15.2: JavaScriptOperatoren (Forts.)
a. Im Sinn von Negation b. Im Sinn von Subtraktion
Die reservierten Worte von JavaScript JavaScript kennt die folgenden Schlüsselworte, die selbstverständlich nicht für Variablen- oder Funktionsbezeicher verwendet werden dürfen. Diverse der Schlüsselworte sind nur reserviert und werden noch nicht verwendet: Schlüsselwort
Beschreibung
abstract
reserviert
boolean
reserviert
break
Abbruch in Schleifen
byte
reserviert
case
Fallunterscheidungen
catch
reserviert
char
reserviert
class
reserviert
const
reserviert
continue
Fortsetzung in Schleifen
debugger
reserviert
default
Fallunterscheidungen
delete
Löschen eines Array-Elements oder einer selbst definierten Objekteigenschaft
do
Beginn einer Erweiterung der while-Schleife (do-while)
Java 2 Kompendium
Tabelle 15.3: JavaScriptSchlüsselworte
803
Kapitel 15 Tabelle 15.3: JavaScriptSchlüsselworte (Forts.)
804
Andere Sprachen in Verbindung mit Java
Schlüsselwort
Beschreibung
double
reserviert
else
Einleitung des alternativen Blocks in einer if-Schleife
enum
reserviert
export
Objekte oder Funktionen für fremde Scripts ausführbar machen
extends
reserviert
false
Der Wert falsch
final
reserviert
finally
reserviert
float
reserviert
for
Einleitung von for-Schleifen
function
Einleitung von Funktionen
goto
reserviert
if
Einleitung von if-Schleifen
implements
reserviert
import
Objekte oder Funktionen eines fremden Scripts importieren
in
Bedingte Anweisungen in if-Schleifen
instanceof
reserviert
int
reserviert
interface
reserviert
long
reserviert
native
reserviert
new
Definition von Objekten
null
reserviert
package
reserviert
private
reserviert
protected
reserviert
public
reserviert
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Schlüsselwort
Beschreibung
return
Übergabe eines Rückgabewertes in Funktionen
short
reserviert
static
reserviert
super
reserviert
switch
Fallunterscheidung
synchronized
reserviert
this
Bezug auf die aktuelle Instanz eines Objekts
throw
reserviert
throws
reserviert
transient
reserviert
true
Der Wert wahr
try
reserviert
typeof
Typ eines Elements
var
Definition einer Variablen
void
Leerer Funktionstyp
volatile
reserviert
while
Einleitung einer while-Schleife
with
Erlaubt, mehrere Anweisungen mit einem Objekt durchzuführen
Kapitel 15 Tabelle 15.3: JavaScriptSchlüsselworte (Forts.)
JavaScript-Anweisungen Anweisungen sind das Herz von JavaScript. Eine Anweisung ist eine Quellcodezeile, die bestimmte Befehle enthält. Beispiel: window.setTimeout('fenster_neu()',3000);
JavaScript-Anweisungen können auch über mehrere Zeilen im Editor gehen, denn sie enden immer erst mit einem Semikolon. Wenn mehrere Anweisungen zu einer Gruppe zusammengefasst werden, faßt man sie wie in Java zu Blöcken zusammen. Diese werden in geschweifte
Java 2 Kompendium
805
Kapitel 15
Andere Sprachen in Verbindung mit Java Klammern eingeschlossen ({ }), die Anfang und Ende eines Blocks markieren. Der JavaScript-Interpreter des Browsers wird einen solchen Block als Einheit behandeln und ihn zusammen abarbeiten. JavaScript-Eigenschaften In JavaScript werden Eigenschaften normalerweise als Variablen vorkommen. Die Eigenschaften können in JavaScript beliebige Variablenarten zugewiesen bekommen. Um auf eine Eigenschaft zuzugreifen, geben Sie den Namen des zugehörigen Objekts an, dann folgt ein Punkt und zum Schluss die Eigenschaftsvariable (der Name) – bekannte Punktnotation. Da JavaScript allerdings nur objektbasierend und nicht streng objektorientiert ist, kann man auch globale Variablen verwenden. Kontrollstrukturen in JavaScript JavaScript kennt die auch in Java verwendeten Kontrollstrukturen. Hier folgen einige der wichtigsten Kontrollstrukturen. Die if-Bedingung: if (Bedingung) { Anweisungen } else { alternative Anweisungen }
Die while-Bedingung: while (Bedingung) { Anweisungen }
Die for-Schleife: for (Zählvariable;Bedingung;Veränderung der Zählvariable) { Anweisungen }
Funktionen JavaScript stellt die Möglichkeit bereit, mit Funktionen zu arbeiten. Ein signifikanter Unterschied zu Java, aber was Funktionen sind, muss wohl nicht erklärt werden. Sie können in JavaScript auch eigene Funktionen definieren, um umfangreichere und häufiger benötigte Strukturen zusammenzufassen. Der Interpreter wird beim Laden einer Webseite so markierten Code nicht
806
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Kapitel 15
abarbeiten, sondern erst beim expliziten Aufruf über den Bezeichner samt eventuell notwendiger Parameter. Zur Deklaration einer Funktion dient die folgende Syntax: function [Name]([Parameterliste]) { Anweisungen }
15.3.2
Das Objektmodell von JavaScript
JavaScript ist zwar nicht objektorientiert, kann aber – wie bereits angesprochen – Objekte nutzen und (mit erheblichen Einschränkungen) sogar selbst erzeugen. Genau genommen basiert die Leistungsfähigkeit von JavaScript darauf, Objekte nutzen zu können. Eines der internen Objekte, die JavaScript zur Verfügung stehen, wird navigator genannt. Dieses Objekt enthält Informationen über den Browser, den der Anwender verwendet. Eine unter JavaScript nutzbare Eigenschaft von navigator ist appName. Diese Zeichenkette enthält den Namen des Browsers. Um den Wert der Eigenschaft, also den Namen, zu bekommen, müssen Sie einen Punkt zwischen Objekt und Eigenschaft setzen: navigator.appName
Das Objektmodell selbst ist kein Bestandteil von JavaScript, sondern übergeordnet. Es kann also von verschiedenen Techniken verwendet werden. Jede Eigenschaft eines Objekts kann so abgefragt werden und eine Eigenschaft kann auch ein anderes Objekt sein. In JavaScript werden die meisten Objekte automatisch entstehen. Dann muss sich ein JavaScript-Programmierer eigentlich um nichts mehr kümmern. Die manuelle Erstellung durch den Programmierer geschieht wie in Java mithilfe des reservierten JavaScript-Schlüsselworts new. Die internen Objekte von JavaScript Unter JavaScript können Sie u.a. folgende vordefinierten Objekte nutzen: Objekt
Beschreibung
all
Das Objekt ermöglicht den direkten Zugriff auf alle Elemente einer HTML-Datei. Es gehört aber nicht zum offiziellen JavaScript-Standard, sondern ist eine Implementation für den Internet Explorer ab der Version 4.0.
anchor
Das Objekt beinhaltet alle Verweisanker in einer HTML-Datei.
Java 2 Kompendium
Tabelle 15.4: Objekte unter JavaScript
807
Kapitel 15 Tabelle 15.4: Objekte unter JavaScript (Forts.)
808
Andere Sprachen in Verbindung mit Java
Objekt
Beschreibung
applet
Das Objekt beinhaltet alle Java-Applets in einer HTML-Datei.
Array
Über dieses Objekt werden Arrays erzeugt. Dessen Elemente können über einem gemeinsamen Bezeichner und einen Index angesprochen werden.
Boolean
Ein Objekt mit Wahrheitswerten.
Date
Das Objekt enthält Informationen zu Datum und Uhrzeit.
document
Dieses Objekt repräsentiert die Webseite selbst.
event
Ein Objekt, das bei Anwenderereignissen erzeugt wird und für die (zentrale) Ereignisbehandlung genutzt werden kann.
form
Ein Objekt, das die Formulare einer HTML-Seite repräsentiert.
frame
Ein Objekt, das die Framesets und Frames einer HTML-Seite repräsentiert.
Function
Ein Objekt mit JavaScript-Funktionen.
history
Dieses Objekt enthält Informationen über die URLs, die ein Anwender besucht hat.
image
Ein Objekt, über das auf die Grafiken in einer HTML-Datei zugegriffen werden kann.
layer
Die Layer in einer HTML-Datei (Netscape-spezifisch und sogar im neuen Navigator 6 nicht mehr unterstützt).
link
Das Objekt, das die Verweise in der aktuellen HTML-Datei repräsentiert.
location
In diesem Objekt werden Informationen über URL-Adressen geführt.
Math
Ein Objekt mit zahlreichen mathematischen Konstanten und Methoden.
mimeType
Ein Objekt mit Mim-Typ-Informationen.
navigator
Die Objektrepräsentation mit Informationen über den verwendeten WWW-Browser.
Number
Ein Objekt mit numerischen Werten.
plugin
Ein Objekt, das die vorhandenen Plug-Ins in einem Browser repräsentiert.
RegExp
Ein Objekt mit regulären Ausdrücken.
screen
Ein Objekt mit Informationen über den verwendeten Bildschirm.
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Objekt
Beschreibung
String
Ein Objekt für die Manipulation von Zeichen und Zeichenketten.
Style
Die Objektrepräsentation der Stil-Attribute eines Elements.
window
Dieses Objekt enthält Statusinformationen über das gesamte Browser-Fenster. Jedes Fenster hat sein eigenes window-Objekt. Das window-Objekt ist das höchste Objekt in der Objekthierarchie der JavaScript, die den Browser direkt betreffen.
Kapitel 15 Tabelle 15.4: Objekte unter JavaScript (Forts.)
Es gibt neben den hier aufgeführten Objekten weitere Objekte, die sich in der Notation ein wenig von den anderen Objekten unterscheiden, aber sonst ganz »normale« Objekte sind. Dies sind so genannte Objektfelder. Charakteristisch dafür ist, dass diese über einen Feldnamen sowie eine Indexnummer identifiziert werden. Ansonsten ist die Anwendung von Eigenschaften und Methoden vollkommen identisch. Beispiele für solche Objektfelder sind forms[] oder elements[]. Es handelt sich dabei um Arrays, die Objekte enthalten. Wie stehen nun Objektfelder mit den obigen Objekten in Beziehung? Einige Objektfelder entstehen automatisch, wenn eine Webseite geladen wird und Objekte eines bestimmten Typs darin enthalten sind. Wenn beispielsweise eine Webseite ein Formular enthält, bedeutet dies, ein Objekt des Typs form ist darin enthalten. Wenn nun mehr als ein Formular in einer Webseite vorhanden ist, muss der Browser diese Formulare irgendwie identifizieren und speichern. Jedes Formular wird in einem Feld eines Objektfelds gespeichert, das automatisch generiert wird und das vom Bezeichner meist dem erzeugenden Objekt sehr ähnlich ist (im Fall von Formularen ist das beispielsweise forms – beachten Sie das s). Die Indexnummern entstehen automatisch, wenn der Browser das Objekt bei Abarbeitung der HTMLSeite erzeugt und in einen Schlitz des Arrays einordnet. Das erste im Dokument auftretende Objekt jedes vorkommenden Typs erhält den Index 0, das zweite den Index 1 usw. Bei Formularen wird das erste Objekt vom Typ form im Array-Eintrag forms[0] gespeichert, das zweite in forms[1] usw. Die nachfolgende Tabelle gibt die wichtigsten Objektfelder an, deren potenziellen Inhalt sowie eine kleine Beschreibung.
Java 2 Kompendium
809
Kapitel 15 Tabelle 15.5: Objektfelder unter JavaScript
Andere Sprachen in Verbindung mit Java
Objektfeld
Typ der enthaltenen Objekte
Beschreibung
anchors
anchor
Die im Objektfeld enthaltenen Objekte repräsentieren eine Liste aller Hypertext-Anker in einer Webseite.
applets
applet
Die enthaltenen Objekte repräsentieren eine Liste aller Applets in einer Webseite.
elements
[Eingabelemente eines HTML-Formulars]
Die enthaltenen Objekte repräsentieren eine Liste aller Eingabeelemente, die sich in einem als übergeordnetes Objekt angegebenen Formular befinden. Diese werden in JavaScript durch die folgenden Objekte repräsentiert: Button, Checkbox, FileUpload, Hidden, Password, Radio, Reset, Select, Submit, Text und Textarea.
810
forms
form
Die enthaltenen Objekte repräsentieren eine Liste aller Formulare in einer Webseite.
frames
frame
Die enthaltenen Objekte repräsentieren eine Liste aller Frames in einer Webseite.
images
image
Die enthaltenen Objekte repräsentieren eine Liste aller Bilder in einer Webseite.
links
link
Die enthaltenen Objekte repräsentieren eine Liste aller Hyperlinks in einer Webseite.
mimeTypes
mimeType
Die enthaltenen Objekte repräsentieren eine Liste aller Mimetypen in einer Webseite.
options
[Liste der Optionen eines Eingabefeldes vom Typ select]
Die enthaltenen Objekte repräsentieren eine Liste aller erlaubten Optionen, die bei dem als übergeordnetes Objekt angegebenen Objekt vom Typ select vorkommen.
plugins
plugin
Die enthaltenen Objekte repräsentieren eine Liste aller in dem Browser installierten Plug-In-Module.
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Kapitel 15
Die Objekthierarchie in JavaScript Viele der JavaScript-Objekte stehen in einer Objekthierarchie zueinander. Dies bedeutet, ein Objekt ist die Ableitung eines anderen Objekts. Es erbt damit dessen Eigenschaften und Methoden und erweitert diese sinnvollerweise um irgendwelche zusätzlichen Funktionalitäten. Dieses Unterobjekt besitzt umgekehrt ein übergeordnetes Objekt. Wenn Sie ein in der Objekthierarchie tiefer angesiedeltes Objekt ansprechen wollen, müssen Sie einfach dessen Elternobjekt über die Punktnotation mit angeben. Beispiel: window.document Das Objekt window ist das Elternobjekt von document. Allerdings sind nicht sämtliche Objekte in einer einzigen Hierarchiebeziehung miteinander verbunden. Neben den hierarchisch geordneten JavaScript-Objekten gibt es auch solche, die nicht direkt in diese Hierarchieebenen einzuordnen sind.
15.3.3
Verbindung von Java und JavaScript
JavaScript und Java lassen sich im Rahmen einer Webseite gut miteinander verbinden. Es ist möglich, von Java auf JavaScript zugreifen und umgekehrt. Allerdings ist das Verfahren nicht in allen Browsern gleich gehalten. Um einen externen Zugriff auf Java-Objekte zu erlauben, beinhaltet das Objektmodell von Netscape die Objekte Packages (ein ohne besonderen Konstruktor überall bereitstehendes Toplevel-Objekt für den Zugriff auf jede Javaklasse – etwa direkt aus dem JavaScript-Code heraus), java (ein ohne besonderen Konstruktor überall bereitstehendes Toplevel-Objekt für den Zugriff auf jede Javaklasse in dem Package java.*), JavaArray (eine Instanz eines Java-Arrays, die an JavaScript weitergereicht werden kann), JavaObject (eine Instanz eines Java-Objekts, die an JavaScript weitergereicht werden kann), und JavaPackage (eine JavaScript-Referenz auf ein Java-Package). Damit können Objekte von Java erreicht werden, die zum Core-Java-Standard gehören. Die Objekte netscape und sun sind weitere Toplevel-Objekte für den Zugriff auf die speziellen Java-Klassen in dem Package netscape.* bzw sun.*, die über die virtuelle Maschine des Navigators genutzt werden können. Das gesamte Konzept ist unter dem Namen LiveConnect bekannt und setzt – da eine Verbindung von zwei Technikwelten – natürlich entsprechende Kenntnisse in beiden Welten voraus. Es beinhaltet sowohl den Zugriff aus Java heraus auf JavaScript als auch den umgekehrten Weg. Der Internet Explorer behandelt nun Java-Applets wie ActiveX-Steuerelemente. Er unterstützt deshalb meist keine direkten Zugriffe wie der Navigator in LiveConnect. Dafür aber gibt es die Möglichkeit, über die Ansteuerung von HTML-Elementen aus JavaScript heraus ein Java-Applet zu
Java 2 Kompendium
811
Kapitel 15
Andere Sprachen in Verbindung mit Java nutzen. Das Verfahren wiederum gibt unter Umständen Probleme im Navigator, der die Zugriffe auf HTML-Elemente viel eingeschränkter bereitstellt. Bei vielen einfacheren Zugriffen wird es aber in beiden Welten funktionieren. Betrachten wir die Verbindung von Java und JavaScript getrennt nach den Konzepten. LiveConnect – die Netscape-Variante Schauen wir uns zuerst die Richtung von JavaScript auf Java im NetscapeModell an. Über das Packages-Objekt bzw. die anderen gerade genannten Toplevel-Objekte hat man Zugriff auf alle unter Java als public deklarierten Methoden und Felder direkt aus JavaScript heraus mittels der StandardPunktnotation von Java (sofern die JVM zur Verfügung steht, was in neueren Browsern aber so gut wie immer der Fall ist). Die Objekte java, netscape und sun sind einerseits Eigenschaften von Packages, andererseits wieder selbst Objekte, die die Pakete java.*, netscape.*, und sun.* repräsentieren. Aber die Technik geht viel weiter. Der Zugriff aus JavaScript heraus auf den Konstruktor einer beliebigen Java-Klasse erfolgt mit folgender Syntax: var [JavaScriptObjektvariable] = new Packages.[Klasse];
Das sieht etwa für die Java-Klasse Frame aus JavaScript heraus folgendermaßen aus: var meinFrame = new Packages.java.awt.Frame();
Es geht sogar noch einfacher, denn die jeweiligen JavaScript-ToplevelObjekte netscape, sun, und java stehen als Synonyme für die Pakete gleichen Namens. Daher ist ein Zugriff auch ohne das Packages-Schlüsselwort möglich. Das Beispiel von oben sieht dann so aus: var meinFrame = new java.awt.Frame();
Das Package-Objekt stellt überdies mit der Eigenschaft className den Pfadnamen zu jeder aus JavaScript erreichbaren Java-Klasse zur Verfügung. Damit und mit dem Package-Objekt kann dann sogar auf Java-Klassen außerhalb der direkt auf Toplevel-Ebene zur Verfügung stehenden Pakete zugegriffen werden. Beachtet werden sollte aber, dass die Technik wie gesagt explizit auf das Netscape-Objektmodell abgestimmt ist. Deshalb werden die Beispiele auch nur im Netscape Navigator funktionieren. Außerdem muss in dem Browser (ab Version 3.0) sowohl die JavaScript-Unterstützung als auch die JavaUnterstützung aktiviert sein.
812
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Kapitel 15
Der explizite Datenaustausch von JavaScript nach Java und umgekehrt ist nicht ganz trivial (zwischen Java und anderen Sprachen wie VBScript erst recht nicht). Beide Sprachen verfügen nicht über die vollkommen gleichen Datentypen und Objekte. So gilt beispielsweise, dass auf dem Weg von JavaScript nach Java unter LiveConnect die JavaScript-Datentypen String, Number und Boolean in entsprechend Java-Objekte vom Typ String, Float und Boolean umgewandelt werden. Die meisten anderen JavaScript-Objekte werden in Java in ein Objekt vom Typ JSObject gewandelt, das über das Paket netscape.JavaScript unter Java bereitgestellt wird. Auch der umgekehrte Weg der Übergabe von Daten aus Java nach JavaScript liegt fest. Ein boolean-Wert unter Java wird zu Boolean in JavaScript oder die Java-Datentypen byte, char, short, int, long, float und double werden allesamt zu Number. Die allgemeinen Regeln für die Umwandlung liegen also fest, aber durch die unterschiedlichen Längen der Datentypen und deren Struktur ist eine Konvertierung nicht immer ohne Datenverlust möglich. Ein offensichtliches Beispiel ist der Datentyp long unter Java (64 Bit groß), der zu Number konvertiert wird (unter JavaScript nur 32 Bit groß). Gehen wir aber nun in die Praxis. Das erste einfache Beispiel demonstriert die Ausgabe auf Systemebene über die Java-Standard-Ausgabemethode println(), die innerhalb eines JavaScripts aufgerufen wird: <SCRIPT LANGUAGE="JavaScript"> function ausgabe() { java.lang.System.out.println( "Nutzen von Java auf Systemebene"); }
Listing 15.2: Zugriff auf java.* aus JavaScript
Um die Wirkung zu sehen, müssen Sie die Java-Konsole des Browsers anzeigen. Dort erfolgt die Ausgabe. Im Communicator (unter dem Menüpunkt COMMUNICATOR und EXTRAS) kann sie geöffnet werden. Das nächste etwas komplexere Beispiel verwendet eine JavaScript-Funktion, um eine Java-Dialogbox zu erstellen (beachten Sie, dass der Java-Code keinen Schließbefehl für die Dialogbox enthält – Browser Schließen beendet aber auch das Fenster):
Java 2 Kompendium
813
Kapitel 15
Andere Sprachen in Verbindung mit Java
Abbildung 15.2: Aktivierung der Java-Konsole im Navigator
Abbildung 15.3: Die println()Ausgabe per JavaScript-Funktion in der Java-Konsole
Listing 15.3: Zugriff auf java.awt.Frame aus JavaScript
<SCRIPT LANGUAGE="JavaScript"> function fenster() { var derFrame = new java.awt.Frame(); var meinFenster = new java.awt.Dialog(derFrame); meinFenster.setSize(350,200); meinFenster.setTitle("Hello World"); meinFenster.setVisible(true); }
Das Java-Fenster wird automatisch geöffnet, wenn die Datei in den Navigator geladen wird. 814
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Kapitel 15 Abbildung 15.4: Ein per JavaScriptFunktion aufgerufenes Java-Fenster
Das dritte Beispiel greift über Java auf die Systemeinstellungen zu und generiert daraus eine dynamische Webseite: <SCRIPT LANGUAGE="JavaScript"> function spionage() { var dasSystem = java.lang.System; var dasOS = dasSystem.getProperty("os.name"); var dieOSversion = dasSystem.getProperty("os.version"); var dieJavaVersion = dasSystem.getProperty("java.version"); document.write("Sie verwenden folgendes Betriebssystem: "); document.write(dasOS); document.write(" "); document.write("Die Version ist: "); document.write(dieOSversion); document.write(" "); document.write("Ihre Java-Version ist: "); document.write(dieJavaVersion); document.write(" "); }
Listing 15.4: Zugriff auf Systeminformationen aus JavaScript heraus via Java
Abbildung 15.5: Systeminfos
Java 2 Kompendium
815
Kapitel 15
Andere Sprachen in Verbindung mit Java Zugriff auf JavaScript aus Java heraus Wenn man nun unter Java im Rahmen des LiveConnect-Konzepts auf JavaScript zugreifen will, muss in den Klassen das Paket netscape.javascript importiert werden. Das heißt, Sie benötigen über die Core-Java-Klassen hinaus von Netscape bereitgestellte Klassen. Dieses Paket netscape.javascript.* enthält neben der Klasse JSException zur Behandlung von JavaScript-Ausnahmen die Klasse JSObject, mit der der Zugriff auf JavaScript, aber auch auf HTML-Elemente aus Java heraus möglich ist. Insbesondere stellt die Klasse die Methode call() bereit, mit der eine JavaScriptFunktion innerhalb von Java mit Parametern aufgerufen werden kann. Eine weitere Methode – eval() – kann einen JavaScript-Ausdruck ebenfalls auswerten – als Aufruf eines einzelnen Strings. Das Konzept funktioniert nicht nur im Navigator, sondern auch im Internet Explorer (zumindest in den neueren Varianten). Demonstrieren wir die Technik in einem kleinen Beispiel Schritt für Schritt. Zuerst das kleine, aber vollständig die Situation beschreibende Beispiel:
Listing 15.5: Zugriff auf JavaScript aus Java heraus
import java.awt.*; import java.applet.*; import netscape.javascript.*; public class Applet2 extends Applet { JSObject jsHandle; /**Das Applet initialisieren*/ public void init() { jsHandle = JSObject.getWindow(this); jsHandle.eval("alert('Hallo Welt')"); } public void paint(Graphics g) { g.drawString("Das Java-Applet ruft zuerst die JavaScript-Funktion auf", 5, 25); }}
Abbildung 15.6: Das Applet ruft zuerst eine JavaScript-Funktion auf – Netscape Navigator
816
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Kapitel 15 Abbildung 15.7: Der Internet Explorer kann es auch.
Beachten Sie als ersten Punkt den import-Befehl für die Netscape-spezifischen Klassen. Wenn sich diese nicht im Suchpfad des JDK bzw. Ihrer JavaEntwicklungsumgebung befinden, muss beim Kompilieren der Pfad dorthin unter Umständen explizit beim Compiler angegeben werden. Dies kann mit der Option -classpath erfolgen. Etwa so: javac -classpath C:\Programme\Netscape\Communicator\Program\java\classes\java40.jar Applet2.java
In der Regel ist nach der Installation des Communicators unter Windows im Verzeichnis \Programme\Netscape\Communicator\Program\java\classes das benötigte jar-Paket zu finden. Um nun auf JavaScript aus Java heraus zugreifen zu können, benötigen Sie einen Handle auf ein JavaScript-Objekt. Den erhalten Sie, indem Sie eine JSObject-Variable anlegen und mit der Methode getWindow([Applet]) dieser dann das Applet-Fenster zuweisen. Dabei wird für das übergebene Applet in der Regel this (also das aktuelle Applet-Fenster) verwendet. Bester Platz für eine solche Zuweisung ist die init()-Methode. Mit dem JavaScript-Objekt haben Sie dann diverse Methoden für den Zugriff auf JavaScript-Funktionalitäten zur Verfügung. Wir verwenden hier in dem Beispiel nur die eval()Methode, um damit eine einfache JavaScript-Anweisung aufzurufen. Interessant ist aber auch die Methode getMember(), um damit einzelne Bestandteile einer Webseite gezielt anzusprechen. Die Methode bekommt als Parameter einen String übergeben, der das Element spezifiziert. Die Methoden können verschachtelt angewandt werden. Im Allgemeinen ist der Übergabewert der ersten getMember()-Methode document für die Webseite. Dieser Rückgabewert wird dann auf ein JSObject gecastet. Dann wird darauf die getMember()-Methode mit dem Namem des HTML-Elements angewandt.
Java 2 Kompendium
817
Kapitel 15
Andere Sprachen in Verbindung mit Java
Abbildung 15.8: Die NetscapeErweiterungen für Java
Etwa für den Fall eines Formulars mit dem Namen meineForm in einer Webseite sieht das skizziert so aus: JSObject jsHandle, jsDoc, jsForm; ... jsHandle = JSObject.getWindow(this); jsDoc = (JSObject)jsHandle.getMember("document"); jsForm = (JSObject)jsDoc.getMember("meineForm");
Das Verfahren kann so auf jedes Element einer Webseite fortgesetzt werden, wenn man die Verschachtelungen der HTML-Elemente entsprechend beachtet. Aber zurück zu unserem Beispiel. Wir sind noch nicht ganz fertig, denn die HTML-Datei zum Einbinden des Applets muss noch leicht modifiziert werden. Das <APPLET>-Tag benötigt zwingend das Attribut MAYSCRIPT. Ohne das Attribut wird der Browser in der Regel den Zugriff auf JavaScript aus Java heraus nicht gestatten. Die HTML-Datei sieht also so aus: Listing 15.6: Das Applet-Tag muss mit MAYSCRIPT erweitert werden
818
<APPLET CODE = "Applet2.class" WIDTH = 350 HEIGHT = 80 MAYSCRIPT>
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Kapitel 15 Abbildung 15.9: Ohne MAYSCRIPT geht es nicht.
Java – JavaScript-Verbindung über document.applets Zusätzlich zu dem LiveConnect-Konzepts gibt es auch andere Möglichkeiten, aus JavaScript heraus Java zu nutzen, und zwar sowohl im Navigator (mit ein paar Einschränkungen) als auch im Internet Explorer und Opera, wobei letzterer gelegentlich Ärger machen kann. Das Verfahren beruht darauf, dass man mittels der Syntax document.applets[] ein Applet aus JavaScript heraus ansprechen kann. Dies erfolgt über die Syntax document.[Applet].[Appletmethode/Appleteigenschaft].
Man hat direkten Zugriff auf alle öffentlichen Methoden und Eigenschaften. So kann man beispielsweise immer auf die Standardmethoden von einem Applet (init(), start(), stop() usw.) zugreifen. Ein weiteres Beispiel mit dem Zugriff auf Variablen und Methoden in einem Java-Applet und Zugriff auf die Rückgabewerte aus dem Applet innerhalb von JavaScript mit dieser Technik soll nun das Verfahren zeigen. Es geht um die Überprüfung eines Passwortes in einer Webseite, das in einem JavaApplet versteckt und dort überprüft wird. Das Java-Applet verbirgt bei geeigneter Programmierung alle Informationen. Wir verwenden zwar in dem Beispiel aus Gründen der Übersichtlichkeit ein hardcodiertes Passwort, aber Java stellt natürlich zahlreiche Möglichkeiten bereit, um Passwörter individuell zu verwalten. Der Java-Code könnte so aussehen: import java.awt.Graphics; public class Passw extends java.applet.Applet { public String pw; private String zugang = "Sesam"; public int ueberprPW() { if (pw.equals(zugang)) { return 1; } else {
Java 2 Kompendium
Listing 15.7: Passwortüberprüfung in einem Applet
819
Kapitel 15
Andere Sprachen in Verbindung mit Java return 2; } } public void paint(Graphics g) { g.drawString("Ein Java-Applet als Blackbox zum Überprüfen des Passwortes", 5, 25); } }
Die Methode public void paint(Graphics g) und die am Anfang notierte Zeile import java.awt.Graphics; sind im Prinzip für die Funktionalität des Applets als Zugangskontrollmechanismus nicht notwendig. Sie dienen nur dazu, dass überhaupt etwas von dem Applet zu sehen ist (was aber für eine Funktionalität wie gesagt absolut nicht notwendig ist). Der Rest des Applets ist einfach. Wichtig sind die zwei folgenden Variablendeklarationen : public String pw; private String zugang = "Sesam";
Die erste Zeile deklariert eine String-Variable unter Java als public. Darauf kann auf JavaScript heraus zugegriffen werden. Sie wird das in einem HTML-Formular eingegebene und an eine JavaScript-Funktion übergebene Passwort aufnehmen. Die zweite Zeile hingegen deklariert eine String-Variable als private. Diese Variable ist somit nicht öffentlich, was in unserem Zusammenhang bedeutet, dass darauf auf JavaScript heraus nicht zugegriffen werden kann (Stichwort Datenkapselung). Und sogar noch mehr. Diese Variable wird durch das äußerst zuverlässige Java-Sicherheitskonzept vor Zugriffen aus anderen Java-Klassen heraus versteckt. Diese Variable ist damit sehr effektiv versteckt. Sie enthält den Kontrollzugangswert, der natürlich nicht nach aussen gegeben werden darf (bei der Wertzuweisung setzt auch die individuelle Verwaltung von Passworten an). Auf die unter Java als public deklariert Methode public int ueberprPW() kann hingegen wieder aus JavaScript heraus zugegriffen werden. In der – sehr einfachen – Methode wird das übergebene Passwort mit dem versteckten Kontrollwert verglichen und je nach Übereinstimmung ein anderer Rückgabewert der Methode erzeugt, der dann unter JavaScript wieder zur Verfügung steht (weil die gesamte Methode öffentlich ist). Dort kann dann auf Grund des Rückgabewertes entschieden werden, wie weiter zu verfahren ist. Selbstverständlich kann diese Entscheidung auch innerhalb des JavaApplets erfolgen (etwa das Laden von Webseiten oder der Download von Dateien direkt aus dem Applet heraus, was das Verfahren noch viel sicherer macht).
820
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Kapitel 15
Die HTML-Datei mit dem JavaScript sieht so aus: <SCRIPT LANGUAGE="JavaScript"> function pwtest() { document.applets[0].pw = document.forms[0].passwort.value; if (document.applets[0].ueberprPW()==1) { alert("Ihr Passwort ist korrekt."); } else { alert("Ihr Passwort ist leider falsch."); } }
Listing 15.8: Verlagerung der Überprüfung in ein Applet
Abbildung 15.10: Das Passwort ist falsch – Navigator
Java 2 Kompendium
821
Kapitel 15
Andere Sprachen in Verbindung mit Java
Abbildung 15.11: So funktioniert es auch mit dem Internet Explorer – das Passwort ist korrekt
Innerhalb der HTML-Datei wird ein Applet referenziert. Das nachfolgende Formular enthält ein Eingabetextfeld, das als Passwort-Feld formatiert ist (type=password) und eine Schaltfläche, die beim Klick darauf eine JavaScriptFunktion aufruft. Die wichtigste Funktionalität des Beispiels steckt in der Zeile document.applets[0].pw = document.forms[0].passwort.value;
Die linke Seite der Zuweisung referenziert die öffentliche Variable innerhalb des Applets (alle öffentlichen Elemente der in einer Webseite enthaltenen Applets lassen sich so ansprechen), der ein Wert zugewiesen wird. Auf der rechten Seite wird auf das Formular und dort über den Namen des Eingabefeldes auf den durch den Benutzer eingegebenen Wert zugegriffen. Dieser wird der Java-Variablen zugewiesen. Mit der Syntax if (document.applets[0].ueberprPW()==1)
wird innerhalb einer JavaScript-if-Abfrage der Rückgabewert der öffentlichen Java-Methode ausgewertet. Das war’s.
822
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
15.3.4
Kapitel 15
Applets dynamisch schreiben
Auch jenseits des LiveConnect-Konzepts und des Zugriffs über document.applets kann man aus Scripten heraus Java nutzen. Man kann z.B. die Parameter für ein Java-Applet in einer Webseite setzen. Dabei ist ein einfacher Weg die Beeinflussung von Java-Applets bei der Referenzierung von Applets mittels des <APPLET>-Tags, indem über einen Schreibanweisung einfach die Referenz samt -Werte geschrieben werden, die dann vom Applet aufgenommen werden. Dies kann mit beliebigen Scriptsprachen erfolgen, denen die Webseite als Objekt bereitsteht. Schauen wir uns zuerst die Java-Datei des Applets an. Diese beinhaltet nur drei Label, die auf einem GridLayout platziert werden. Der Inhalt der Labels wird aus den Übergabewerten der HTML-Referenz entnommen. import java.awt.*; import java.applet.*; public class DynamischApp extends Applet { String var0; String var1; String var2; GridLayout gridLayout1 = new GridLayout(); Label label1 = new Label(); Label label2 = new Label(); Label label3 = new Label(); /**Parameterwert holen */ public String getParameter(String key, String def) { return (getParameter(key) != null) ? getParameter(key) : def; } /**Das Applet konstruieren*/ public DynamischApp() { } /**Das Applet initialisieren*/ public void init() { try { var0 = this.getParameter("param0", ""); } catch(Exception e) { e.printStackTrace(); } try { var1 = this.getParameter("param1", ""); } catch(Exception e) { e.printStackTrace(); } try { var2 = this.getParameter("param2", ""); }
Java 2 Kompendium
Listing 15.9: Ein Applet, das aus der HTML-Datei Parameter entgegennimmt
823
Kapitel 15
Andere Sprachen in Verbindung mit Java catch(Exception e) { e.printStackTrace(); } try { initial(); } catch(Exception e) { e.printStackTrace(); } } /**Initialisierung der Komponenten*/ private void initial() throws Exception { label1.setAlignment(1); label1.setText(var0); this.setLayout(gridLayout1); label2.setAlignment(1); label2.setText(var1); label3.setAlignment(1); label3.setText(var2); gridLayout1.setRows(3); this.add(label1, null); this.add(label2, null); this.add(label3, null); } /**Applet-Information holen*/ public String getAppletInfo() { return "Applet-Information"; } /**Parameter-Infos holen*/ public String[][] getParameterInfo() { String[][] pinfo = { {"param0", "String", ""}, {"param1", "String", ""}, {"param2", "String", ""}, }; return pinfo; }}
Die HTML-Datei zum Referenzieren sieht so aus: Listing 15.10: Die HTML-Datei mit den Parametern
824
<APPLET CODE = "DynamischApp.class" WIDTH = 200 HEIGHT = 100>
Java 2 Kompendium
Verbindung von Java zu JavaScript und anderen Scriptsprachen
Kapitel 15 Abbildung 15.12: Das Applet mit Original-HTMLDatei
Wir könnten nun zur Abwechselung VBScript verwenden. Visual Basic Script oder kurz VBScript ist eine abgespeckte Version von Visual Basic für Anwendungen (VBA), die ursprünglich nur für den Microsoft Internet Explorer verfügbar war. VBScript ähnelt JavaScript darin, dass es nur innerhalb von HTML-Dokumenten zu referenzieren ist. Es gibt aber auch einige Unterschiede. Während JavaScript in seiner Syntax Java (oder C und C++) ähnlicher ist, ähnelt VBScript eher dem Visual Basic Code. Mit VBScript können Sie Visual Basic-ähnlichen Code in Ihre Dokumente einbetten. Wenn ein VBScript-fähiger Webbrowser auf das <SCRIPT>-Tag stö ß t, wird der Code kompiliert und dann ausgeführt. In dem nachfolgenden Beispiel könnten wir die VBScrpt-Anweisung document.write "..." verwenden, die unter JavaScript mit Klammern und abschließendem Semikolon notiert wird (document.write("...");). Wir bleiben aber bei JavaScript, da das auch von Navigator und Opera verstanden wird. <SCRIPT LANGUAGE="JavaScript"> document.write('<APPLET CODE = "DynamischApp.class" WIDTH = 200 HEIGHT = 100>'); document.write(' '); document.write(' '); document.write(' '); document.write('');
Java 2 Kompendium
Listing 15.11: Die Steuerung eines Applets über dynamisch generierte -Tags
825
Kapitel 15
Andere Sprachen in Verbindung mit Java
Abbildung 15.13: Das Applet mit neu geschriebener HTML-Datei
15.3.5
Verbindung mit weiteren Techniken
Neben der schon angesprochenen Verbindung mit VBScript (Informationen dazu gibt bei Microsoft unter http://www.microsoft.com/intdev) kann man Java auch mit VRML koppeln. VRML ist die Abkürzung für Virtual Reality Modeling Language und ist ein Konstruktionsmittel für den Cyberspace. Es wurde ungefähr zeitgleich mit Java erstmals der Internet-Öffentlichkeit präsentiert. VRML ist ein offener Sprachstandard zur Entwicklung von virtuellen Welten. VRML wurde aus HTML entwickelt. Zwar steht VRML inzwischen für Virtual Reality Modeling Language. Ursprünglich war es jedoch die Abkürzung für Virtual Reality Markup Language, um die Herkunft HTML deutlich zu machen. VRML und HTML waren vom Aufbau her ursprünglich recht ähnlich, jedoch ist VRML natürlich viel mächtiger und umfangreicher, um die dreidimensionalen Effekte beschreiben zu können. Geburtsstunde für VRML war im Frühjahr 1994 auf der alljährlich stattfindenden WWW-Konferenz in Genf. Der erste offizielle Standard 1.0 wurde im April 1995 bekannt gegeben. Für die Normung und Koordination von VRML gründeten Pioniere der ersten VRML-Stufe eine Arbeitsgruppe (VAG = VRML Architecture Group), die die Geschicke von VRML in Zukunft lenken sollte. VRML soll weder eine direkte Konkurrenz zu der Sprache Java, noch eine Ablösung von HTML sein, sondern ist momentan eher als Ergänzung beider Sprachen (natürlich mit unterschiedlichen Schwerpunkten der Erweiterun826
Java 2 Kompendium
Zusammenfassung
Kapitel 15
gen) zu sehen. VRML lässt sich durch seine Offenheit recht leicht mit Sprachelementen anderer Internetsprachen kombinieren und mit eigenen Funktionen erweitern. Mittlerweile werden in die VRML-Welten gerne JavaElemente integriert, die dann für einen Teil der Interaktion mit dem Cyberspace-Surfer zuständig sind. VRML wird also meist für die Konstruktion einer Welt verwendet, während darin vorkommende Objekte oft Java-Applets sind. Ein anschauliches Beispiel ist ein Haus mit diversen Zimmern und Stockwerken (VRML), wo die unterschiedlichsten Objekte (Java-Applets) vorkommen. Durch das Haus – den VRML-Cyberspace – kann man sich bewegen, in die Zimmer eintreten oder die Stockwerke wechseln. Ein klingelndes Telefon, ein Wasserhahn mit fließendem Wasser oder ein Radio mit einstellbarem Sender könnten dort interaktive Java-Applets sein. Statt Hypertexten legt VRML dreidimensionale Szenarien fest, die auf Grund dieser Beschreibung bei jeder Bildschirmdarstellung von dem VRML-Browser jeweils neu berechnet werden. Im Cyberspace werden bislang starre »statische« WWW-Seiten zu dynamischen dreidimensionalen Welten. Es gibt durchaus auch Verbindungsmöglichkeiten von Java mit noch weiteren Techniken wie sogar einer so alten Sprache wie Basic und Derivaten. Visual Basic-Programme lassen sich mit einem einfachen Konvertierungsprogramm nach Java übersetzen. Es gibt zumindest Tools, die das versprechen.
15.4
Zusammenfassung
Java lässt sich mit anderen Sprachen durchaus kombinieren. Die direkte Verbindung mit nativem Code – etwa C – ist jedoch mit grö ß ter Vorsicht durchzuführen und nicht-trivial. Es ist zudem gefährlich, da das JavaSicherheitskonzept ausgehebelt wird. JNI erleichtert die Verbindung von Java und C, stellt aber die grundsätzlichen Probleme und Bedenken bei der direkten Verbindung von Java und nativem Code nicht in Frage. Alternative Wege über externe Schnittstellen sind sicherer und einfacher zu realisieren, auch wenn sie auf Kosten der Performance gehen. Die Verbindung von Java und C++ ist noch schwieriger als die Java-C-Verbindung. Die Verbindung von Java mit Scriptsprachen ist auf einer anderen Ebene angesiedelt. Hier sollte man eher von einer Ergänzung der unterschiedlichen Welten sprechen. Insbesondere ist die Verbindung – zumindest in die Richtung – »Java aus JavaScript heraus nutzen« nicht sonderlich kompliziert, zumal es verschiedene Wege gibt.
Java 2 Kompendium
827
16
Weiterführende Themen
Wir wollen in diesem abschließenden Kapitel einige weiterführende JavaThemen ansprechen. Ob der Komplexität dieser Themen (jedes für sich könnte ein ganzes Buch füllen) können wir sie in diesem Rahmen nicht erschöpfend behandeln. Es soll Ihnen dessen ungeachtet ein Einblick in die jeweilige Thematik vermittelt werden. Wir werden folgende Themen ansprechen: Reflection Serialization JavaBeans Java-Servlets RMI IDL CORBA Netzwerkzugriffe und Sockets Datenbanken und JDBC
16.1
Reflection und Serialization
Zu Beginn der weiterführenden Themen wollen wir – zugegeben recht knapp – zwei Begriffe besprechen, die Grundlage für einige der nachfolgend intensiver behandelten Punkte sind. Es handelt sich einmal um die so genannte Reflection. Dies ist ein Verfahren in Java, um in Java-Code Informationen über Felder, Methoden und Konstruktoren von geladenen Klassen verfügbar zu machen. Die Technik der JavaBeans nutzt beispielsweise diese so verfügbar gemachten Informationen, um gegenüber Entwicklungsumgebungen Instrumente wie einen Inspektor nutzbar zu machen. Das zweite Grundlagenthema ist die so genannte Object Serialization oder einfach nur Serialization bzw. Serialisierung, die auf der Reflection aufbaut. Dieser Begriff beschreibt ein Java-Verfahren, um Objekte in einem Strom über die Laufzeit einer Java-Applikation hinaus zur Verfügung zu haben. Dazu wird es ermöglicht, dass der Inhalt eines Objekts in einen Stream gespeichert werJava 2 Kompendium
829
Kapitel 16
Weiterführende Themen den kann. Dieser Strom kann z.B. eine beliebige Datei sein. Ein Objekt kann somit in einem Stream zwischengespeichert und zu einem späteren Zeitpunkt daraus wieder aufgebaut werden. Die Lebensdauer eines Objekts kann also über die eigentliche Laufzeit eines Programms hinaus verlängert werden. Hauptanwendung hierfür ist das Versenden von Objekten über das Netzwerk im Zusammenhang mit dem RMI-Konzept, CORBA oder aber der Socket-Kommunikation, was wir dann auch nachfolgend in der Praxis zeigen werden.
16.1.1
Reflection
Das Konzept der Reflection entstand daraus, dass es in Java-Varianten bis zum JDK 1.1 massive Probleme gab, wenn man ein Objekt anlegen oder auf Teile davon zugreifen wollte und noch nicht zu Kompilierzeit bekannt war, wie es genau aussah. Die statische Struktur von Klassen und Objekten machte das extrem schwer. Das ist zwar im Allgemeinen kaum ein Problem, aber wenn man diese Informationen externen Debuggern oder visuellen Entwicklungstools – wie es Visual Basic mit seinen Komponenten für Basic darstellt – unter Java bereitstellen will, ist es ziemlich unbequem. Um das Problem zu beseitigen, wurde ein Konzept entwickelt, mit dem Klassen geladen und instanziert werden konnten, ohne dass bereits zu Kompilierzeit die konkreten Namen bekannt sein mussten. Die Lösung bestand in der Bereitstellung eines Reflection-APIs, das diese normalerweise vom Compiler geforderten Eigenschaften des Laufzeitsystems anderen Anwendungen in einer Proxyform bereitstellt. Im JDK 1.1 wurde das API in Form des Packages java.lang.reflect eingeführt und es wird auch im JDK 1.3 nahezu unverändert so bereitgestellt. Das API stellt die Schnittstellen InvocationHandler (wird vom Aufrufhandler einer Proxyinstanz eingebunden) und Member (Reflektion eindeutig identifizierender Informationen über ein einzelnes Mitglied – das kann ein Feld, eine Methode oder ein Konstruktor sein) bereit. Dazu kommen die Klassen AccessibleObject (Basisklasse von Feldern, Methoden und Konstruktoren), Array (Unterstützung von statischen Methoden für die dynamische Erstellung von Java-Arrays und den Zugriff darauf), Constructor (Informationen über den Konstruktor einer Klasse und Unterstützung des Zugriffs darauf), Field (Informationen über die Felder einer Klasse oder Schnittstelle und Unterstützung des Zugriffs darauf), Method (Informationen über die Methoden einer Klasse oder Schnittstelle und Unterstützung des Zugriffs darauf), Modifier (Unterstützung in Form von statischen Methoden und Konstanten zum Dekodieren der Zugriffsmodifier von Klassen und deren Mitgliedern), Proxy (die Superklasse aller dynamischen Proxyklassen mit Unterstützung in Form von statischen Methoden zum Generieren von dynamischen Proxyklassen und -instanzen) und ReflectPermission (allgemeine reflektive Operationen). Die Klassen in diesem Package bilden zusammen mit der Klasse java.lang.Class1 die Basis für Applikationen wie Debugger, Interpreter, Objektinspektoren, Klassenbrowser, aber auch Diensten wie der Object Serialization und JavaBeans. Letztere 830
Java 2 Kompendium
Reflection und Serialization
Kapitel 16
benötigen ja den Zugriff auf die mit public deklarierten Mitglieder von einem Zielobjekt bereits vor der Laufzeit. Denken Sie etwa bei visuellen Entwicklungsumgebungen an den grafischen Erstellungsmodus von Komponenten. Deren Eigenschaften und Methoden werden bereits während des Design-Modus benötigt. Wir werden Reflection gleich im Rahmen der JavaBeans noch genauer verfolgen. Auch bei den darauf folgenden Datenbankzugriffen wird die Technik eine Rolle spielen.
16.1.2
Serialization
Serialization ist eine Erweiterung der Core-Java-Input/Output-Klassen um die Unterstützung von Objekten. Sie werden in einen Strom von Bytes gespeichert und können daraus komplett rekonstruiert werden. Serialization wird beispielsweise verwendet, um Objekte persistent zu machen (das dauerhafte Speichern von Objekt-Daten auf externen Datenträgern), für die Kommunikation via Sockets (das werden wir anschließend in der Praxis noch sehen) oder für die Umsetzung des Remote Method Invocation-Konzepts (RMI) verwendet. Die Defaultverschlüsselung von Objekten schützt dabei private und transiente Daten und unterstützt die Evolution von Klassen. Eine Klasse kann ihre eigene externe Verschlüsselung implementieren und ist dann aber selbst verantwortlich für das externe Format. Serialization basiert im Wesentlichen auf dem Package java.io mit seinen Ein- und Ausgabeströmen. Das Schreiben von Objekten und primitiven Daten in einen Object Stream funktioniert meist so, wie in dem nachfolgend skizzierten Beispiel: // Serializiere das Tagesdatum in eine Datei FileOutputStream f = new FileOutputStream("tmp"); ObjectOutput s = new ObjectOutputStream(f); s.writeObject("Heute: "); s.writeObject(new Date()); s.flush();
Zuerst wird ein OutputStream (hier ein FileOutputStream) benötigt, der die Bytes empfängt. Dann wird ein ObjectOutputStream kreiert, der in den FileOutputStream schreibt. Nächster Schritt ist, den Textstring und das DateObjekt in den Stream zu schreiben. Allgemein gilt, dass Objekte mit der writeObject()-Methode und primitive Daten mit den Methoden eines 1
Die Klasse stellt insbesondere die Methode getClass() bereit, mit der ein beliebiges Objekt zu jeder Klasse, die das Laufzeitsystem verwendet, während des Ladevorgangs ein Klassenobjekt vom Typ Class erzeugt werden kann sowie die Methode forName(), um ein Klassenobjekt zu einer Klasse eines Namens zu beschaffen. Wir wenden die Methode bei den Beispielen zu den Datenbankzugriffen an.
Java 2 Kompendium
831
Kapitel 16
Weiterführende Themen DataOutput-Stroms (etwa writeInt(), writeFloat() oder writeUTF()) in den Stream geschrieben werden.
Der umgekehrte Vorgang – das Lesen aus einem Object Stream – funktioniert ähnlich. Wir schauen uns das skizzierte Beispiel an, wie die oben serialisierten Daten wieder deserialisiert werden können. // Deserialisiert einen String und ein Datum aus // einer Datei FileInputStream in = new FileInputStream("tmp"); ObjectInputStream s = new ObjectInputStream(in); String today = (String)s.readObject(); Date date = (Date)s.readObject();
Zuerst wird ein InputStream (hier ein FileInputStream) benötigt, der als Quelle dient. Dann wird ein ObjectInputStream kreiert, der aus dem InputStream liest. Nächster Schritt ist, den Textstring und das Date-Objekt aus dem Stream zu lesen. Allgemein gilt, dass Objekte mit der readObject()Methode und primitive Daten mit den Methoden eines DataInput-Stroms (etwa readInt(), readFloat(), oder readUTF()) in den Stream gelesen werden. Bei dem nachfolgenden Beispiel der Socket-Kommunikation werden wir eine vollständige Anwendung von Serialization sehen.
16.2
JavaBeans
Wir kommen zu einem äußerst spannendem Thema – den Java Beans, welches massiv auf der Technik der Reflection aufsetzen. JavaBeans ist der Name für die Java-Komponenten innerhalb eines Objektmodells. Oder genauer: JavaBeans ist ein portables, plattformunabhängiges, in Java geschriebenes Komponentenmodell, das Sun in Kooperation mit führenden Industrieunternehmen der Computerbranche entwickelt hat. Motiviert wurden Beans höchst wahrscheinlich durch die grafischen Komponenten, die Visual Basic mit den Werkzeugen seiner IDE bereitgestellt hat. Visual Basic beinhaltete das erste Komponentenmodell, das sich auf dem Massenmarkt durchsetzte. JavaBeans können wie Visual-Basic-Komponenten visuell manipuliert werden, was gerade für viele Programmiereinsteiger eine erhebliche Erleichterung darstellt. In Visual Basic war der Erfolg einer solchen einfachen Erstellung von Programmen zu sehen. Das schnelle Erfolgserlebnis brachte viele Einsteiger dazu, sich mit dieser Sprache näher zu befassen. Aber auch für professionelle Programmierer erleichtert visuelles Erstellen von Oberflächen und ähnlichen Strukturen ein Projekt, indem es relativ einfache, aber stupide und zeitaufwändige Arbeit extrem beschleunigt. Ein JavaBean besteht im Allgemeinen aus einer Sammlung einer oder mehrerer Java-Klassen, die oft in einer einzelnen JAR-Datei zusammengefasst 832
Java 2 Kompendium
JavaBeans
Kapitel 16
sind. Ein JavaBean kann zum einen eine Komponente sein, die zur Erstellung einer Benutzerschnittstelle verwendet wird. Die visuellen Entwicklungsumgebungen für Java stellen solche Werkzeug-Tools bereit, etwa den frei verfügbaren JBuilder Foundation von Inprise (mehr Informationen zu diesem Tool und anderen Java-Entwicklungsprogrammen gibt es im Anhang). Abbildung 16.1: Die visuellen Komponenten des JBuilders sind JavaBeans
Es gibt aber nicht nur Beans, die zur Laufzeit einer Applikation sichtbar sind. Es gibt auch solche, die im Rahmen einer Java-Applikation im Hintergrund verwendet werden sollen. Dies ist etwa eine Komponente, die Datenbank-Zugriffsfunktionalitäten bereitstellt. Auch diese wird im Rahmen einer visuellen IDE über eine Komponente in einer Toolbox bereitgestellt und kann visuell zur Designzeit bearbeitet werden. In der einfachsten Ausführung handelt es sich bei einem JavaBean um eine Java-Klasse vom Typ public, die über einen parameterlosen Konstruktor verfügt. JavaBeans verfügen normalerweise über Eigenschaften, Methoden und Ereignisse, die bestimmten Namenskonventionen (auch als DesignMuster bezeichnet) unterliegen. Obwohl JavaBeans schon seit Anfang 1997 als Schlagwort durch die Presse geistern, ist die Technologie selbst noch nicht so alt. Zudem gab es bei den Beans in der Anfangsphase einige Irrungen und Wirrungen. Aber seit dem JDK 1.2 sind Beans schlüssig in Java integriert. Zu den Neuerungen gegenüber der ersten Generation zählen die bessere Unterstützung in Applikationen und Browsern, das Zusammenspiel mit Drag&Drop sowie das Activation-Framework.
16.2.1
Wie passen JavaBeans in Java?
JavaBeans bestehen im Grunde aus normalem Java-Code, sind also reine Java-Klassen. Nur der Grund, warum sie erstellt werden, wie sie verwendet werden und welche Konventionen sie einhalten, hebt sie gegenüber »normalen« Java-Klassen hervor. Sie werden sehr oft nicht auf Grund von Tipparbeit erstellt oder modifiziert, sondern mittels eines eigenen Werkzeugs – meist einer modernen visuellen IDE. Wenn man die volle Funktionalität des Beans-Konzepts nutzen will, kommt man um den Einsatz einer solchen professionellen IDE kaum herum. Aber auch die Verwendung von Beans unterscheidet sich von dem Einsatz »normaler« Java-Klassen. Statt auf Quelltextebene aus einer Java-Klasse eine Instanz zu erzeugen, wird per Drag&Drop aus einer Toolliste ein Symbol des Beans in einen Container gezogen. Damit das funktioniert, müssen Beans einige strenge Konventionen einhalten und bestimmte Eigenschaften bereitstellen, damit IDEs das leisten können. DenJava 2 Kompendium
833
Kapitel 16
Weiterführende Themen noch – genau wie andere Komponententypen auch, sind JavaBeans einfach wiederverwendbare Codeteile, die aber mit minimaler Auswirkung auf den Test des Programms, dem sie hinzugefügt werden, aktualisiert werden können. Sie sind plattformübergreifende, reine Java-Komponenten, die in viele visuelle IDEs installiert und von da aus bei der Konstruktion eines Programms per Drag&Drop verwenden können. Das Kernmodell von JavaBeans besteht im Wesentlichen aus Properties (Eigenschaften bzw. mit Namen versehene Attribute, die einer Komponente zugeordnet sind), Events (Ereignissen) und Methoden. Von besonderem Interesse ist, dass die Eigenschaften einer so in einer Applikation integriertem Bean-Komponente im Rahmen einer geeigneten visuellen Entwicklungsumgebung mit der Maus bzw. einem Property-Sheet-Fenster verändert werden kann. In dem Property-Sheet-Fenster kann man gegebenenfalls Werte verändern, die sich dann unmittelbar auf die Eigenschaften einer Komponente auswirken.
Abbildung 16.2: Der Inspektor – das Property-SheetFenster des JBuilders
834
Java 2 Kompendium
JavaBeans
Kapitel 16
Dabei sollte beachtet werden, dass die Eigenschaften der Komponente durchaus als private deklariert sein können (und meist auch sind) und das Property-Sheet-Fenster eventuell nur öffentliche Zugriffsmethoden bereitstellt. Damit kann gezielt ein intelligenter Filtermechanismus für erlaubte Eigenschaften einer Bean-Komponente realisiert werden (Stichwort Datenkapselung). Mithilfe der visuell abgebildeten Eigenschaften kann also das Erscheinungsbild und das Verhalten einer Komponente beeinflusst werden. Die JavaBeans-Architektur unterscheidet zwischen so genannten Indexed, Bound und Constraint Properties: Normale Attribute haben einen einzigen Wert, während bei Indexed Properties mehrere Werte zusammengefasst sind, die zu einem Property gehören. Der Zugriff erfolgt über einen Index. Bound Properties verbinden Properties mit dem Event-Mechanismus. Damit können bestimmte registrierte Komponenten bei Zustandsänderungen eines Properties eine Nachricht erhalten. Dazu muss jedoch die betreffende Komponente einen Event vom Typ Property-Change zur Verfügung stellen. Constraint Properties erlauben die Validierung einer durchzuführenden Änderung durch andere Komponenten. Auch hierfür wird ein Standard-Event vom Typ Property-Change zur Verfügung gestellt. Abbildung 16.3: Nicht alles kann für eine Eigenschaft gesetzt werden
Zu den einem Bean zugeordneten Faktoren zählen wie angedeutet auch die Ereignisse, auf die das Objekt (natürlich kann man ein Bean auch als reines Objekt betrachten) reagieren soll. Die Besonderheit von Beans ist es nun, dass das Property-Sheet-Fenster in der Regel in einem Pull-Down-Menü oder einem Registerblatt die Events anzeigt, die für die aktuelle Komponente verwendet werden können. Von besonderem Vorteil ist die Tatsache, dass die Quelltextstruktur für diese Events mit einem einfachen Klick mit der Maus generiert werden kann und »nur noch« mit Leben gefüllt werden muss.
Java 2 Kompendium
835
Kapitel 16
Weiterführende Themen
Abbildung 16.4: Der Inspektor des JBuilders zeigt in einem Registerblatt die erlaubten Ereignisse einer Komponente an.
Um aus einer IDE heraus diese Zusammenhänge von Quelltext-Details analysieren zu können, muss diese Eigenschaften und zur Verfügung stehende Methoden einer Bean-Komponente über einen Mechanismus herausfinden, der Introspektion genannt wird und massiv auf der Reflection-Technik aufbaut. Dabei wird der Bytecode einer Komponente – sie ist ja im Prinzip reiner Java-Source – durchleuchtet. Auf Grund dessen können die Eigenschaften, Methoden und Ereignisse der Komponente bestimmt und der IDE bereitgestellt werden. Umgekehrt wird jede in dem Property-Sheet-Fenster vorgenommene Änderung an der richtigen Stelle im Quelltext aktualisiert. Sind JavaBeans Applets oder eigenständige Applikationen? Weder noch. Beans sind keine Applets. Sun gibt als wesentlichen Unterschied die Möglichkeit der visuellen Erstellung und eine relativ eng ausgelegte Zielfunktionalität von Beans an. Allerdings stellt Sun ebenso fest, dass Applets so entwickelt werden können, dass sie wie Beans aussehen und arbeiten.
836
Java 2 Kompendium
JavaBeans
Kapitel 16
Auf der anderen Seite sind Beans aber auch in der Regel keine eigenständigen Anwendungen (obwohl es explizit nicht verboten ist, sie mit einer main()-Methode auszustatten). Applets und Anwendungen können jedoch aus JavaBeans modular zusammengesetzt werden. Es ist bereits seit langer Zeit Ziel der Computerwelt, mittels Komponenten eine Wiederverwendbarkeit von vorgefertigten Teilen zu gewährleisten, um komplexere Anwendungen aus leicht zu kombinierenden Einzelteilen zu erstellen. Die objektorientierte Theorie verfolgt auf abstrakterer Ebene das gleiche Ziel. Damit sind Komponenten bei konsequenter Umsetzung hervorragend in die objektorientierte Theorie integriert und eine logische Folge der objektorientierten Programmierung. Sie bieten gleichwohl gegenüber den normalen objektorientierten Techniken einige Vorteile. Die Wiederverwendung von vielen Objekten scheitert oft daran, dass das Design nicht zu der speziell benötigten Aufgabe passt. Das Objekt ist zu speziell oder passt in einem entscheidenden Detail nicht genau. Also wird neu programmiert. Bei gutem Design bieten Komponenten als kleine Programme, die nur ganz beschränkte Aufgaben erfüllen, eine bessere Alternative. Sie sind wie kleine Bausteine, aus denen die eigentlichen Anwendungen zusammengesetzt werden. Bei den JavaBeans steht für die Zusammenarbeit der Komponenten über so genannte Bridges eine Schnittstelle – auch zu anderen Komponenten wie ActiveX-Controls oder OLE-Komponenten – zur Verfügung. JavaBeans contra ActiveX-Controls Wenn man an Komponenten denkt, ist die Verbindung zu ActiveX-Controls naheliegend. Vergleichbar sind JavaBeans recht gut mit ActiveX-Controls oder OCX-Controls. Sie können wie diese visuell manipuliert werden und bieten über so genannte Bridges eine Schnittstelle zu anderen Komponenten vom gleichen Typ, jedoch auch andersartigen Typen – wie bei Beans zu ActiveX-Controls (die so genannte ActiveX-Brücke oder ActiveX-Bridge) oder OLE-Komponenten – an. Im Gegensatz zu diesen Komponenten sind JavaBeans jedoch explizit plattformunabhängig und im Sicherheitskonzept von Java integriert. Überdies gibt es grundlegende Unterschiede in der dahinter liegenden Philosophie. Die ActiveX-Technologie ist erheblich aufwändiger, weil sie ein grö ß eres Spektrum an potenziellen Anwendungen abdeckt. JavaBeans müssen durch ihre Integration in das Java-Sicherheitskonzept auch nicht registriert werden und sind damit viel leichter und flexibler einsetzbar. Ein weiterer Unterschied ist, dass JavaBeans-Komponenten zur Laufzeit einer Anwendung dynamisch geladen werden können und sich in ein bestehendes Modell noch zur Laufzeit einbinden. Bei ActiveX-Controls und Windows (in den meisten Versionen) muss der Rechner neu gestartet werden.
Java 2 Kompendium
837
Kapitel 16
Weiterführende Themen
16.2.2
Beans erstellen
In der Regel erstellt man JavaBeans nicht mehr »von Hand«, sondern mit einer professionelle Java-IDE. Das heißt aber nicht, dass man nicht ohne ein solches Tool Beans erstellen könnte. Wenn man Beans erstellen möchte, basiert die Realisierung der meisten erweiterten Funktionalitäten auf den Paketen java.beans und java.beans.beancontext. Aber ein einfaches JavaBean kann sogar schon so unkompliziert aussehen: Listing 16.1: Ein einfachstes JavaBean
import java.awt.*; import javax.swing.JPanel; public class MeinBean extends JPanel { BorderLayout borderLayout1 = new BorderLayout(); // Eigenschaft des Beans – private private String sample = "Beispiel"; public MeinBean() { initial(); } private void initial() { this.setLayout(borderLayout1); } // Wert der Eigenschaft abfragen public String getSample() { return sample; } // Wert der Eigenschaft setzen public void setSample(String newSample) { sample = newSample; }}
Das Bean wurde mit dem angegebenen Namen und der angegebenen Superklasse erstellt. Die Klasse ist als public deklariert und verfügt über einen parameterlosen Konstruktor. Selbst in diesem rudimentären Status handelt es sich aber um ein gültiges JavaBean. Um das Bean nun aber laufen zu lassen, kommt man nicht um eine geeignete Testumgebung herum. Das Bean besitzt ja keine main()-Methode. Jede Java-IDE, mit der man Beans erstellen kann, bietet eine solche LaufzeitTestumgebung für Beans. Aber Sie können auch von Sun eine kostenlose Bean-Testumgebung laden – das BDK (JavaBeans Development Kit oder kurz Beans Development Kit). Sie finden es unter http://java.sun.com/ products/javabeans/software/bdk_download.html. Das BDK stellt einen Bean-Container, die »BeanBox« und einige Beispielcodes bereit. Das Anfang 2001 aktuelle BDK 1.1 basiert mindestens auf der Java-2-Standard-Edition SDK 1.2 oder höher.
838
Java 2 Kompendium
JavaBeans
Kapitel 16 Abbildung 16.5: Hier gibt es das BDK
Wenn man das Bean um eine sinnvolle Funktionalität erweitern will, muss man es einfach wie eine normale Java-Klasse um die Dinge ergänzen, die es tun soll. Das klingt zwar ziemlich nichtssagend, soll aber deutlich machen, dass die Unterschiede zu der Erstellung einer GUI-Applikation in der Tat nicht riesig sind. Wir könnten das Bean-Beispiel, das bisher nur ein SwingPanel mit einem Borderlayout und einer Beispieleigenschaft beinhaltet, einfach um zwei Buttons und ein Label erweitern, das über die Buttons gefüllt und gelöscht werden kann. Was wir also tun würden, ist, wie bei GUI-JavaApplikationen gewohnt die Benutzerschnittstelle des Beans zu erweitern. Interessanter und speziell auf Beans angepasst ist der Vorgang, wie Eigenschaften zu Beans hinzugefügt und dann einer IDE bereitgestellt werden. Diese Eigenschaften legen die Attribute fest, über die ein Bean verfügt. Diese werden wie bereits gesagt meist nicht direkt offen gelegt. JavaBeans-Eigenschaften verfügen statt dessen üblicherweise über eine Lese- und eine Schreibzugriffsmethode, auch entsprechend als Getter- und Setter-Methode bezeichnet. Eine Getter-Methode gibt den aktuellen Wert der Eigenschaft zurück. Mit einer Setter-Methode wird die Eigenschaft auf einen neuen Wert gesetzt. Wenn die Eigenschaft schreibgeschützt sein soll, gibt es keine Setter-Methode. Eine Eigenschaft eines Beans kann von jedem beliebigen Java-Datentyp sein.
Java 2 Kompendium
839
Kapitel 16
Weiterführende Themen Wenn Sie ein Bean in einer IDE bereitstellen wollen, kann man optional BeanInfo-Klassen zur Dokumentation im Rahmen der IDE erstellen. Sie können einige Eigenschaften verbergen, sodass sie im Property-Sheet-Fenster der IDE nicht angezeigt werden. Auf solche Eigenschaften kann weiterhin mit Programmcode zugegriffen werden, Benutzer können jedoch ihre Werte in der Entwurfsphase nicht ändern. Eine BeanInfo kann Informationen über das Icon in der Werkzeugleiste oder eine in der IDE anzuzeigene Hilfe beinhalten. Die BeanInfo basiert im Wesentlichen auf dem Paket java.beans.*. Noch wichtiger ist es, Ereignisse zu Beans hinzufügen, damit ein JavaBean das Generieren (oder Auslösen) von Ereignissen und/oder das Senden eines Ereignisobjekts an ein Listener-Objekt auslösen kann (eine oder beide dieser Funktionalitäten sind möglich). Dazu müssen natürlich die Überwachung von Ereignissen und Reagieren auf die Ereignisse implementiert werden. Sowohl die Reaktion auf Standardereignisse als auch selbst definierte Ereignisse lassen sich implementieren. Allgemein gilt, dass eine Komponente bei einem aufgetretenen Ereignis benachrichtigt wird, wenn die passende Listener()-Methode aufgerufen wird. Eine Bean-Klasse beinhaltet darüber hinaus oft fire<event>-Methoden, die ein Ereignis an alle registrierten Listener senden. Für jede Methode in der Listener-Schnittstelle wird ein derartiges Ereignis generiert. Wenn ein Bean zum Überwachen von Ereignissen als Listener definiert werden soll, muss der zu überwachenden Ereignistyp mit der passenden Listener-Schnittstelle implementiert werden. Schauen wir uns eine entsprechend erweiterte Bean-Klasse (zwei Eigenschaften) an, die auf die Standard-actionPerformed()-Methode (hier mit Beispiel noch leer) reagieren kann.
Listing 16.2: Ein JavaBean mit Ereignissen
840
import javax.swing.JPanel; import java.awt.*; import java.awt.event.*; import java.util.*; public class MeinBean2 extends JPanel implements ActionListener { BorderLayout borderLayout1 = new BorderLayout(); // Eigenschaft des Beans – private private String sample = "Beispiel"; private String farbe; private transient Vector actionListeners; public MeinBean2() { initial(); } private void initial() { this.setLayout(borderLayout1); } // Wert der Eigenschaft abfragen public String getSample() {
Java 2 Kompendium
JavaBeans
Kapitel 16
return sample; } // Wert der Eigenschaft setzen public void setSample(String newSample) { sample = newSample; } public void setFarbe(String newFarbe) { farbe = newFarbe; } public String getFarbe() { return farbe; } public synchronized void removeActionListener(ActionListener l) { if (actionListeners != null && actionListeners.contains(l)) { Vector v = (Vector) actionListeners.clone(); v.removeElement(l); actionListeners = v; } } public synchronized void addActionListener(ActionListener l) { Vector v = actionListeners == null ? new Vector(2) : (Vector) actionListeners.clone(); if (!v.contains(l)) { v.addElement(l); actionListeners = v; } } protected void fireActionPerformed(ActionEvent e) { if (actionListeners != null) { Vector listeners = actionListeners; int count = listeners.size(); for (int i = 0; i < count; i++) { ((ActionListener) listeners.elementAt(i)).actionPerformed(e); } } } public void actionPerformed(ActionEvent e) { }}
16.2.3
Spezielle Anwendungen von JavaBeans
JavaBeans werden im Allgemeinen dafür erstellt, besondere Anwendungen zusammenzufassen, damit diese dann per Mausklick bereitstehen. Dazu zählt beispielsweise die Bündelung von Datenbankfunktionalitäten in einem Bean. Dazu zählt der Zugriff auf relationale Datenbanken über das JDBCAPI (Java Database Connectivity), worauf wir nachfolgend eingehen werden.
Java 2 Kompendium
841
Kapitel 16
Weiterführende Themen Neben der Wiederverwendung von Bausteinen und der visuellen Erstellung von Programmteilen ist ein weiterer wichtiger Aspekt bei JavaBeans die mögliche Verteilung auf verschiedene Server. Einmal entwickelte Beans werden üblicherweise in JAR-Dateien verpackt und können ggf. erst beim EndUser zusammengesetzt werden. Dies setzt natürlich ein erheblich komplexeres Architekturmodell voraus, als es bei rein lokalen Anwendungen der Fall ist. Die Object Management Group (OMG) beschreibt in ihrem Modell Object Management Architecture (OMA) eine solche Architektur verteilter Komponenten. Das CORBAKonzept spezifiziert die dafür benötigten Dienste. Auch dieses Thema konkretisieren wir in den nächsten Abschnitten mit Beispielen. Verteilung auf verschiedene Server heißt bei JavaBeans übrigens nicht, dass diese sich auch noch zur Laufzeit dort befinden, sondern JavaBeans verfolgen als lokales Komponentenmodell zur Laufzeit die Kommunikation der Komponenten auf der lokalen Maschine. Sie müssen also zwingend dort zusammengesetzt sein oder werden zumindest dynamisch nachgeladen und binden sich in das laufende Modell ein. Diese Einschränkungen haben den Grund, dass die Entwicklung von verteilten Komponenten zur Laufzeit ein noch komplizierteres Modell erfordert.
16.2.4
Beans einer IDE bereitstellen
Wie ein fertiges Bean einer IDE zur Verfügung gestellt wird (was eigentlich immer für einen Nutzen des Beans notwendig ist, wenn man keine main()Methode integriert hat), kann nicht allgemeingültig gesagt werden. Jede geeignete IDE stellt dafür ein eigenes Verfahren bereit, das in der dortigen Hilfe beschrieben sein sollte. Allgemein muss aber meist die kkompilierte Datei bzw. das ganze Paket mit dem Bean über die Konfiguration der IDE hinzugefügt werden. Die meisten IDEs stellen dazu einen Assistenten bereit, der Beans aus Paketen importieren lässt. Diese tauchen dann in der Werkzeugleiste der IDE auf und lassen sich wie die Standard-Beans per Drag&Drop einsetzen.
16.3
Verteilte Systeme – RMI, IDL und CORBA
Mit den Schlagworten RMI, IDL und CORBA bewegen wir uns zu dem Bereich der verteilten Systeme. Um diese einleitend etwas zu erläutern, beginnen wir mit dem Ursprung – den monolithischen Systemen und Großrechnern. In solchen Systemen waren (und sind immer noch) Geschäftslogik, Benutzeroberfläche und Funktionalität in einer einzigen großen Anwendung enthalten. Etwa ein Datenbanksystem, auf das nur per dummer Terminals zugegriffen wird. Die erste Weiterentwicklung dieser Technologie war die (später zweischichtig genannte) Client-Server-Architektur, mit der 842
Java 2 Kompendium
Verteilte Systeme – RMI, IDL und CORBA
Kapitel 16
ein Teil der Aufgaben einer Anwendung zu einem intelligenten Client (etwa einem PC) verlagert werden konnte. Client-Server-Anwendungen sind in der Regel so konzipiert, dass der Client auf jeden Fall die Benutzeroberfläche beinhaltet. Die eigentliche Funktionalität bleibt beim Server. Die Geschäftslogik wird mal hier, mal da geführt und bildet mit der jeweiligen Schicht eine Einheit. Ein weiterer Fortschritt war die mehrschichtige Client-ServerArchitektur, die in der Regel drei (im Prinzip aber beliebig viele) Schichten aufweist. Die drei beschriebenen logischen Bestandteile einer Applikation (Geschäftlogik, Benutzeroberfläche und Funktionalität) werden in logische Schichten aufgeteilt. Der nächste logische Schritt war die Auflösung der strengen Client-Server-Abhängigkeiten. Dabei wird die gesamte Funktionalität einer Anwendung in Form von Objekten dargestellt. Von der Anzahl her sind das meist noch mehr logische Einheiten als bei mehrschichtigen Client-Server-Applikationen. Diese Objekte sind weder vom Ort her gebunden (im Rahmen der technischen Möglichkeiten natürlich doch), noch liegt eindeutig fest, dass ein Objekt nur Server und eines nur Client ist. Die Unterschiede verschwimmen. Ein solches System nennt man dann ein verteiltes System. Ein ganz großer Fortschritt im Bereich der Realisierung von verteilten Programmierung ist das RMI-Modell, auf das unter anderem auch Java-Applikationen (sowohl eigenständige Applikationen, aber auch Applets und Beans) zugreifen können. Das Remote Method Invocation Interface (RMI) bietet die Möglichkeit, Java-Klassen, die auf einer anderen virtuellen Maschine laufen, anzusprechen. Dabei ist es egal, ob die virtuelle Maschine lokal vorhanden oder irgendwo im Internet ausgeführt wird. RMI stellt ein API zur Verfügung, mit dessen Hilfe die besagte Kommunikation zweier Komponenten über Adress- oder Maschinenräume hinweg möglich ist. RMI ist eine Art objektorientierter RPC-Mechanismus (Remote Procedure Call), der speziell für Java entwickelt wurde. Das heißt aber auch, dass RMI nur genutzt werden kann, wenn sowohl das Server- als auch das Client-Objekt in Java implementiert sind. Deswegen wird auch keine spezielle Beschreibungssprache benutzt, um das entfernte Interface zu beschreiben. In Java werden im SDK 2 zur Umsetzung des RMI-Konzeptes im Wesentlichen fünf Packages verwendet: java.rmi java.rmi.dgc java.rmi.registry java.rmi.server java.rmi.activation
Zwei in der JDK-Version 1.1.x eingeführte Programme, rmic – der Java RMI Stub Compiler – und rmiregistry (Java Remote Object Registry) dienen zur programmiertechnischen Umsetzung des RMI-Konzeptes. Neu im JDK 1.2 wurde das Tool rmid – Java RMI Activation System Daemon – hinzugenommen. Java 2 Kompendium
843
Kapitel 16
Weiterführende Themen In diesem Zusammenhang wurde auch das Konzept der oben bereits beschriebenen Object Serialization aufgenommen, um darüber Inhalte eines Objekts in einen Stream zu speichern und wieder zu reproduzieren. Dies ist bei der Versendung von Objekt-Inhalten zwischen verteilten Client-ServerBeziehung in der Regel notwendig. Das Java-Package java.io beinhaltet die entsprechenden Erweiterungen. serialver heißt das Object Serialization Tool für Java. Nun ist aber RMI nicht die einzige Möglichkeit, verteilte Systeme aufzubauen, auch nicht unter Java. Da gibt es einmal die Möglichkeit der so genannten Socket-Programmierung. Dies ist ein Kanal, über den Anwendungen oder Teile davon (Threads) miteinander verbunden sind und kommunzieren können. Eine Abart der Socketkommunikation ist der so genannte Remote-Prozedurenaufruf (RPC – Remote Procedure Call), wo die Socket-Kommunikation nicht direkt, sondern über eine darüber liegende Schnittstelle realisiert wird. Socket-Programmierung ist im Allgemeinen recht tief an der Hardware angesiedelt und aufwändig zu realisieren. Wir demonstrieren sie dennoch mit Beispielen etwas weiter unten im Rahmen des Netzwerkabschnitts. Weitere Standards für die verteilte Programmierung sind der DCE-Standard (Distributed Computing Environment) und das – weitgehend auf die Windows-Plattform beschränkte2 – DCOM (Distributed Component Object Model) von Microsoft. Und last but not least CORBA, worauf wir neben der Socket-Kommunikation als einzige Technik genauer eingehen. Der Grund ist, dass CORBA wohl die universellste Technik ist. Dazu relativ leicht einzusetzen, flexibel und stabil. Das macht CORBA und seine Basis-Beschreibungssprache IDL – trotz RMI – auch für verteilte Java-Applikationen zu einem idealen Partner. Zumal man dann auch verteilte Systeme erstellen kann, die nicht nur Java einsetzen.
16.3.1
Java IDL oder wie ist RMI in Bezug auf CORBA zu sehen?
RMI und CORBA werden über IIOP (Internet InterORB Protocol) zusammengebracht. Eine logische Fortsetzung der RMI-Entwicklung ist die Java IDL (Interfaces Definition Language), die es Java ermöglicht, eine Verbindung zu anderen Verteilungsplattformen, wie zum Beispiel CORBA (Common Object Request Broker Architecture), aufzubauen. Dies bedeutet, entfernte Schnittstellen über IDL zu definieren. IDL ist eine Definitionssprache, die die Kommunikation zwischen verschiedenen Programmiersprachen über Schnittstellen ermöglicht und in das CORBA-Konzept integriert. Sie ist eine objektorientierte Sprache, die aber nur der Definition von Schnittstellen dient und nicht irgendwelchen konkreten Implementierungen. Dazu gilt, dass IDL sprachenunabhängig ist und zur Sprachabbildung (Language Mapping) für die unterschiedlichsten Sprachen (etwa Java, C/C++, Small2
844
Es gibt auch Erweiterungen für andere Plattformen oder sie sind zumindest angekündigt (Solaris, Unix, MVS), aber Kern ist Windows.
Java 2 Kompendium
Verteilte Systeme – RMI, IDL und CORBA
Kapitel 16
talk oder Cobol) verwendet werden kann. Die Syntax von IDL ähnelt stark der von Java und auch die anderen Sprachmerkmale sind verwandt und für Java-Programmierer leicht zu lesen. IDL unterscheidet Groß- und Kleinschreibung, bildet Blöcke mittels geschweifter Klammen, beendet Anweisungen mit einem Semikolon, ist polymorph, kennt Exceptions, benötigt keine Speicherverwaltungsbefehle, kann Schnittstellen erweitern (über Doppelpunkt und Nachstellen der Schnittstelle), deklariert Methoden ähnlich wie Java in Schnittstellen, hat ähnliche Datentypen wie Java (aber alle klein geschrieben, auch string) und kennt die traditionellen Java-Kommentare. Viele Schlüsselwörter von IDL kommen auch in Java vor. Mittels IDL werden Module erstellt (eingeleitet mit dem Schlüsselwort module), in denen Schnittstellendeklarationen (Schlüsselwort interface) festgelegt werden. Module entsprechen den Paketen unter Java, und insbesondere die Schnittstellenphilosophie von Java und IDL respektive CORBA ist nahezu deckungsgleich. Der neben IDL zweite wichtige Eckpfeiler von CORBA ist der so genannte ORB (Object Request Broker). Das SDK 2 enthält einen ORB, der es erlaubt, verteilte Anwendungen auf der Basis von Java und CORBA zu schreiben. Kern der Realisierung davon sind alle Elemente, die in den mit org.omg.CORBA beginnenden Paketen enthalten sind. Ein solcher ORB basiert auf folgendem Konzept: Wenn eine Anwendungskomponente einen Remote-Dienst verwenden will, muss sie darauf eine Objektreferenz erlangen. Besonders, wenn sich das Remote-Objekt auf einem entfernten Rechner befindet, aber auch über Namensräume auf dem gleichen Rechner hinweg. Solche Objektreferenzen werden als IOR (Interoperable Object References) bezeichnet, wenn sie das IIOP verwenden. In CORBA erfolgt die gesamte Kommunikation zwischen den Objekten über diese IOR. Das hat zur Folge, dass zwischen Objekten nur Referenzen weitergegeben werden (Pass by Reference). Über diese kann die Anwendungskomponente (der CORBA-Client) dann die Methoden und Eigenschaften des entfernten Objekts (des CORBA-Servers) nutzen. Dabei sollte beachtet werden, dass unter CORBA jede Komponente als Server
Java 2 Kompendium
845
Kapitel 16
Weiterführende Themen betrachtet wird, wenn sie CORBA-Objekte enthält, die anderen Objekten Dienste bereitstellen. Analog ist ein CORBA-Client eine Komponente, die auf einen Dienst eines anderen CORBA-Objekts zugreift. Eine CORBA kann also zur gleichen Zeit sowohl als Server als auch als Client agieren, je nach Sichtweise (ein einleitend schon beschriebenes Charakteristikum von verteilten Systemen). Die Hauptaufgabe des ORB ist es nun, die Auflösung der Objektreferenzen über beliebige (!) Verbindungen hinweg zu gewährleisten. Dabei wird es unter Umständen notwendig sein, Parameter eines entfernten Methodenaufrufs in ein Format zu wandeln, das über ein Netzwerk zum Remote-Objekt übertragen werden kann (so genanntes Marshalling) und natürlich auch dessen Rückwandlung (Unmarshalling). Ein Client ruft einfach die gewünschte Remote-Methode auf. Für den Client erscheint sie als lokale Methode. Dies gewährleistet, dass die gesamte Formatübertragung und Kommunikation plattformunabhängig stattfindet. Auch dies gehört zu den Leistungen, die ein ORB bietet. Verschiedene ORBs auf verteilten Plattformen (das können physikalisch getrennte Rechner oder auch nur verschiedene Shells auf einem Rechner sein) kommunizieren nun auf hoher Ebene (etwa auf TCP/IP aufsetzend) von Netzwerkprotokollen über das GIOP (General Inter-ORB Protocol). Es handelt sich aber wie gesagt um ein sehr allgemeines Protokoll, das durch andere Protokolle ergänzt wird. Wo ist nun der Unterschied zwischen RMI und IDL bzw. CORBA genau zu sehen? Während RMI eine reine Java-Lösung darstellt, ist CORBA eine von der Java-Sprache vollkommen losgelöste Lösung für verteilte Strukturen. IDL ist dazu eine neutrale Beschreibungssprache. CORBA ist insbesondere sehr robust und kann zusammen mit Java eingesetzt werden, muss es aber nicht. Die OMG verabschiedete vor einiger Zeit ein IDL/Java-Mapping, das die Abbildung der Interface Definition Language (IDL) auf die Java-Sprache definiert. Die sprachunabhängige Definition von Komponenten und die plattformübergreifende Kommunikation ist in einer unternehmensweiten Architektur ein unbedingtes Muss. CORBA ist zurzeit die einzige Architektur, die beide Anforderungen erfüllt.
16.3.2
Entwicklung einer CORBA-Anwendung in Java
Die Entwicklung einer CORBA-Applikation, die auf Java basiert, kann in einige immer wiederkehrende Schritte gegliedert werden. Generierung der IDL-Schnittstelle Der erste Schritt besteht darin, eine oder mehrere IDL-Schnittstelle(n) zu generieren, etwa so wie in dem nachstehenden einfachen Beispiel (Datei-
846
Java 2 Kompendium
Verteilte Systeme – RMI, IDL und CORBA
Kapitel 16
name Hallo.idl) mit der Definition einer Schnittstelle. Diese stellt eine Methode bereit, die einen String zurückgibt: module HalloApp { interface Hallo { string sagHallo(); }; };
Listing 16.3: Ein einfaches IDLModul mit der Definition einer Schnittstelle, die eine Methode bereitstellt
Anwendung eines IDL-Compilers Im zweiten Schritt wird der IDL-Quelltext mit einem IDL-Compiler übersetzt. Das SDK 2 stellt im Rahmen des JDK 1.3 einen solchen IDL-Compiler bereit. Alternativ können Sie IDL-Compiler wie VisiBroker oder jeden anderen geeigneten verwenden (auch von älteren JDK-Versionen, sofern es nicht zu Inkompatibilitäten in den resultierenden Dateien kommt). Wenn Sie den IDL-Compiler des SDK 2 idlj wie folgt verwenden, werden Ihnen nach der Anwendung unter anderem dann vorgefertigte Client-Stubs und Server-Skeletons als Schablonen bereitstehen: idlj -fclient -fserver Hallo.idl
Die Angabe von Optionen ist in dem IDL-Compiler des JDK 1.3 nicht unbedingt notwendig. Ohne diese Optionen werden keine Skeletons erstellt. Beachten Sie aber, dass sowohl die Online-Dokumentation als auch die direkte Hilfe zu dem IDL-Compiler des SDK Inkonsistenzen aufweisen und diese Optionen nicht unbedingt angeben. Wenn der Aufruf auch ohne die Optionen funktioniert und alle nachstehenden Dateien erzeugt werden – umso besser. Beachten Sie, dass das IDL-Konvertierungtool im SDK lange Zeit idltojava hieß und in diversen Quellen immer noch so geführt wird. Auch Namen wie idl2java oder ähnliche Namen, die mit idl beginnen, sind gebräuchlich, wenn es IDL-Compiler sind, die nicht zum JDK gehören. Die konkrete Anwendung ist natürlich nicht zwingend identisch mit idlj und muss der jeweiligen Dokumentation entnommen werden. Allgemein gilt aber, dass die Arbeit der IDL-Compiler nicht immer einwandfrei ist, die Dokumentationen nicht ganz fehlerfrei sind und es zu Komplikationen kommen kann. Ein Client-Stub ist ein Java-Quelltextteil, der einem Client erlaubt, auf eine Server-Komponente zuzugreifen. Dieser Quelltextteil wird zusammen mit dem eigentlichen Client-Anteil einer Anwendung kompiliert. Stubs dienen dazu, den ORB des Clients anzuweisen, die Formatübertragung für abgehende und ankommende Parameter durchzuführen. Analog handelt es sich beim Server-Skeleton um einen Quelltextteil um das Gegenstück auf Server-
Java 2 Kompendium
847
Kapitel 16
Weiterführende Themen seite, das einem Client Funktionalität bereitstellt. Sie bilden das Gerüst eines Servers und übergeben ankommende Parameter an den von dem Entwickler geschriebenen Implementierungsquelltext. Zusätzlich werden abgeleitete Parameter wieder an den Client zurückgeleitet. Aus der oben aufgeführten IDL-Quelltextdatei wird ein Unterverzeichnis mit dem Namen des IDL-Moduls (nicht dem Namen der IDL-Datei – eine weitere Verwandtschaft zu Java) generiert (also HalloApp). Dieses enthält nach der Kompilierung die folgenden Dateien: Hallo.java HalloHelper.java HalloHolder.java HalloOperations.java _HalloImplBase.java _HalloStub.java
Bei diesen Java-Dateien handelt es sich um besagte Stubs und Skeletons sowie einige Hilfsklassen: Die Datei _HalloImplBase.java beinhaltet eine von der Klasse org.omg.CORBA.portable.ObjectImpl abgeleitete abstrakte Klasse, die das Server-Skeleton darstellt. Die Klasse implementiert das Hallo.javaInterface und die Standard-CORBA-Schnittstelle org.omg.CORBA. portable.InvokeHandler. Die nachfolgend noch zu erstellende Serverklasse HalloServant erweitert dann _HalloImplBase. Datei _HalloStub.java beinhaltet eine von der Klasse org.omg.CORBA.portable.ObjectImpl abgeleitete Klasse, die CORBA-
Die
Funktionalität für den Client beinhaltet. Sie implementiert das Hallo.java-Interface. Die Datei Hallo.java ist eine Schnittstelle, die die Java-Version von dem IDL-Interface beinhaltet. Sie ist von HalloOperations, org.omg.CORBA.Object und org.omg.CORBA.portable.IDLEntity abgeleitet. In älteren Versionen des JDK beinhaltete diese Schnittstelle direkt die Methode sagHallo(). Diese Deklaration ist im aktuellen SDK 2 in die Datei HalloOperations.java verlagert, die – wie oben angegeben – implementiert wird. Die Datei HalloHelper.java beinhaltet eine finale Klasse mit Hilfsfunktionalitäten – etwa zum Casten von CORBA-Objekten. Die Datei HalloHolder.java beinhaltet eine weitere finale Klasse mit einem öffentlichen Instanzmember vom Typ Hallo. Sie dient der Unterstützung von Ein- und Ausgabeargumenten, die nicht exakt zwischen CORBA und Java übereinstimmen. Die Klasse implementiert org.omg.CORBA.portable.Streamable.
848
Java 2 Kompendium
Verteilte Systeme – RMI, IDL und CORBA
Kapitel 16
Die
Datei HalloOperations.java beinhaltet das Interface HalloOperations, das nur die Methode String sagHallo() bereitstellt. Nun ist alles vorbereitet, um konkret die Implementierung des Servers und des Clients vorzunehmen. Wir wollen die Dateien HalloServer.java und HalloClient.java nennen und halten uns an die Beispiele, wie sie Sun auch ähnlich in der Online-Dokumentation bzw. als Demo bereitstellt. Die JavaDateien werden wie die automatisch generierten Dateien in das Verzeichnis HalloApp gestellt. Implementierung des Servers Der Server soll zwei Klassen besitzen, zum einen HalloServant, die die abstrakte Klasse _HalloImplBase vervollständigt. Die eigentliche Server-Klasse HalloServer beinhaltet die main()-Methode, die Folgendes leisten soll: Eine ORB-Instanz kreieren. Eine Instanz von HalloServant kreieren und beim ORB anmelden. Eine CORBA-Objektreferenz entgegennehmen für einen benannten Kontext, in welchem das neue CORBA-Objekt registriert wird. Das neue Objekt registieren in dem benannten Kontext unter dem Namen »Hallo«. Auf Aufrufe des neuen Objekts warten. package HalloApp; import HalloApp.*; import org.omg.CosNaming.*; import org.omg.CosNaming.NamingContextPackage.*; import org.omg.CORBA.*; class HalloServant extends _HalloImplBase { public String sagHallo() { return "\nIch tue nie, was mir befohlen wird!\n"; } } public class HalloServer { public static void main(String args[]) { try{ // Kreieren und Initialisieren des ORB ORB orb = ORB.init(args, null); // Kreieren des Servants und Registrieren beim // ORB HalloServant halloRef = new HalloServant(); orb.connect(halloRef); // Erhalten des Rootnamingcontext org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService");
Java 2 Kompendium
Listing 16.4: Die Server-Implementierung
849
Kapitel 16
Weiterführende Themen NamingContext ncRef = NamingContextHelper.narrow(objRef); // Binden der Objekt-Referenz NameComponent nc = new NameComponent("Hallo", ""); NameComponent path[] = {nc}; ncRef.rebind(path, halloRef); // Auf Aufrufe von Clients warten java.lang.Object sync = new java.lang.Object(); synchronized (sync) { sync.wait(); } } catch (Exception e) { System.err.println("ERROR: " + e); e.printStackTrace(System.out); } } }
Implementierung des Clients Die Implementing des Clients soll Folgendes leisten: Kreieren eines ORB Erhalten einer Referenz auf den benannten Kontext Suchen nach »Hallo« in den benannten Kontext und entgegennehmen einer Referenz auf das CORBA-Objekt Aufrufen der von dem Objekt bereitgestellten Methode sagHallo() und Ausgabe des Resultats Listing 16.5: Die Client-Implementierung
850
package HalloApp; import HalloApp.*; import org.omg.CosNaming.*; import org.omg.CORBA.*; public class HalloClient { public static void main(String args[]) { try{ // Kreieren und Initialisieren des ORB ORB orb = ORB.init(args, null); // Erhalten des Rootnamingcontext org.omg.CORBA.Object objRef = orb.resolve_initial_references("NameService"); NamingContext ncRef = NamingContextHelper.narrow(objRef); // Erhalten einer Referenz auf den benannten // Kontext NameComponent nc = new NameComponent("Hallo", ""); NameComponent path[] = {nc}; Hallo halloRef = HalloHelper.narrow(ncRef.resolve(path)); // Aufruf der Serverobjekte und Ausgabe des // Resultats String ups = halloRef.sagHallo(); System.out.println(ups); } catch (Exception e) { Java 2 Kompendium
Verteilte Systeme – RMI, IDL und CORBA
Kapitel 16
System.out.println("ERROR : " + e) ; e.printStackTrace(System.out); } } }
Erstellen und Laufenlassen Der abschließende Schritt besteht nun darin, alle Java-Dateien – sowohl die vom IDL-Compiler generierten als auch die selbst erstellten – zu kompilieren. Dies erfolgt von außerhalb des HalloApp-Verzeichnisses (wo sich alle Java-Dateien befinden sollten) mit der folgenden Syntax: javac *.java HalloApp/*.java
Im nächsten Schritt brauchen Sie ein weiteres Tool des JDK: tnameserv. Dieses stellt einen Nameserver bereit, der gestartet werden muss. Dazu müssen Sie einen weitgehend beliebigen Port angegeben (möglichst jenseits von 1024). Um etwa den Nameserver mit Port 2000 zu starten, genügt folgender Aufruf: tnameserv -ORBInitialPort 2000 Abbildung 16.6: Der Nameserver ist gestartet.
Im nächsten Schritt wird der CORBA-Server gestartet. Dies erfolgt beispielsweise in einer zweiten DOS-Box bzw. Shell-Umgebung mit Angabe der Server-Applikation und des Ports. Etwa so, wenn Sie direkt außerhalb des Unterverzeichnisses HalloApp stehen: java HalloApp/HalloServer -ORBInitialPort 2000
Java 2 Kompendium
851
Kapitel 16
Weiterführende Themen
Abbildung 16.7: Der CORBA-Server ist gestartet, obwohl man nicht viel sieht.
Als letzten Schritt lassen wir analog dem CORBA-Server den CORBA-Client laufen, ebenfalls wieder aus einer anderen Shell-Umgebung: java HalloApp/HalloClient -ORBInitialPort 2000 Abbildung 16.8: Manchmal tut der CORBA-Client doch, was ihm gesagt wird.
Das Beispiel ist – wenn es auf einem Rechner, nur durch verschiedene Shells getrennt, gestartet wird – nicht besonders eindrucksvoll. Sie sollten aber bedenken, dass Client und Server auch beliebig in einem Netzwerk verteilt sein können. Diese Netzwerkfähigkeit von Java wollen wir nun näher betrachten.
852
Java 2 Kompendium
Netzwerkzugriffe, Sockets und Servlets
16.4
Kapitel 16
Netzwerkzugriffe, Sockets und Servlets
Durch seine Internet-Orientierung ist es für Java zwingend, Netzwerkfähigkeiten mitzubringen. Damit ist erst einmal nur gemeint, dass es möglich sein muss, mit einem Applet oder einer Java-Applikation über ein Computernetzwerk eine Verbindung zu einem anderen System aufzubauen. Den Fall von beliebig in einem Netzwerk verteilten Objekten, die mittels CORBA oder anderer Standards kommunizieren, haben wir als Spezialfall ja gerade behandelt. Das Thema Netzwerke gehört auf jeden Fall zur hohen Schule der EDV und die Programmierung von Netzwerkprogrammen setzt dem noch eins drauf. Wir werden uns dennoch an das Thema wagen und versuchen, Licht in die Netzwerkwelt zu bringen. Schauen wir uns zunächst Servlets an.
16.4.1
Java-Servlets, JSP und SSI
Java-Servlets gehören zu den wichtigsten Entwicklungen von Java. Grob gesagt geht es darum, in einer Client-Server-Beziehung (z.B. der Zusammenarbeit von Browser und Server im Internet) einige Arbeiten vom Client auf einen in Java geschriebenen Server bzw. ein dort laufendes Java-Modul zu verlagern. Dafür gibt es mehrere Gründe. In traditionellen Client-Server-Beziehungen wird vorwiegend ein so genanntes 2-Tier-System verwendet (Tier = Schicht). Wir haben es bei der Einleitung zu dem Thema angesprochen. Ein Serverprogramm hat dabei hauptsächlich die Aufgabe, die Daten zu speichern und zu verwalten und bei Bedarf dem Clientprogramm zur Verfügung zu stellen. Die eigentliche Anwendung oder zumindest die Benutzeroberfläche ist vom Serverprogramm getrennt und läuft vollständig auf dem Client. Das ist auch sinnvoll so. Individuelle Logik befindet sich so beim Client, allgemeine Funktionalität befindet sich auf dem Server. Allerdings enthalten normale Anwendungen mehr Funktionalität und Logik, als für jeden Client individuell notwendig ist. Viele Bereiche einer Anwendung können zu der allgemeinen Funktionalität gezählt werden. Ebenso gibt es Vorgänge, die nur zentral auf einem Server erfasst werden können (beispielsweise ein Gästebuch für Webseiten). Es erscheint sinnvoll, diese allgemeine Funktionalität ebenfalls auf den Server zu verlagern. Dieses Verfahren wird 3-Tier-Architektur genannt bzw. allgemein MultiTier-Architektur (besonders, wenn es sich um noch mehr Schichten handelt). Schicht 1 ist wie gehabt die Datenschicht. Die neue zweite Schicht ist ein so genannter Applikations-Server und realisiert die allgemeine Funktionalität in dem System. Schicht 3 ist wie bisher die individuelle Client-Funk-
Java 2 Kompendium
853
Kapitel 16
Weiterführende Themen tionalität, allerdings um die allgemeine Funktionalität abgespeckt. Meist reduziert sich die individuelle Funktionalität auf eine reine Präsentationsschicht. Ein solcher in Schicht 2 realisierter Applikations-Server kann als eine Art Verbindungsoffizier zwischen Schicht 1 und 3 interpretiert werden. Er vermittelt zwischen dem Datenbestand und der Präsentation und kann den Datenverkehr in einem Netzwerk erheblich reduzieren. Servlets bieten Web-Entwicklern einen einfachen, einheitlichen Mechanismus, mit dem die Funktionalität eines Webservers erweitert und auf vorhandene Systeme zugegriffen werden kann. Durch Servlets und deren dynamische Möglichkeiten sind viele Web-Anwendungen erst möglich geworden. Servlets und darauf aufbauende Techniken bieten eine komponentenbasierte, plattformunabhängige Technik für die Erstellung von WWWAnwendungen. Damit stehen sie in Konkurrenz zu den vor Java meist verwendeten (und auch heute noch zu findenden) CGI-Anwendungen (Common Gateway Interface – Allgemeine Zugangsschnittstelle) und auch proprietären Server-Erweiterungsmechanismen wie NSAPI (Netscape Server API) oder Apache-Modulen. Servlets sind aber leistungsfähiger als CGIAnwendungen und dennoch explizit server- und plattformunabhängig. Kurzer CGI-Exkurs Da Servlets im Wesentlichen geschaffen wurden, um Vorgänge von einem CGI-Prozess zu optimieren, soll CGI hier kurz erläutert werden. Dessen Grundvorgänge bilden die Basis für den Umgang mit Servlets. CGI ist eine Schnittstelle für einen Client, über die die Übergabe von Parameterwerten an Programme oder Scripte auf einem Server erfolgen kann. Dort werden die übergebenen Werte verarbeitet und bei Bedarf eine Antwort (meist ein HTML-Dokument) an den Client generiert. Festhalten muss man bei CGI immer wieder, dass es sich nicht um eine Programmiersprache handelt, sondern nur um ein Protokoll, dessen wesentliches Charakteristikum die plattformübergreifende Standardisierung ist. CGI-Scripte können in jeder Sprache erstellt werden, die Scripte generieren kann und für die auf dem jeweiligen Server ein passender Interpreter vorhanden ist. Die meistverbreitete Sprache zur Erstellung von CGI-Scripten ist Perl (die Sprachspezifikation ist unter http://www.perl.com zu finden). Die grundlegenden Schritte, die bei der Verwendung eines CGI-Sciptes ablaufen, sind folgende: Aktionen, die auf dem Client ausgelöst werden, müssen ungeprüft zum Server zurückgeschickt werden. Auf dem Server werden sie verarbeitet.
854
Java 2 Kompendium
Netzwerkzugriffe, Sockets und Servlets
Kapitel 16
Anschließend wird das daraus ermittelte Ergebnis als Antwort zum Client zurückgeschickt. Ein typischer CGI-Vorgang lässt sich in vier Stufen einteilen: Übertragung einer Client-Anforderung an den Webserver Start des CGI-Programms auf dem Server Lauf des CGI-Programms auf dem Server Senden der Antwort an den Client Wenn beispielsweise ein HTML-Formular durch den Client abgeschickt wird, findet die Übertragung an den Webserver statt (der so genannte Request). Die meisten CGI-Scripte verwenden für den CGI-Requests das HTTP-Protokoll. Diese HTTP-Request-Header werden i.d.R. als Umgebungsvariablen übertragen. Im Formular kann man über zwei Angaben – der Angabe action für den URL des Webservers samt vollständiger Pfadund CGI-Programmangabe und method für die Art und Weise der Datenübertragung – den genauen Ablauf des Vorgangs spezifizieren. Bevor die Daten an den Server gesendet werden, werden sie zunächst vom Browser zu einer einzigen Zeichenkette verpackt. Die Art der Datenübertragung wird durch den Parameter method gesteuert. Dabei wird grundsätzlich zwischen GET und POST unterschieden. Der Unterschied zwischen den beiden Methoden besteht hauptsächlich darin, wie der Server das zurückgeschickte Formular speichert und verarbeitet. Bei GET wird das zurückgeschickte Formular in der Standard-Umgebungsvariablen QUERY_STRING gespeichert. Das CGI-Programm/Script wertet den Inhalt dieser Umgebungsvariablen aus. Danach wird der restliche Inhalt weiterverarbeitet. POST veranlasst das CGI-Programm, das Formular wie eine auf der Kommandozeilenebene erfolgte Benutzereingabe zu behandeln. Es gibt daher kein EndOfFile-Signal (EOF) und die Länge der übermittelten Daten muss vom CGI-Programm aus einer weiteren Standard-Umgebungsvariable (CONTENT_LENGTH) entnommen werden. Wenn bei einem Formular die Eingaben per GET zum Server geschickt werden sollen, wird aus der im
Listing 16.8: Die Webseite zum Aufruf des Servlets
Die Java-Datei selbst ist ziemlich einfach: public class JspB { private String sample = "Vorbelegung"; // Beispieleigenschaft abfragen public String getSample() { return sample; } // Beispieleigenschaft setzen public void setSample(String newValue) { if (newValue!=null) { sample = newValue; } } }
Listing 16.9: Das JSP-Servlet
Wenn Sie die JSP-Datei aufrufen (das Servlet sollte natürlich im Server lauffähig bereit stehen), erhalten Sie ein Eingabeformular. Abbildung 16.15: Die Webseite zum Entgegennehmen der Benutzereingaben und dem Aufruf des Servlets
Im Eingabeformular können Sie etwas eintragen, dann mit dem Button das Servlet aufrufen und ihm den Wert übermitteln.
Java 2 Kompendium
869
Kapitel 16
Weiterführende Themen
Abbildung 16.16: Das geht an den Server.
Das Servlet generiert eine neue Seite, wo der eingebene Wert als dynamische Information enthalten ist. Abbildung 16.17: Die Antwort des Servlets
16.4.2
Allgemeine Netzwerkzugriffe
Für allgemeine Netzwerkzugriffe steht Java das Paket java.net zur Verfügung. Die darin enthaltenen Klassen erlauben unter Java relativ einfache, plattformübergreifende Vernetzungsoperationen. Darunter fallen das Lesen und Schreiben von Dateien über das Netzwerk oder das Verbinden mit den üblichen Internet-Protokollen. Um die Einschränkungen von Lese- und Schreiboperationen bei Applets wissen Sie bereits (Stichwort Sicherheit), aber ansonsten sind Netzwerkzugriffe mit Java leichter zu realisieren als mit den meisten anderen Sprachen. Man unterscheidet in Java (wie auch in allgemeiner Netzwerktechnik) im Wesentlichen zwischen paketorientierter Kommunikation über das Netzwerk und der verbindungsorientierten Kommunikation. Die erste Variante versendet einfach ein Datenpaket mit der Adresse des Zielrechners (in den meisten Fällen eine IP-Adresse und Port) und kümmert sich nicht weiter darum (weder um den genauen Weg, noch darum, ob es überhaupt ankommt). So etwas findet man meistens bei WAN-Netzwerken (Wide Area Network) wie dem Internet. Die zweite Variante baut eine permanente Verbindung zu einem Zielrechner auf, die im Laufe der Kommunikation immer wieder verwendet wird. Sie wird einmal aufgebaut und dann gehalten, bis die Arbeit abgeschlossen ist. Dies nennt man eine semi-permanente Verbindung.
870
Java 2 Kompendium
Netzwerkzugriffe, Sockets und Servlets
Kapitel 16
Sie finden diese hauptsächlich bei LAN-Netzwerken (Local Area Network), wo sich Betriebsmittel und Leitungen in einer Hand befinden, aber auch bei einer direkten Wahlverbindung zwischen zwei Rechnern. Wenden wir uns zuerst der verbindungsorientierten Kommunikation zu. Die URL-Klassen von Java Internet und Java gehören zusammen. Damit aber auch Java und Netzwerke jeder Art. Java ist von Anfang an als eine Netzwerksprache entworfen worden. Die URL-Klasse bietet einige interessante Möglichkeiten, um eine Internet-Ressource unter Java zu spezifizieren. Die URL-Klasse (URL steht für Uniform Resource Locator) besitzt Konstruktoren und Methoden für die Verwaltung einer URL. Dies können beliebige Objekte oder Dienste im Internet sein. Das TCP-Protokoll, über das Sie normalerweise einen Netzwerkzugriff auf einen Server realisieren werden, benötigt zwei Teile zur Information: die IP-Adresse des Rechners und die Port-Nummer. Holen eines URL-Objektes Die IP-Adresse des Rechners werden Sie meist nicht direkt angeben (und grö ß tenteils auch nicht wissen), sondern statt dessen verwenden Sie den DNS-Namen des Rechners. Dieser Name wird mittels spezieller Namensserver dann automatisch in die IP-Nummer übersetzt, wenn die Information auf der Reise zum Zielserver benötigt wird. Wir sind darauf bei der beschreibung der Funktionsweise des Internets eingegangen. Den Port benötigen Sie nicht immer, denn normalerweise ist Port 80 für den Web-Dienstport reserviert. Wenn Sie also keinen Port angegeben, wird der adressierte Server normalerweise diesen Port defaultmä ß ig verwenden. Es gibt jedoch keine feste Regel, die besagt, dass Port 80 der Web-Port sein muss. Es ist einfach Usus, aber nicht zwingend. Die URL-Klasse berücksichtigt diese möglichen Spezifikations-Variationen. Um eine neue URL zu erstellen, sind diverse Konstruktoren möglich. Nachfolgend drei Beispiele: Konstruktor
Beschreibung
public URL(String url_zeichenkette) throws MalformedURLException;
Dieser Konstruktor erzeugt ein URLObjekt, das in der url_zeichenkette sämtliche notwendigen Informationen für den Zugriff (Protokoll, Host, evtl. Port, Pfad und/oder Dateiname) enthalten sollte.
Java 2 Kompendium
Tabelle 16.3: URL-Konstruktoren
871
Kapitel 16 Tabelle 16.3: URL-Konstruktoren (Forts.)
Weiterführende Themen
Konstruktor
Beschreibung
public URL(String protokoll, String host, int port, String Datei) throws MalformedURLException;
Dieser Konstruktor erzeugt ein URLObjekt, das sämtliche notwendigen Informationen für den Zugriff (Protokoll, Host, Port, Pfad und/oder Dateiname) als einzelne Parameter benötigt.
public URL(String protokoll, String host, String Datei) throws MalformedURLException;
Dieser Konstruktor erzeugt ein URLObjekt, das für den Zugriff außer dem Port sämtliche notwendigen Informationen (Protokoll, Host, Pfad und/oder Dateiname) als einzelne Parameter benötigt.
Die ausgeworfene Ausnahme sollten Sie jedes Mal gezielt abfangen (trycatch-Block), denn das Holen eines URL-Objekts ist ein sehr instabiles Unterfangen. Dazu können Sie folgenden Code verwenden (Syntax mit dem ersten Konstruktor): String url = ... // irgendeine Url try { meinUrl = new URL (url); } catch(MalformedURLException e) { // tue etwas sinnvolles }
Anzeigen eines URL-Objekts in einem Browser Um ein URL-Objekt aus einem Applet an einen Browser weiterzugeben, brauchen Sie nur eine einzige Zeile Code: getAppletContext.showDocument(meineURL);
Die Methoden showDocument(meineURL) bzw. showDocument(meinURL, String) gehören zum Interface java.applet.AppletContext und zeigen in einem Browser den URL an. Testen wir das in einem kleinen Beispiel: Listing 16.10: Das Applet lädt unmittelbar die angegebene URL.
872
import java.net.URL; import java.net.MalformedURLException; public class Netz1 extends java.applet.Applet { URL homepage; public void init() { try { homepage=new URL("http://localhost/"); } catch (MalformedURLException e) {
Java 2 Kompendium
Netzwerkzugriffe, Sockets und Servlets
Kapitel 16
System.out.println( "Nix gibt's:" + e.toString()); } getAppletContext().showDocument(homepage); }}
Das Applet lädt bei der Initialisierung die Startpage des lokalen Hosts (in unserem Fall) und zeigt sie direkt statt des Applets an. Eingebunden wird das Applet wie üblich: <APPLET code="Netz1.class" height=200 width=200>
Listing 16.11: Die Webseite zum Aufruf des Applets
Öffnen von Webverbindungen Statt ein URL-Objekt nur anzuzeigen, kann man es auch direkt in einem Applet verwenden. Sicherheitsgründe begrenzen jedoch diese Form von Netzwerkverbindungen. Ein Applet kann normalerweise nur eine Verbindung zu dem Host aufbauen, von dem es ursprünglich geladen wurde (falls der Browser es überhaupt kommunizieren lässt). Sicherheitslücken und zu freizügige Einstellungen des Browsers seien bei dieser Aussage außer Acht gelassen. Die Klasse URL definiert eine Methode public final InputStream openStream() throws IOException, die eine Netzwerkverbindung mit einer bestimmten URL öffnet und eine Instanz der Klasse InputStream ausgibt. Wir haben in dem Kapitel über die Ein- und Ausgabe in Java gesehen, dass man einen solchen Strom in einen DataInputStream konvertieren kann. Damit können Sie die Zeichen aus dem Strom auf vielfältige Weise lesen und anschließend verarbeiten. Wenn Sie beispielsweise wie oben eine URL in meineURL gespeichert haben, können Sie folgenden Code verwenden, um zeilenweise die Zeichen zu verarbeiten: try { InputStream eingabe = meineURL.openStream(); DataInputStream data_wert = new DataInputStream(new BufferedInputStream(eingabe)); String zeile; while ((zeile = data_wert.readLine()) != null) { // tue etwas mit den Zeilen } } catch (IOException e);{ // tue etwas sinnvolles }
Java 2 Kompendium
873
Kapitel 16
Weiterführende Themen Die URLconnection-Klasse Bei der openStream()-Methode handelt es sich um eine vereinfachte Variante einer Methode aus der allgemeineren URLconnection-Klasse. Diese abstrakte Klasse enthält diverse Möglichkeiten, Dateien anhand beliebiger URLs aus dem Netz zu laden und bietet eine weitaus grö ß ere Flexibilität zur Handhabung von URL-Verbindungen. Sie ist die Superklasse für alle Klassen, die eine Verbindung zwischen einer Anwendung und einer URL repräsentieren. Über diese Klasse können Verbindungen sowohl zum Lesen als auch zum Schreiben über diesen Kanal aufgebaut werden.
16.4.3
Sockets
Die Klassen URL und URLconnection bieten nur relativ eingeschränkte Möglichkeiten für Netzwerkverbindungen. Für die meisten Fälle werden sie zwar ausreichen, aber es gibt genügend Anwendungen für weitergehende Funktionalitäten, etwa andere Protokolle. Dafür stellt Java neben den schon angesprochenen Techniken für verteilte Anwendungen und Servlet-Kontakte die Klassen Socket und ServerSocket zur Arbeit mit Sockets zur Verfügung. Die Klasse Socket ist für die Erstellung und Verwaltung von Sockets auf der Clientseite und die Klasse ServerSocket für die Erstellung und Verwaltung von Sockets auf der Serverseite zuständig. Java besitzt die gleichen UNIX-Wurzeln wie das Internet. Die von UNIXSockets abgeleitete Klasse Socket stellt ein Grundobjekt in der InternetKommunikation dar, das das TCP-Protokoll unterstützt. Die Socket-Klasse verfügt über diverse Datenstrommethoden zur Ein- und Ausgabe, was das Auslesen und Schreiben in die Socket-Klasse sehr vereinfacht. Um eine Verbindung über die Socket-Klasse aufzubauen, erstellen Sie eine Instanz von der Klasse. Dies geschieht mit der folgenden Syntax: Socket verbindung = new Socket(hostname, portnummer);
Dabei ist hostname durch den tatsächlichen Namen des Hosts und portnummer durch die Nummer des Ports zu ersetzen. Dabei ist die Klasse InetAddress aus dem Paket java.net sehr nützlich. Ein aus dieser Klasse erstelltes Objekt enthält sowohl den symbolischen DNS-Namen als auch die IP-Adresse des jeweiligen Rechners. Und einige sehr nützliche Methoden: Die Methode public String getHostName() liefert den Namen des Hosts für diese IP-Addresse. Wenn ein Securitymanager aktiv ist, wird eine SecurityException ausgeworfen, wenn die Abfrage nicht erlaubt ist. Dazu wird die Methode SecurityManager.checkConnect(java.lang.String, int) genutzt. 874
Java 2 Kompendium
Netzwerkzugriffe, Sockets und Servlets
Kapitel 16
Die Methode public byte[] getAddress() liefert die IP-Addresse des InetAddress-Objekts als byte-Array. Die Methode public String getHostAddress() liefert die IP-Adresse als String in der Form »%d.%d.%d.%d«. Die Methode public static InetAddress getByName(String host) throws UnknownHostException bestimmt die IP-Addresse eines Hosts, wenn der Name des Hosts gegegeben ist. Dieser Hostname kann in der From www.rjs.de oder als eine String-Repräsentation der IP-Addresse angegeben werden. UnknownHostException wird ausgeworfen, wenn der Host
nicht gefunden werden kann. Die Methode public static InetAddress[] getAllByName(String host) throws UnknownHostException liefert alle IP-Addressen von einem Host als Array, wenn der Name des Hosts gegeben ist. Dieser Hostname kann in der From www.webscripting.de oder als eine String-Repräsentation der IP-Addresse angegeben werden. UnknownHostException wird ausgeworfen, wenn der Host nicht gefunden werden kann. Wenn ein Securitymanager aktiv ist, wird eine SecurityException ausgeworfen, wenn die Abfrage nicht erlaubt ist. Dazu wird die Methode SecurityManager.checkConnect(java.lang.String, int) genutzt. UnknownHostException wird ausgeworfen, wenn für den Host keine IP-Adresse gefunden werden kann. Besonders interessant ist die Methode public static InetAddress getLocalHost() throws UnknownHostException, die den lokalen Host in Form seiner IP-Adresse zurückliefert. Wenn ein Securitymanager aktiv ist, wird eine SecurityException ausgeworfen, wenn die Abfrage nicht erlaubt ist. UnknownHostException wird ausgeworfen, wenn der Host nicht gefunden werden kann. Sobald die Verbindung geöffnet ist, können Sie wie üblich mit Ein- und Ausgabeströmen arbeiten. Die Eingabe könnte so realisiert werden: DataInputStream eingabe = new DataInputStream(new BufferedInputStream(verbindung.getInputStream()));
Die Ausgabe könnte analog laufen: DataOutputStream ausgabe = new DataOutputStream(new BufferedOutputStream(verbindung.getOutputStream()));
Sehr sinnvoll sind in diesem Zusammenhang serialisierte Objekte, die zwischen Socket und ServerSocket hin- und hergesendet werden. Wir werden das im nachfolgenden Beispiel tun. Ein Socket sollte geschlossen werden, wenn alle Arbeiten erledigt sind. Dazu dient die close()-Methode. Java 2 Kompendium
875
Kapitel 16
Weiterführende Themen Beispiel: verbindung.close(); Die Klasse ServerSocket kümmert sich um Client-Anforderungen, wobei ServerSocket diesen Service nicht selbst ausführt, sondern ein Socket-Objekt im Auftrag des Clients erstellt. Die Kommunikation wird dann über dieses Objekt ausgeführt. Man nennt diese Objekte serverseitige Sockets. Ein ServerSocket richtet sich nach einem TCP-Port, um eine Client-Verbindung aufzubauen. Wenn sich nun ein Client an diesen Port anschließt, muss diese Verbindung mittels einer eigenen Methode – der accept()-Methode – akzeptiert werden. Wenn Sie diese Methode im Server notiert haben, wird der Server dort auf eine Verbindung zu einem Client warten. Um ein ServerSocket zu erstellen, gehen Sie ähnlich vor wie bei den clientseitigen Sockets. Als Erstes erstellen Sie eine Instanz von der Klasse. Dies geschieht mit der folgenden Syntax: ServerSocket verbindung = new ServerSocket(portnummer);
Dabei ist portnummer durch die Nummer des Ports zu ersetzen. Sie haben damit einen Port festgelegt, den Sie dann für Client-Anfragen mit der accept()-Methode freigeben können. Beispiel: verbindung.accept(); Danach können Sie wieder die Ein- und Ausgabeströme verwenden, um mit dem Client über Lese- und Schreibvorgänge zu kommunizieren. Spielen wir den Vorgang in einem Beispiel durch, wobei wir hier mit serialisierten Daten arbeiten. Zuerst erstellen wir den Quelltext für den Server. Listing 16.12: Der ServerSocketPart
876
import java.io.*; import java.net.*; import java.util.*; public class MeinServer { // Serversocket erstellen und den Strom zum // Empfang von serialisierten Objekten verwenden public static void main(String args[]) { ServerSocket meinServerSocket = null; Socket meinSocket = null; String str1 = null; String str2 = null; try { meinServerSocket = new ServerSocket(1234); System.out.println("Server ist gestartet und wartet auf einen Client."); // Warten auf eine Verbindung zu diesem // Socket meinSocket = meinServerSocket.accept(); // Eingabestrom von serialisierten Objekten Java 2 Kompendium
Netzwerkzugriffe, Sockets und Servlets
Kapitel 16
// entgegennehmen InputStream o = meinSocket.getInputStream(); ObjectInput s = new ObjectInputStream(o); str1 = (String) s.readObject(); str2 = (String) s.readObject(); s.close(); // Ausgabe der vom Client übergebenen Werte System.out.println(str1); System.out.println(str2); } catch (Exception e) { System.out.println("Nix is: " + e.getMessage()); System.exit(1); } }}
Der Client sieht so aus: import java.io.*; import java.net.*; import java.util.*; public class MeinClient { public static void main(String args[]) { try { // Socket erstellen Socket meinSocket = new Socket(InetAddress.getLocalHost(), 1234); // Outputstream erstellen, mit Socket // verbinden und serialisieren OutputStream o = meinSocket.getOutputStream(); ObjectOutput s = new ObjectOutputStream(o); System.out.println( "Ab geht es an den Server."); s.writeObject( "Server, das sagt dir dein Client:"); s.writeObject(args[0]); s.flush(); s.close(); System.out.println("Das war's. Und ciao."); } catch (Exception e) { System.out.println("Nix is: " + e.getMessage()); System.exit(1); } }}
Java 2 Kompendium
Listing 16.13: Der Client-Part
877
Kapitel 16
Weiterführende Themen Starten Sie zuerst den Server und dann in einer eigenen DOS-Box bzw. einer zusätzlichen Shell das Clientprogramm. Der Server meldet sich mit einer kurzen Ausgabe und wartet dann auf eine Verbindung zum Client. Der Client übergibt die serialisierten Strings an den Server, der diese dann in seiner Shell ausgibt. Sollte das Beispiel nicht funktionieren, wird es unter Umständen am Port liegen. Probieren Sie einen anderen Port aus.
Abbildung 16.18: Socket-Kommunikation über zwei Shells hinweg
Wenn ein Server mehrere Clients bedienen soll, muss er sinnvollerweise Multithreading-fähig sein. Ein Thread wartet dabei permanent auf Verbindungen. Datagram-Sockets Kommen wir nun zu einer paketorientierten Verbindungsart. Über DatagramSockets zu kommunizieren ist einfacher als über die Klassen Socket und ServerSocket, die auf TCP basierende Sockets sind. Die Kommunikation ist sogar schneller, da der Verbindungsaufwand bedeutend geringer ist. Das TCP-Protokoll ist ein sehr sicheres Protokoll. So versucht es beispielsweise, Pakete nochmals zu versenden, wenn ein Fehler auftritt. Die darauf aufsetzenden Sockets können und müssen diese Fähigkeiten auch nutzen, was zwar sicher ist, jedoch zu Lasten der Geschwindigkeit geht. Ein Datagram-Paket dagegen wird einfach als eine Ansammlung von Bytes an ein empfangendes Programm gesendet. Das empfangende Programm wartet 878
Java 2 Kompendium
Netzwerkzugriffe, Sockets und Servlets
Kapitel 16
in der Regel an einer bestimmten IP-Adresse und einem zugehörigen Port. Man nennt diese Kommunikation über Datagramme UDP (Unreliable Datagram Protocol): Das Empfängerprogramm wird zum Sender, wenn es dem sendenden Programm eine Antwort schicken möchte. Dazu wird einfach an die aus dem empfangenen Sendevorgang bekannte IP- und Portadresse des ursprünglichen Senders zurückgeschickt. Das Verfahren ist aus der Funktechnik bekannt. Eine Station wird abwechselnd zum Sender und Empfänger. Datagram-Sockets sind dann zur Kommunikation sinnvoll, wenn es auf
Geschwindigkeit und/oder geringe Übertragungsmengen ankommt und die Übertragungssicherheit nicht so wichtig oder anderweitig gewährleistet ist. Letzteres ist z.B. dann gegeben, wenn die Kommunikation nur lokal stattfindet oder die Leitungen störungsfrei und sicher sind. Versenden eines Datagram-Pakets Um ein Datagram-Paket zu versenden, erstellen Sie zuerst eine Instanz von der Klasse. Dies geschieht beispielsweise mit der folgenden Syntax: DatagramPacket meinPacket = new DatagramPacket( byte_array, nachrichten_laenge, internet_addresse, port);
Die Angabe byte_array ist ein byte-Array, das die versendete Nachricht beinhaltet. Die Angabe nachrichten_laenge ist ein Integerwert, der die Länge der Nachricht angibt. Die Angabe internet_addresse beinhaltet die IP-Adresse, wo die Nachricht hin gesandt werden soll. Die Angabe port ist wieder ein Integerwert, der die Port-Nummer spezifiziert. Anschließend erstellen Sie eine Instanz von DatagramSocket: DatagramSocket meinSocket = new DatagramSocket();
Mit der send()-Methode können Sie nun das gerade erstellte Paket versenden: meinSocket.send(meinPacket);
Das Socket sollte wieder geschlossen werden, wenn alle Arbeiten erledigt sind. Dazu dient wieder die close()-Methode: meinSocket.close(); Java 2 Kompendium
879
Kapitel 16
Weiterführende Themen Empfangen eines Datagram-Pakets Das Datagram-Paket ist nun unterwegs zum Ziel und soll dort empfangen werden. Um ein Datagram-Paket zu empfangen, gehen Sie ziemlich ähnlich zum Senden vor. Sie erstellen zuerst eine Instanz von der Klasse DatagramPacket, allerdings mit einem etwas veränderten Konstruktor. Dies geschieht nun beispielsweise mit der folgenden Syntax: DatagramPacket empfangPacket = new DatagramPacket(buffer, buffer.length );
Die Angabe buffer ist wieder ein byte-Array, das die Nachricht nach dem Empfang beinhalten soll, danach folgt die Länge des Arrays. Anschließend erstellen Sie eine Instanz von DatagramSocket: DatagramSocket empfangSocket = new DatagramSocket(port);
Die Angabe port ist ein Integerwert, der durch den Port zu ersetzen ist, den der Sender verwendet. Mit der receive()-Methode können Sie nun das spezifizierte Paket empfangen: empfangSocket.receive(empfangPacket); Das Socket sollte über die close()-Methode wieder geschlossen werden, wenn alle Arbeiten erledigt sind.
16.5
Datenbanken und JDBC
Die netzwerkorientierte Struktur von Java macht die Sprache zu einem idealen Kandidaten für Client-Server-Einsätze. Das 3-Tier-Schichtmodell und Servlets haben dies ja schon angedeutet. Client-Server-Datenbanken sind ein sehr wichtiges Beispiel in der Praxis. Java wird als plattformunabhängiger Client für Datenbanken eingesetzt, auf die über das Netzwerk zugegriffen werden kann. Wegen der Plattformunabhängigkeit muss man sich keine großen Gedanken über plattformspezifische Fragestellungen machen. Für den Zugang zu Datenbanken von außen bietet Java eine einfache Möglichkeit – die Java DataBase Connectivity (JDBC). Diese Schnittstelle erlaubt es Entwicklern von Datenbankanwendungen, datenbankunabhängige Java-Clients zu schreiben, die auf zahlreiche verbreitete relationale Datenbanken zugreifen können. Auf Grund des modularen Aufbaus der JDBC-Spezifikation können aufgabenbezogene Erweiterungen hinzugefügt und für die Datenverarbeitung notwendige Tools jederzeit integriert werden. Diese können über den JDBCLayer mit den erforderlichen Datenbanken kommunizieren. Die JDBC-Trei880
Java 2 Kompendium
Datenbanken und JDBC
Kapitel 16
ber übernehmen dabei die gesamte Datenbankanbindung. Sofern sich die Datenbankentwickler an die normale Standard SQL-Syntax (SQL = Structured Query Language) halten, sollte jedes Datenbankprodukt mit einem JDBC-kompatiblen Treiber verwendet werden können. JDBC ist nicht als Produkt zu verstehen, sondern es handelt sich um die abstrakte Spezifikation einer Schnittstelle zwischen einer Client-Anwendung und einer SQL-Schnittstelle. Das Interface ist als Low-Level-API zum grundlegenden SQL-Zugriff entworfen worden. Es liegt an den verschiedenen Herstellern von Datenbanken und Software, ob JDBC-kompatible Treiber eingebaut werden, damit Java-Anwendungen mit den Datenbanksystemen verbunden werden können.
16.5.1
Was sind relationale Datenbanken?
Die Grundlagen von Datenbanken dürften nicht jedem Leser ein Begriff sein. Aber auch einige Leser mit Vorkenntnissen in Programmierung werden bisher noch keine Erfahrung mit der Erstellung und dem Zugriff auf Datenbanken aus einer eigenen Applikation heraus gemacht haben. Das Thema Datenbankprogrammierung ist eher im Bereich der professionellen Software-Entwicklung angesiedelt. JDBC gibt Ihnen die Möglichkeit, aus Java relativ einfach auf relationale Datenbanken zuzugreifen. Wir wollen deshalb in einem kleinen Exkurs erst einmal klären, was sich hinter dem Konzept einer relationalen Datenbank verbirgt. Im Allgemeinen enthalten Datenbanken Informationen, die in einer bestimmten Weise angeordnet sind und die auf Grund von bestimmten Abfragen aufbereitet und ausgegeben werden können. Eine Datenbank kann in tabellarischer Form vorliegen, aber ebenso komplexer geordnet sein. Man unterscheidet vom Konzept her drei Haupttypen von Datenbanken: Hierarchisch strukturierte Datenbanken Relational aufgebaute Datenbanken Netzwerkdatenbanken Historisch das älteste System ist die hierarchisch strukturierte Datenbank. In der Computersteinzeit (70er- bis 80er-Jahre) war das System sehr populär. Dabei werden Daten als ein Baumsystem mit Datensammlungen behandelt, die als Äste dargestellt werden. In einem hierarchischen Schema müssen Zugriffe auf die Daten entlang der Baumstruktur verlaufen. Die üblichste Beziehung in einer hierarchischen Struktur ist eine »Eins zu Vielen-Beziehung« zwischen den Datensätzen. Eine »Viele-zu-Viele-Beziehung« ist ohne erhebliche Redundanz (Überschneidungen von Daten ohne zusätzlichen Informationsgehalt) nicht zu bewerkstelligen.
Java 2 Kompendium
881
Kapitel 16
Weiterführende Themen Aber klären wir erst einmal, was das mit »Eins zu Vielen-Beziehung« bzw. »Viele-zu-Viele-Beziehung« auf sich hat. Es gibt drei Arten von Beziehungen zwischen Datensätzen: Eins zu Eins: Ein Datensatz in einer Tabelle ist mit einem Datensatz in einer anderen Tabelle verbunden. Eins zu Vielen: Ein Datensatz in einer Tabelle kann mit vielen Datensätzen in einer anderen Tabelle verknüpft sein. Viele zu Vielen: Ein Datensatz in einer Tabelle kann genau wie bei einer »Eins-zu-Vielen-Beziehung« mit vielen Datensätzen in einer anderen Tabelle verknüpft sein. Daneben können aber auch weitere Datensätze in dem Beziehungsgeflecht solche »Eins-zu-Vielen-Beziehung« mit vielen Datensätzen in einer anderen Tabelle haben. Auf das hierarchische Schema folgten irgendwann die Netzwerk-Datenbanken. Das Netzwerk-Datenmodell entspricht einer »Viele-zu-Vielen-Beziehung« zwischen den Datenelementen. Man versucht sich den Unterschied zu dem hierarchischen Schema so zu verdeutlichen, indem man das hierarchische Schema mit einer Eltern-Kind-Beziehung vergleicht, während das Netzwerkschema eine Gleichgestellten-Beziehung repräsentiert. In den 90ern entstand das relationale Datenzugriffskonzept. Das relationale Schema betrachtet die Daten als eine Tabelle mit Zeilen und Spalten. Die Zeilen (auch Records genannt) stellen die Datensätze der Tabelle dar. Jede Zeile unterteilt sich entsprechend der Spalten in der Tabelle in Felder. Die Felder enthalten die eigentlichen Daten. Das Hauptkonzept beim relationalen Schema ist, dass die Daten einheitlich sind. Jede Zeile einer Tabelle enthält die gleiche Anzahl an Spalten. Viele solcher Tabellen (die sich in ihrer Struktur unterscheiden können) bilden eine Datenbank. Die Relationen zwischen verschiedenen Tabellen einer Datenbank sind der Namensgeber für das Konzept. Um zwei Tabellen miteinander zu verbinden, müssen die zwei Tabellen mindestens eine gemeinsame Spalte haben. Diese gemeinsame Spalte enthält die Information, die für einen Datensatz der einen Tabelle einen Zugriff auf die zugehörigen Informationen aus der anderen Tabelle erlaubt. Auf diesem Weg entsteht eine Eins zu Vielen-Beziehung. Die gemeinsamen Felder nennt man Schlüssel. Wenn man diesen Schlüssel mit Zuordnungsinformationen in einer separaten Tabelle verwaltet, spricht man von einer Index-sequenziellen Datenbank. Ein anderer Weg führt über eine dritte Tabelle mit zwei Spalten, eine für ein Schlüsselfeld der ersten Tabelle und die zweite entsprechend für die zweite Tabelle. Über diese beiden Spalten werden die Datensätze der beiden Tabelle einander zugeordnet, wodurch eine Relation entsteht. Dies ist dann eine Viele zu Vielen-Beziehung.
882
Java 2 Kompendium
Datenbanken und JDBC
16.5.2
Kapitel 16
Was ist SQL?
SQL (Structured Query Language) ist eine universelle Datenbanksprache, die Aktionen auf relationalen Datenbanken ermöglicht. Unter solche Aktionen fallen das Erzeugen (create), Aktualisieren (update), Einfügen (insert) und Löschen (delete) von Daten oder Datendefinitionen für die Erzeugung von Tabellen und Spalten. Des Weiteren gibt es Möglichkeiten, den Zugriff auf Datenelemente zu beschränken und Anwender und Gruppen zu erzeugen. Weitere Bestandteile der Sprache betreffen das allgemeine Datenmanagement, Backup-Verfahren, das Kopieren und Aktualisieren von umfangreichen Datensätzen und Transaktionsverarbeitung (SQL-Statements, die Datenreihen und -felder in eine Datenbank löschen, aktualisieren oder hinzufügen). Nahezu jeder Datenbankhersteller stellt über eine eigene Implementation von SQL sicher, dass man mit SQL auf seine Datenbank zugreifen kann.
16.5.3
JDBC versus ODBC
Einer der ersten JDBC-kompatiblen Treiber ist der JDBC-Treiber für ODBC-kompatible Datenbanken, mit dem Java-Programmierer leicht auf eine beliebige ODBC-kompatible Datenbank zugreifen können. Microsofts ODBC (Open Database Connectivity) basiert auf dem gleichen Konzept wie JDBC – dem X/Open SQL CLI (Call Level Interface). Die JDBC-Spezifikation ist der ODBC-Spezifikation von Microsoft zwar sehr ähnlich, ist aber – als von Sun entwickelte Schnittstelle – natürlich besser auf die Zusammenarbeit mit Java ausgelegt. Zusätzlich können viele der Mängel von ODBC – wie zu viele Zeichenketten, Verarbeitung von sehr großen binary-Objekten und falsch verweisende Pointer – mit Java leichter gehandhabt werden oder treten auf Grund der Art, wie Java unbekannte Datentypen behandelt, gar nicht erst auf. Die Hauptunterschiede zwischen JDBC und ODBC liegen darin, wie Daten zwischen dem Aufrufer und dem Treiber hin- und hergeschickt werden. ODBC ist ein C-basiertes API. Daher verwendet es das »(void*)«-Casting, um Spaltenergebnisse an die Aufrufprozedur zurückzugeben. Java verwendet dagegen Methoden, die die erwarteten Typen direkt an den Aufrufer zurückgeben. Da Java ebenso nicht auf eine statische Größe von Arrays beschränkt ist, können Probleme auf Grund zu großer Zeichenketten und verschiedener Zeichengrößen leicht innerhalb von Java gehandhabt werden. Darüber hinaus besitzt JDBC Sicherheitsstufen, die es bei ODBC nicht gibt.
Java 2 Kompendium
883
Kapitel 16
Weiterführende Themen
16.5.4
Grundaufbau von JDBC und des JDBC-Managers
JDBC arbeitet auf zwei Stufen. Die erste Stufe ist der Verbindungsaufbau zwischen der Java-Anwendung und dem JDBC-Treibermanager mittels der JDBC-API. Über diesen JDBC-Treibermanager kann ein Java-Programm dann mehrere JDBC-Treiber verwalten und mit ihnen Informationen und Daten austauschen. Jeder Treiber wiederum kann aus Java direkt auf lokale Daten zugreifen, ODBC als Zwischenebene dazwischen schalten oder einen Netzwerkzugriff auf eine Datenbank auslösen. Die Treiber registrieren sich bei dem JDBC-Manager während der Initialisierung, sodass der Manager einen Überblick über alle verfügbaren Treiber hat. Der JDBC-Manager hat noch weitergehende Aufgaben. So untersucht er beispielsweise den Zustand eines Treibers, damit ggf. ein Applet oder eine Applikation einen geeigneten Treiber herunterladen kann, wenn noch keiner auf dem System vorhanden ist. Während des Versuchs, sich mit einer Datenbank zu verbinden, gibt das Java-Programm eine Datenbank-URL an den JDBC-Manager weiter. Der JDBC-Manager ruft dann jeden geladenen JDBC-Treiber, bis man die angefragte URL öffnen kann. Jeder Treiber ignoriert solche URLs, die Datenbanken erfordern, zu denen er sich nicht verbinden kann. Java Client-Programme können diesen Vorgang des Treibersuchens übergehen und explizit angeben, welcher Treiber verwendet werden soll, wenn der Java-Client bereits vorher weiß, welcher Treiber geeignet ist. Die URLs von JDBC haben immer die folgende Form: jdbc:subprotocol:subname Das Subprotokoll ist der Name des Verbindungsprotokolls, und der Subname ist der Name der jeweiligen Datenbank innerhalb der Domäne des Protokolls. Sofern über den Subnamen Informationen über Host und Port verschlüsselt sind, sollte dieser den Host und den Port in der URL-StandardNotation angeben: //hostname:port/subname Beispielsweise kann eine ODBC-Datenbank mit Namen Bankkonten über den URL jdbc:odbc:Bankkonten spezifiziert werden. Die gleiche Datenbank auf dem Rechner bundesbank zusammen mit dem Verbindungsprotokoll ixnet würde folgende URL haben: jdbc:ixnet://bundesbank/Bankkonten
Jeder Client kann mehrere Datenbankverbindungen geöffnet haben, wobei jede von diesen Verbindungen die Instanz einer Klasse ist, die aus java.sql.connection abgeleitet ist. Die Mehrfachverbindung kann über den gleichen JDBC-Treiber oder über mehrere unterschiedliche Treiber laufen.
884
Java 2 Kompendium
Datenbanken und JDBC
16.5.5
Kapitel 16
Woraus besteht das JDBC-API?
JDBC besteht aus mehreren portablen Java-Klassen/-Schnittstellen und liegt derzeit als JDBC 2 vor, das im SDK 2 von Java integriert ist. JDBC wurde vor dem SDK 2 so gut wie ausschließlich über Klassen realisiert, die allesamt zum Paket java.sql gehören. Dieses Paket beinhaltet u.a. die nachfolgend beschriebenen Schnittstellen, Klassen und Ausnahmen. Wichtigste Schnittstellen: Interface
Beschreibung
CallableStatement
Ein Interface für die Ausführung von SQL-Stored-Procedures.
Connection
Eine Connection (Session) mit einer angegebenen Datenbank. Die Schnittstelle enthält eine Menge Funktionalität, von der Transaktionsverarbeitung bis zum Erzeugen von Statements bildet sie die Grundlage.
DatabaseMetaData
Allgemeine Metainformationen über die Datenbank. Um den JDBC-Einsatz möglichst einfach zu halten, unterstützen JDBC-konforme Programme die JDBC-Schnittstelle mit zusätzlichen Informationen (so genannte Metadaten) über die Datenbank und die Ergebnisse. Dies erfolgt mit Methoden der Schnittstellen java.sql.DatabaseMetaData und java.sql.ResultSetMetaData. Das bedeutet, es gibt über die eigentlichen Datenbankinhalte hinausgehende Informationen. Dies können beispielsweise der Tabellenname, die Breite einer Spalte, die Typen in einer Spalte usw. sein.
Tabelle 16.4: Schnittstellen für JDBC
Die Schnittstelle DatabaseMetaData stellt Katalogfunktionen bereit, die denen in ODBC ähneln. Eine Anwendung kann die zugrunde liegenden DBMS-Systemtabellen abfragen. ODBC gibt die Informationen als ResultSet zurück. JDBC gibt die Ergebnisse als ein ResultSet-Objekt mit wohldefinierten Spalten zurück. Ein DatabaseMetaData-Objekt stellt über 100 Methoden zur Verfügung. Die meisten davon werden jedoch wahrscheinlich nur selten Verwendung finden. Driver
Eine ganz wichtige Schnittstelle. Jede Treiberklasse muss sie implementieren. Die Schnittstelle stellt gewöhnlicherweise Informationen wie PropertyInfo, Versionsnummer usw. bereit.
ResultSet
Eine Tabelle mit Daten, die ein Datenbank-ResultSet repräsentieren, das gerade durch die Ausführung von einen Abfragestatement der Datenbank generiert wurde.
Java 2 Kompendium
885
Kapitel 16 Tabelle 16.4: Schnittstellen für JDBC (Forts.)
Weiterführende Themen
Interface
Beschreibung
ResultSetMetaData
Metainformationen über die Typen und Eigenschaften der Spalten in einem ResultSet-Objekt. Die Schnittstelle dient also zum Auswerten von Ergebnissen einer Abfrage. Verglichen mit dem DatabaseMetaData-Objekt ist das ResultSetMetaData-Objekt leichter und hat weniger Methoden. Über das ResultSetMetaData-Objekt kann man mehr über die Typen und Eigenschaften von Spalten in einem ResultSet herauszufinden. Die Methoden des ResultSetMetaDataObjekts wie getColumnLabel() und getColumnDisplaySize() kann man deshalb in normalen Anwendungsprogrammen verwenden.
SQLData
Mapping von anwenderdefinierten SQL-Typen.
SQLInput
Ein Eingabestrom, der einen Strom von Daten beinhaltet, die als Instanz von einem SQL-struktierten oder -getrennten Typ zu verstehen sind.
SQLOutput
Ein Ausgabestrom zum Schreiben der Attribute von anwenderdefinierten Typen in die Datenbank.
Statement
Ausführung eines statischen SQL-Statements und wieder Entgegennehmen der produzierten Resultate.
Struct
Standard-Mapping zwischen der Java-Programmiersprache für einen SQL-strukturierten Typ.
Klassen: Tabelle 16.5: Klassen für JDBC
886
Klasse
Beschreibung
Date
Wrapper für JDBC um java.util.Date zur Identifikation als ein SQL-Datum.
DriverManager
Die Basisklasse zum Managen eines Satzes von JDBCTreibern unter JDBC vor dem JDBC 2.0 API (dort gibt es alternative Möglichkeiten). Die Klasse enthält die Treiberinformationen, Statusinformationen und mehr. Wenn ein Treiber geladen ist, registriert er sich beim DriverManager. Wenn eine Verbindung geöffnet wird, wählt der DriverManager den Treiber in Abhängigkeit vom JDBC URL aus. Der DriverManager gibt ein Connection-Objekt zurück, wenn Sie die getConnection()-Methode verwenden.
DriverPropertyInfo
Treibereigenschaften für den Aufbau einer Connection.
Java 2 Kompendium
Datenbanken und JDBC
Kapitel 16
Klasse
Beschreibung
SQLPermission
Die Erlaubnis, die der Securitymanager checken wird, wenn Appletcode eine der setLogWriter-Methoden aufruft. Die Klasse ist neu im Java 2 SDK hinzugefügt worden.
Time
Wrapper für JDBC um java.util.Date zur Identifikation als ein SQL-Zeitwert.
Timestamp
Wrapper für JDBC um java.util.Date zur Identifikation als ein SQL-TIMESTAMP.
Types
Definition von Konstanten, die zur Identifikation von generierten SQL-Typen verwendet werden (JDBC-Typen).
Tabelle 16.5: Klassen für JDBC (Forts.)
Wichtigste Exceptions: Exception
Beschreibung
SQLException
Fehler beim Zugriff auf eine Datenbank.
SQLWarning
Warnungen beim Zugriff auf eine Datenbank.
Tabelle 16.6: Exceptions für JDBC
Wenn irgend möglich verwendet JDBC statische Typenfestlegungen zur Kompilierungszeit. Laufzeitfehler beim Datenbankzugriff erzeugen eine Ausnahme vom Typ java.sql.SQLException, die – wie alle Ausnahmen – eine Ableitung von java.lang.Exception ist. Aus diesem Grund sollten sämtliche Datenzugriffsmethoden throws SQLException zum Abfangen bereitstellen und diese in einem try-catch-Block bearbeiten. So etwas könnte folgendermaßen aussehen (eine einfache Ausgabe von Ausnahmen auf das Standardausgabegerät): try { // JDBC-Aufruf } catch (SQLException e) { System.out.println( "In folgendem SQL-Aufruf ist eine Ausnahme aufgetreten: " + e.getSQLState()); System.out.println("Die Meldung der Ausnahme lautet: " + e.getMessage()); System.out.println("Der zugehoerige Errorcode: " + e.getErrorCode()); }
Da ein JDBC-Aufruf zu Folgefehlern durch verkette Ausnahmen führen kann, wird oft die Ausnahmebehandlung in eine Schleifenstruktur verpackt: try { // JDBC-Aufruf
Java 2 Kompendium
887
Kapitel 16
Weiterführende Themen } catch (SQLException e) { while (e != null) { // ... Behandlung einer Ausnahme e.getNextException(); } }
Anders als eine SQLException, die das Programm wegen der ausgeworfenen Ausnahme bemerkt, verursacht eine SQLWarning in einem Java-Programm erst einmal keine Aufmerksamkeit oder gar Probleme. Eine SQLWarning wird an das Objekt, dessen Methode die Warnung verursacht hat, angehängt. Sie sollten deshalb mit der getWarning()-Methode, die für alle Objekte verfügbar ist, auf Warnungen hin überprüfen.
16.5.6
Die JDBC-Treiber
Die JDBC-Treiber existieren in verschiedenen internen Ausprägungen: Der Typ JDBC-ODBC ist für den Zugriff auf alle vorhandenen ODBCTreiber und ihre Datenquellen zuständig. Dieser Treiber wurde gemeinsam von Sun und Intersolv entwickelt und nennt sich JDBC-ODBCBridge. Die JDBC-ODBC-Bridge ist als jdbcOdbc.class in Java implementiert und eine native Bibliothek, um auf den ODBC-Treiber zuzugreifen (keine Standard-Klasse). Unter Windows ist die native Bibliothek eine DLL (JDBCODBC.DLL). Da JDBC sehr am Design von ODBC orientiert ist, ist die ODBC-Bridge eine dünne Schicht über JDBC. Intern mappt dieser Treiber JDBC-Methoden auf ODBC-Aufrufe und tritt so mit jedem verfügbaren ODBC-Treiber in Interaktion. Der Vorteil dieser Bridge ist, dass über JDBC fast alle Datenbanken angesprochen werden können, da es für die meisten Datenbanken ODBC-Treiber gibt. Wenn die ausführenden Binärdateien auf dem Clientrechner installiert sind, kann darüber dann der Zugriff auf Datenbestände erfolgen. Der wohl grö ß te Nachteil der JDBC-ODBCBridge ist die bei großen Datenbanken zu geringe Performance. Der zweite Typ von Treiber konvertiert JDBC-Aufrufe in Textkommandos einer darunter liegenden speziellen Datenbank-API, beispielsweise DB2, Informix, Oracle oder andere DBMS. Damit müssen auf dem Client wieder plattformabhängige Binärdateien vorhanden sein, die diese Kommandos sinnvoll auswerten und die eigentlichen Datenbankzugriffe erledigen. Vorteil dieser Technik gegenüber der JDBCODBC-Bridge ist bessere Performance. Die dritte Form von JDBC-Treibern spricht aus Java direkt Datenbanken an. Zumeist sind dies lokale Datenbanken. Dazu werden alle Zugriffs-, Lese-, Schreib- und Auswertungsaufgaben unmittelbar implementiert.
888
Java 2 Kompendium
Datenbanken und JDBC
Kapitel 16
Der vierte Typ von JDBC-Treibern ist für datenbankspezifische Netzwerkbefehle zuständig. Mittels dieses Treibers werden JDBC-Anweisungen in datenbankspezifische Netzwerkbefehle übersetzt, die die Datenbank-API auf dem Server dann ausführt. Diese Treiberform ist explizit nicht auf zusätzliche Binärdateien auf dem Client (witzigerweise ist hier mit Client der Server gemeint – ist halt eine andere Beziehung) angewiesen. Statt dessen wird direkt mittels den Java-NetzwerkKlassen mit der Datenbank kommuniziert. Diese verhalten sich dort wie jede andere Zugriffssoftware. Nahezu alle namhaften Hersteller von Datenbanken (und diverser Tools) bieten mittlerweile diese Form von Treibern an, weil sie einfach die effektivste, schnellste und sicherste Variante ist.
16.5.7
Schematischer Aufbau einer Datenbank-Applikation
Allgemein geht man bei Datenbankzugriffen so vor, dass man zuerst die notwendigen Packages einbindet. Das sind so gut wie immer java.net.URL bzw. java.net.* und java.sql.*. Oft brauchen Sie noch java.util.* und java.math.* (Letzteres, wenn Sie mit der Klasse BigDecimal arbeiten wollen). Im nächsten Schritt laden Sie die gewünschten JDBC-Treiber unter ihrem Java-Namen. Dies kann mit folgender Syntax geschehen: try { // Laden der gewünschten Treiber Class.forName(drivername); // ... weitere sinnvolle Aktionen } catch (ClassNotFoundException e) { // etwas Sinnvolles tun }
Die Methode forName() der Klasse Class erledigt das Laden der Treiberklasse (ein Vorgang, der auf der Fähigkeit der Reflection basiert). Dazu muss man allerdings den vollständigen Klassennamen kennen. Für die JDBC-ODBC-Bridge sieht das so aus: Class.forName("sun.jdbc.odbc.JdbcOdbcDriver");
Die Namen von anderen Treibern sind in der Dokumentation des jeweiligen Herstellers angegeben. Im nächsten Schritt können wir bereits eine Datenbankverbindung aufbauen. Als fiktives Beispiel soll unsere ODBC Datenbank mit Namen »dbtest« dienen. Über den URL jdbc:odbc:dbtest sprechen wir sie an:
Java 2 Kompendium
889
Kapitel 16
Weiterführende Themen String url="jdbc:odbc:dbtest"; Connection con = DriverManager.getConnection(url, "userid", "passwd");
Die Methode getConnection() liefert eine Connection zur Datenbank. Sie gibt es in verschiedenen Varianten. Insbesondere, wenn keine User-ID und kein Passwort notwendig sind, kann man auch einfach Folgendes notieren: Connection con = DriverManager.getConnection(url);
Der nächste Schritt dient dazu, ein Abfrageobjekt zu erstellen und darauf ein (im Grunde beliebiges) SQL-Kommando auszuführen. In unserem Beispiel wollen wir sämtliche Datensätze aus einer Tabelle namens Tabelle1 ausgeben. Statement abfrageobj = con.createStatement(); ResultSet ergebnis = abfrageobj.executeQuery("SELECT * from Tabelle1");
Wenn wir mittels einer SQL-Abfrage die Datensätze selektiert haben, sollten wir damit auch etwas Sinnvolles tun, z.B. auf dem Bildschirm nach gewissen Kriterien anzeigen, was folgende Methode tut: private static void zeigeResultate (ResultSet res) throws SQLException { // Zur Abfrage der Metadaten wird ein Metadatenobjekt // erstellt ResultSetMetaData resmeta = res.getMetaData(); // Abfrage der Anzahl von Spalten in der Tabelle über // das Metadatenobjekt int anzahlSpalten = resmeta.getColumnCount(); // Abfrage der Namen von den Spalten in der Tabelle // über das Metadatenobjekt und Ausgabe der vorhandenen // Spaltennamen (bis anzahlSpalten) for (int i=1; i <= anzahlSpalten; i++) { System.out.println(resmeta.getColumnName(i)); } // Erzeuge einen Zeilenvorschub System.out.println(""); // Anzeige der Daten über zwei ineinander // verschachtelte Schleifen while(res.next()) { for (int i=1; i <= anzahlSpalten; i++) { // Trennzeichen für die Ausgabe if (i > 1) System.out.print(";"); System.out.print (res.getString(i);); } // Ende for()-Schleife // Erzeuge einen Zeilenvorschub System.out.println(""); } // Ende while()-Schleife System.out.println("Ende der Ausgabe!"); } 890
Java 2 Kompendium
Datenbanken und JDBC
Kapitel 16
Zum Bewegen des Datensatzzeigers in dem Resultset sind insbesondere die folgenden Methoden von Interesse: Methode
Beschreibung
public boolean next() throws SQLException
Nächsten Datensatz auswählen.
public boolean previous() throws SQLException
Vorherigen Datensatz auswählen.
public void afterLast() throws SQLException
Bewegt den Datensatzzeiger auf das Ende des ResultSet-Objekts (direkt hinter die letzte Zeile).
public void beforeFirst() throws SQLException
Bewegt den Datensatzzeiger vor den Beginn des ResultSet-Objekts (direkt vor die erste Zeile).
public boolean first() throws SQLException
Bewegt den Datensatzzeiger auf die erste Zeile des ResultSet-Objekts.
public boolean last() throws SQLException
Bewegt den Datensatzzeiger auf die letzte Zeile des ResultSet-Objekts.
public void moveToCurrentRow() throws SQLException
Bewegt den Datensatzzeiger auf die aktuelle Zeile.
Tabelle 16.7: Methoden für die Navigation auf einem ResultSetObjekt
Eine Methode wie zeigeResultate(ergebnis) sollte nun nach der Selektion der Abfrageergebnisse (am besten innerhalb eines try-catch-Blocks, um die SQLExceptions abzufangen) aufgerufen werden und dann im letzten Schritt die üblichen Aufräumarbeiten erfolgen: abfrageobj.close(); con.close();
16.5.8
Praktischer Einsatz von JDBC
Lassen Sie uns einen Datenbankzugriff mit Java und der JDBC-ODBCBridge anhand eines kleinen, aber vollständigen Beispiels Schritt für Schritt durchspielen. Dabei greifen wir auf eine Access-Datenbank zu, die entsprechend mit einem ODBC-Treiber registriert ist. Sie können das Beispiel mit der auf der CD befindlichen Datenbank DBTest.mdb nachvollziehen oder auch eine eigene Datenbank verwenden. Das muss auch keine AccessDatenbank sein. Das ist ja gerade der Vorteil von JDBC bzw. auch ODBC. Sie müssen nur dafür sorgen, dass die Datenbank im Betriebssystem bereitsteht. Spielen wir zuerst die Anmeldung der Datenbank unter ODBC anhand von Windows NT durch.
Java 2 Kompendium
891
Kapitel 16
Weiterführende Themen Wenn Sie mit der Beispieldatenbank arbeiten wollen, sollten Sie diese in ein Verzeichnis auf der Festplatte kopieren. Das erleichert sowohl die Arbeit mit ODBC (was soll passieren, wenn die CD nicht eingelegt ist?) und macht natürlich erst Schreibvorgänge in der Datenbank möglich. Um die Datenbank (oder jede andere) unter ODBC anzumelden, öffnen Sie die Systemsteuerung von Windows und wählen dort das ODBC-Management aus.
Abbildung 16.19: Das ODBCManagement in der Systemsteuerung
Sie finden dort alle angemeldeten ODBC-Treiber und Schablonen für die zur Verfügung stehenden Datenbankformate. Wenn Sie eine neue Datenbank anmelden wollen, klicken Sie auf HINZUFÜGEN.... Da wir in unserem Beispiel mit einer Access-Datenbank arbeiten wollen, wählen wir dessen Treiber-Schablone aus. Sie müssen entsprechend die passende Treiber-Schablone auswählen. Im Folgedialog wählen Sie die physikalische Datenbank mit allen notwendigen Angaben und/oder – teils optionalen – Optionen (Pfad und Dateiname, Seitentimeout, Puffergrö ß e, Exklusivzugriff, Schreibschutz, Beschreibung, Name). Von besonderem Interesse ist der Datenquellenname, denn dies ist die Angabe, über die Sie die Datenbank dann im Java-Quelltext ansprechen. 892
Java 2 Kompendium
Datenbanken und JDBC
Kapitel 16 Abbildung 16.20: Hinzufügen einer neuen Datenbank
Abbildung 16.21: Hinzufügen einer neuen Datenbank auf Basis von Access
Wenn die Datenbank dann so im Betriebssystem registriert ist, kann sie aus Java heraus, wie oben beschrieben, genutzt werden. Wir demonstrieren in den nachfolgenden Beispielen die wichtigsten Situationen.
Java 2 Kompendium
893
Kapitel 16
Weiterführende Themen
Abbildung 16.22: Die genaue Angabe der DatenbankDatei und ihrer Zugriffseigenschaften
Selects auf eine Datenbank Zu einem einfachen Select auf die Datenbank und der Ausgabe der Ergebnisse genügt eine einfache Struktur wie die folgende: Listing 16.14: Select-Zugriff auf eine Datenbank mit einem SQLStatement
894
import java.net.URL; import java.sql.*; class DBSelectTest { public static void main(String argv[]) { try { // Der JDBC-URL für den Zugriff auf ODBC String url = "jdbc:odbc:dbtest"; // Verbindung zu der Datenbank Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); Connection con = DriverManager.getConnection(url); // Ausführung einer SQL-SELECT-Anweisung Statement stmt = con.createStatement(); ResultSet rs = stmt.executeQuery( "SELECT * FROM Tabelle1"); System.out.println("Das Ergebnis der Abfrage:"); // Durch alle Datensätze des Ergebnisses // der Abfrage steppen und ausgeben while (rs.next()) { // Werte der Spalten der aktuellen Zeile String a = rs.getString(1); String b = rs.getString(2); String c = rs.getString(3); String e = rs.getString(5); String f = rs.getString(6); // Ausgabe des Resultats: System.out.print("Nr: " + a); System.out.print(", Vorn: " + b);
Java 2 Kompendium
Datenbanken und JDBC System.out.print(", Nachn: System.out.print(", Str: " System.out.print(", Ort: " // Zeilenvorschub System.out.print("\n"); } stmt.close(); con.close(); } catch (java.lang.Exception ex.printStackTrace(); } } }
Kapitel 16 " + c); + e); + f);
ex) {
Das Beispiel schickt ein einfaches SQL-Statement an die Datenbank, in dem die Tabelle mit Namen Tabelle1 ausgewählt wird und dort alle Spalten – in der Abfrage – ausgewählt werden. Von dem zurückgegebenen Resultset werden zwar alle Datensätze (eine while-Schleife, in der mit der Methode next() alle Datensätze der Reihe nach ausgewählt werden) geliefert, es werden aber nur die Spalten 1, 2, 3, 5 und 6 ausgegeben. Die konkrete Abfrage wird mit der Methode public ResultSet executeQuery(String sql) throws SQLException ausgelöst. Abbildung 16.23: Gefiltertes Ergebnis der Abfrage
Wenn Sie die Datenbank gezielter abfragen wollen, können Sie im SQLSELECT-Statement die Spaltennamen (die sie natürlich kennen müssen) direkt angeben. Etwa so: ResultSet rs = stmt.executeQuery( "SELECT a, b, c, d, e FROM Tabelle1");
Java 2 Kompendium
895
Kapitel 16
Weiterführende Themen Allgemeine Syntax der SELECT-Anweisung ist folgende: SELECT [ALL|DISTINCT] SpaltenListe FROM Tabname1 [, Tabname2 ...] [WHERE Suchbedingung] [GROUP BY SpaltenName1 [, SpaltenName2 ...]] [HAVING Suchbedingung] [UNION SubQuery] [ORDER BY SpaltenName1 [ASC|DESC] [, SpaltenName2 [ASC|DESC]...]]
Wenn Sie aus dem Resultset nicht nur Strings herausholen wollen, können Sie diverse Methoden direkt auf dem ResultSet-Objekt anwenden. Etwa so: int a = rs.getInt(1); BigDecimal b = rs.getBigDecimal(2); char c[] = rs.getString(3).toCharArray(); boolean d = rs.getBoolean(4);
Grundsätzlich gibt es zahlreiche dieser Abfrage-Methoden, die alle mit get beginnen und dann die Art der Extrahierung genauer spezifizieren. Die Namen der Methoden sind weitgehend selbsterklärend. Hier eine Auswahl der wichtigsten: Array getArray(int i) Array getArray(String colName) BigDecimal getBigDecimal(int columnIndex) BigDecimal getBigDecimal(String columnName) boolean getBoolean(int columnIndex) boolean getBoolean(String columnName) byte getByte(int columnIndex) byte getByte(String columnName) byte[] getBytes(int columnIndex) byte[] getBytes(String columnName) Date getDate(int columnIndex) Date getDate(int columnIndex, Calendar cal) Date getDate(String columnName) Date getDate(String columnName, Calendar cal) double getDouble(int columnIndex) double getDouble(String columnName) float getFloat(int columnIndex) float getFloat(String columnName) int getInt(int columnIndex) int getInt(String columnName) long getLong(int columnIndex) long getLong(String columnName) ResultSetMetaData getMetaData() Object getObject(int columnIndex) Object getObject(String columnName) int getRow() short getShort(int columnIndex)
896
Java 2 Kompendium
Datenbanken und JDBC
Kapitel 16
short getShort(String columnName) Statement getStatement() String getString(int columnIndex) String getString(String columnName) Time getTime(int columnIndex) Time getTime(int columnIndex, Calendar cal) Time getTime(String columnName) Time getTime(String columnName, Calendar cal) Timestamp getTimestamp(int columnIndex) Timestamp getTimestamp(int columnIndex, Calendar cal) Timestamp getTimestamp(String columnName) Timestamp getTimestamp(String columnName, Calendar cal) SQLWarning getWarnings()
Updates Schauen wir uns nun an, wie aus Java heraus ein Update einer Datenbank realisiert werden kann. Wenn wir mit dem ResultSet arbeiten wollen, stehen zahlreiche Methoden für ein gezieltes Update von ganzen Datensätzen und/ oder gezielt einzelnen Feldern der Datenbank zur Verfügung. Die Ausführung eines Updates wird mit der Methode public int sql) throws SQLException angestoßen. Als SQLAnweisung muss ein gültiges INSERT-, UPDATE- oder DELETE-Statement angegeben werden. executeUpdate(String
import java.net.URL; import java.sql.*; class DBTestUpdate { public static void main(String argv[]) { // Die JDBC-URL für den Zugriff auf ODBC String url = "jdbc:odbc:dbtest"; Connection con; Statement stmt; try { // Verbindung zum Treiber Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch(java.lang.ClassNotFoundException e) { System.err.print("ClassNotFoundException: "); System.err.println(e.getMessage()); } try { con = DriverManager.getConnection(url); stmt = con.createStatement(); stmt.executeUpdate("insert into Tabelle2 " + "values('Eins', 'Zwei', 'Drei')"); stmt.close(); con.close(); System.out.println("Fertisch"); Java 2 Kompendium
Listing 16.15: Ein neuer Datensatz wird hinzugefügt.
897
Kapitel 16
Weiterführende Themen } catch (java.lang.Exception ex) { ex.printStackTrace(); } } }
Das Beispiel fügt einen neuen Datensatz in die Tabelle2 der Datenbank hinzu. Diese hat drei Spalten, die einfach ohne die Angabe eines Spaltennamens sequenziell gefüllt werden. Das Beispiel ist – um nur die entscheidenten Details zu zeigen – ganz einfach gehalten und zeigt nicht einmal an, was nach dem Update in der Datenbank steht (das machen wir aber gleich im nächsten Beispiel). Mit SQL kann man selbstverständlich auch gezielt einzelne Spalten ansprechen. Grundsätzlich verwendet man INSERT, um einen neuen Datensatz anzulegen. Allgemeine Syntax ist folgende: INSERT INTO Tabname [(SpaltenName1 [,SpaltenName2 ... ])] VALUES (Wert1 [, Wert2 ...])
Bauen wir das Beispiel von eben ein wenig aus. Es sollen keine festen Werte hinzugefügt werden, sondern die Übergabewerte an das Programm5. Außerdem sollen alle nach dem Update in der Tabelle vorhandenen Werte unmittelbar nach der Aktion ausgegeben werden. Listing 16.16: Drei Übergabewerte werden als neuer Datensatz hinzugefügt und die gesamte Tabelle anschließend ausgegeben.
import java.net.URL; import java.sql.*; class DBTestUpdate2 { public static void main(String args[]) { // Die JDBC-URL für den Zugriff auf ODBC String url = "jdbc:odbc:dbtest"; Connection con; Statement stmt; String sql = ""; // Auswerten der Übergabewerte und // Zusammensetzen des Insert-Strings try{ sql = "INSERT INTO Tabelle2" + " values('" + args[0] + "', '" + args[1] + "', '"+ args[2] + "')"; } catch(Exception e) { System.err.print("Fehler: Das Programm benoetigt drei Uebergabewerte"); System.err.println(e.getMessage()); } try { // Verbindung zum Treiber Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch(java.lang.ClassNotFoundException e) { 5
898
Wir erstellen also eine einfache Datenbank-Shell zum Update von Werten.
Java 2 Kompendium
Datenbanken und JDBC
Kapitel 16
System.err.print("ClassNotFoundException: "); System.err.println(e.getMessage()); } try { con = DriverManager.getConnection(url); stmt = con.createStatement(); // Datensatz updaten mit den // Übergabewerten beim Aufruf stmt.executeUpdate(sql); // Abfrage des Inhalts der Tabelle nach dem // Update ResultSet rs = stmt.executeQuery( "SELECT * FROM Tabelle2"); System.out.println("Das Ergebnis der Abfrage:"); // Durch alle Datensätze des Ergebnisses // der Abfrage steppen und ausgeben while (rs.next()) { // Werte der Spalten der aktuellen Zeile String a = rs.getString(1); String b = rs.getString(2); String c = rs.getString(3); // Ausgabe des Resultats: System.out.print("Feld A: " + a); System.out.print(", Feld B: " + b); System.out.print(", Feld C: " + c); // Zeilenvorschub System.out.print("\n"); } stmt.close(); con.close(); System.out.println("Fertisch"); } catch (java.lang.Exception ex) { ex.printStackTrace(); } } }
Beachten Sie, dass das Programm unbedingt drei Übergabewerte benötigt. Das kann natürlich noch leicht perfektioniert werden, indem in diesem Fall Default-Leerwerte in die Datenbank geschrieben werden oder etwas Ähnliches. Um einen bereits bestehenden Datensatz upzudaten, wird das SQL-Statement UPDATE verwendet. Allgemeine Syntax ist folgende: UPDATE Tabname SET SpaltenName1 = Wert1| NULL [, SpaltenName2 = Wert2| NULL...] [WHERE Suchbedingung]
Java 2 Kompendium
899
Kapitel 16
Weiterführende Themen
Abbildung 16.24: Zwei Inserts wurden durchgeführt.
Grundsätzlich stellt die Klasse ResultSet auch für das Update einer Datenbank zahlreiche Methoden bereit, die alle mit update beginnen und dann die Art des Updates genauer spezifizieren. Die Namen der Methoden sind weitgehend selbsterklärend und zeigen die Verwandtschaft zu den AbfrageMethoden. Hier eine Auswahl der wichtigsten: void void void void void void void void void void void void void void void void void void void void void void void void void void void
900
updateBigDecimal(int columnIndex, BigDecimal x) updateBigDecimal(String columnName, BigDecimal x) updateBoolean(int columnIndex, boolean x) updateBoolean(String columnName, boolean x) updateByte(int columnIndex, byte x) updateByte(String columnName, byte x) updateBytes(int columnIndex, byte[] x) updateBytes(String columnName, byte[] x) updateDate(int columnIndex, Date x) updateDate(String columnName, Date x) updateDouble(int columnIndex, double x) updateDouble(String columnName, double x) updateFloat(int columnIndex, float x) updateFloat(String columnName, float x) updateInt(int columnIndex, int x) updateInt(String columnName, int x) updateLong(int columnIndex, long x) updateLong(String columnName, long x) updateNull(int columnIndex) updateNull(String columnName) updateObject(int columnIndex, Object x) updateObject(int columnIndex, Object x, int scale) updateObject(String columnName, Object x) updateObject(String columnName, Object x, int scale) updateRow() updateShort(int columnIndex, short x) updateShort(String columnName, short x)
Java 2 Kompendium
Datenbanken und JDBC void void void void void void
Kapitel 16
updateString(int columnIndex, String x) updateString(String columnName, String x) updateTime(int columnIndex, Time x) updateTime(String columnName, Time x) updateTimestamp(int columnIndex, Timestamp x) updateTimestamp(String columnName, Timestamp x)
Das Update einer Datenbank ist jedoch nicht ganz so trivial wie das einfache Auslesen. Es handelt sich immerhin um Schreiboperationen. Es kann zu Zugriffskonflikten kommen, unter Umständen müssen neue Datensätze, eventuell sogar neue Tabelle oder gar die ganze Datenbank erzeugt werden. Dazu gibt es die SQL-Anweisungen CREATE und DROP. Updates von Datenbanken erledigt man oft auch nicht mit ResultSet, sondern PreparedStatement. Diese Schnittstelle stellt vorkkompilierte SQL-Statements bereit. Löschvorgänge Löschvorgänge in einer Datenbank sind eigentlich Spezialfälle einer UpdateAktion. Dementsprechend sieht die Beispieldatei fast gleich aus. Sie verwendet im Wesentlichen nur das SQL-DELETE-Statement. Dessen allgemeine Syntax sieht so aus: DELETE FROM Tabname [WHERE Suchbedingung]
Wir wollen einfach in dem folgenden Beispiel die Tabelle leeren. Vorher wird der Inhalt der Tabelle angezeigt und nach dem Löschen zum Beweis nochmal eine Abfrage gestartet und ausgegeben (leer). import java.net.URL; import java.sql.*; class DBTestDelete { public static void main(String argv[]) { // Die JDBC-URL für den Zugriff auf ODBC String url = "jdbc:odbc:dbtest"; Connection con; Statement stmt; try { // Verbindung zum Treiber Class.forName("sun.jdbc.odbc.JdbcOdbcDriver"); } catch(java.lang.ClassNotFoundException e) { System.err.print("ClassNotFoundException: "); System.err.println(e.getMessage()); } try { con = DriverManager.getConnection(url); stmt = con.createStatement();
Java 2 Kompendium
Listing 16.17: Löschen der Tabelle mit Kontrollausgaben
901
Kapitel 16
Weiterführende Themen ResultSet rs = stmt.executeQuery( "SELECT * FROM Tabelle2"); System.out.println( "Tabelleninhalt vor dem Loeschen:"); // Durch alle Datensätze des Ergebnisses // der Abfrage steppen und ausgeben while (rs.next()) { // Werte der Spalten der aktuellen Zeile String a = rs.getString(1); String b = rs.getString(2); String c = rs.getString(3); // Ausgabe des Resultats: System.out.print("Feld A: " + a); System.out.print(", Feld B: " + b); System.out.print(", Feld C: " + c); // Zeilenvorschub System.out.print("\n"); } stmt.executeUpdate("DELETE FROM Tabelle2 "); System.out.println( "Tabelleninhalt nach dem Loeschen:"); // Durch alle Datensätze des Ergebnisses // der Abfrage steppen und ausgeben rs = stmt.executeQuery( "SELECT * FROM Tabelle2"); while (rs.next()) { // Werte der Spalten der aktuellen Zeile String a = rs.getString(1); String b = rs.getString(2); String c = rs.getString(3); // Ausgabe des Resultats: System.out.print("Feld A: " + a); System.out.print(", Feld B: " + b); System.out.print(", Feld C: " + c); // Zeilenvorschub System.out.print("\n"); } stmt.close(); con.close(); System.out.println("Fertisch"); } catch (java.lang.Exception ex) { ex.printStackTrace(); } } }
Achten Sie vor dem Ausführen des Beispiels darauf, dass vorher das UpdateBeispiel gestartet wurde oder die Tabelle anderweitig gefüllt wurde. Sie sehen sonst nicht viel.
902
Java 2 Kompendium
Datenbanken und JDBC
Kapitel 16 Abbildung 16.25: Vorher ist was drin und dann isses weg.
Weiterführende Datenbank-Aktionen Selbstverständlich stellen JDBC und Java Möglichkeiten für weiterführende Datenbank-Aktionen bereit, was aber hier den Rahmen sprengt. So gibt es die Methoden void commit() void rollback() void setAutoCommit(boolean aC)
für Transaktionen und diverse ergänzende Dinge wie Transaktionslevels, Methoden zum Verwenden von Metadaten oder gecachte Connections und Techniken, um mit verknüpften Tabellen zu arbeiten. Für diese sehr umfangreiche Fragestellung sei explizit auf Spezialliteratur oder die OnlineDokumentation verwiesen. JDBC jenseits von SQL Vom seinem Funktionsprinzip her ist JDBC nicht auf SQL-Zugriffe beschränkt. Ein Treiber muss nur die Methode public boolean jdbcCompliant() enthalten. Damit wird mitgeteilt, ob der Treiber den ANSI-92 SQLStandard unterstützt. Für eine SQL-Datenbank muss mindestens das so genannte ANSI SQL92 EntryLevel unterstützt werden. Aber auch wenn ein JDBC-Treiber diesem ANSI SQL92 EntryLevel nicht entspricht, kann er mit Textkommandos einen darunter liegenden DBMS-Treiber ansprechen. Dieser muss nur in der Lage sein, diese Kommandos sinnvoll auszuwerten. Damit lassen sich die spezifischen Funktionen des DBMS-Treibers mittels JDBC aus Java heraus nutzen, die Portabilität geht jedoch verloren, was dann bei anderen Datenbanktypen zu Fehlern führen kann. In diesem Fall wird eine Ausnahme erzeugt.
Java 2 Kompendium
903
Kapitel 16
Weiterführende Themen JDBC-Sicherheit Java verfolgt den Vertrauensstatus (Trusted Status) eines jeden Datenbanktreibers. Dies hat die Folge, dass nicht vertrauenswürdige Treiber die gleichen Beschränkungen wie nicht vertrauenswürdige Applets haben. Damit können sie nicht auf das lokale Dateisystem, sondern nur auf die Datenbestände der Hosts zugreifen, von denen sie kommen. Der JDBC-Manager geht sogar noch einen Schritt weiter – nicht vertrauenswürdige Treiber können nur zusammen mit den Applets verwendet werden, die vom gleichen Host kommen. Während des Verbindungsversuchs und der Suche nach dem geeigneten JDBC-Treiber greift der Manager nicht auf nicht-vertrauenswürdige Treiber der verschiedenen Hosts zurück.
16.6
Zusammenfassung
Ursprünglich war Java nur eine neue Programmiersprache, aber mittlerweile bietet Java auf vielfältigen Ebenen Erweiterungsmöglichkeiten. Auch rund um Java schreiten Entwicklungen voran, die sich mit Java kombinieren lassen oder auf dem Java-Konzept aufbauen. Wir haben uns in diesem Kapitel mit den Themen Reflection Serialization JavaBeans CORBA, IDL und RMI Netzwerkzugriffe, Sockets und Java-Servlets Datenbanken und JDBC Schlaglichter herausgesucht, die einige dieser vielfältigen Einsatzmöglichkeiten darstellen. Vollständig beschrieben sind sie damit aber nicht. Dies macht ja gerade das ungeheuere Potenzial von Java aus. Ich hoffe, Ihnen hat der Einstieg in die Java-Welt Spaß gemacht und Sie können und werden Java als Grundlage Ihrer weiteren Programmiertätigkeit einsetzen.
904
Java 2 Kompendium
Anhang A
A
Anhang A behandelt die erweiterten Tools des JDK und einige Freewarebzw. Shareware-IDEs für Java.
A.1
Ergänzungen zu den JDK-Tools
In Kapitel 3 haben wir die wichtigsten drei Tools des JDK besprochen: Appletviewer Compiler Interpreter Das JDK besteht aber zusätzlich aus einer ganzen Reihe von weiteren Tools. Diese sollen hier durchgesprochen werden. Neben den bereits behandelten Tools des JDK zählen noch einige weitere Programme zu den Basis-Tools. Dazu kommen die Tools für die erweiternden Funktionalitäten. Die Tools für die erweiternden Funktionalitäten von Java lassen sich in verschiedene Gruppen unterteilen, die wir auch getrennt behandeln. Allerdings nur knapp. Eine Ausnahme sind die Sicherheitsprogramme, auf deren Arbeitsweise und Hintergründe wir etwas intensiver eingehen wollen. Eine ausführliche Besprechung aller Programme sprengt jedoch den Rahmen dieses Buchs. Sie werden auch für die meisten Leser von untergeordneter Bedeutung sein. Eine vollständige Abhandlung finden Sie in der offiziellen Dokumentation des JDK.
A.1.1
Erweiterte Basisprogramme
Zu den erweiterten Basisprogrammen werden offiziell Folgende gezählt: Der Java-Disassembler javap Das Programm javah, ein Generator, um C-Header-Dateien und StubDateien zu erstellen Das Dokumentations-Tool javadoc, um HTML-Dokumente für eine Dokumentation zu erstellen
Java 2 Kompendium
905
Anhang A Der Java-Debugger jdb bzw. seine Vorgängerversion, die im JDK 1.3 als oldjdb geführt wird Das Java Archive Tool jar Das Diagnose-Tool extcheck für Jar-File-Versionskonflikte Der Java-Disassembler Im Gegensatz zu vollständig kompiliertem Maschinencode ist es bei JavaBytecode möglich, den Quellcode wieder weitgehend zu reproduzieren. Natürlich werden nicht alle Details reproduzierbar sein, aber alle wichtigen Bestandteile des Quellcodes sind wiederherstellbar. Diesen Vorgang der Rückübersetzung von Bytecode in Quellcode nennt man Disassemblierung. Unter Java übernimmt diesen Prozess ein eigenes JDK-Tool, der Java-Disassembler javap. Nach der Disassemblierung des Codes werden diverse Informationen über den Quelltext ausgegeben. In der Standardeinstellung des Java-Disassemblers werden Informationen über Deklarationen von Definitionen, Methoden, Konstruktoren und statischen Initialisierern ausgegeben, sofern diese nicht private oder protected sind. Mit diversen Optionen kann man diesen Vorgang erweitern und genauer spezifizieren. Wenn man beispielsweise das klassische Java-HelloWorld-Programm kompiliert und dann per javap disassembliert, erhält man aus dem ursprünglichen Source class HelloWorld { public static void main (String args[]) { System.out.println("Hello World!"); }}
folgende Source-Datei zurückgeneriert: Compiled from HelloWorld.java class HelloWorld extends java.lang.Object { public static void main(java.lang.String []); HelloWorld(); }
Es wird auffallen, dass bereits ohne besondere Optionen ausführliche Angaben über die Superklasse (extends java.lang.Object) sowie die Methodenargumente (java.lang.String []) explizit aufgeführt werden. Die Syntax für den Java-Disassembler: javap [Optionen] [Klassenname(n)]
906
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Anhang A
Wie aus der Syntax bereits herleitbar ist, können in einem Schritt mehrere Klassen disassembliert werden. Die Klassen werden alle, durch jeweils ein Leerzeichen getrennt, hintereinander aufgelistet. Wichtig ist auch hier wieder, dass die Angabe der Erweiterung .class unbedingt unterbleiben muss.
Option
Beschreibung
-help
Anzeige von Hilfe
-c
Diese Option weist den Disassembler an, sämtliche Instruktionen der JVM (public, jedoch auch private und protected) auszugeben, also die Quelldatei zu disassemblieren und den vollständigen Bytecode, der vom Compiler erzeugt wird, anzuzeigen. Aus dem oben angeführten HelloWorld wird die disassemblierte Version:
Tabelle A.1: Die Javap-Optionen
Compiled from HelloWorld.java class HelloWorld extends java.lang.Object { public static void main(java.lang.String []); HelloWorld(); Method void main(java.lang.String []) 0 getstatic #7 3 ldc #1 <String "Hello World!"> 5 invokevirtual #8 <Method java.io.PrintStream.println(Ljava/lang/String;)V> 8 return Method HelloWorld() 0 aload_0 1 invokenonvirtual #6 <Method java.lang.Object.()V> 4 return } -classpath [Verzeichnis]
Diese Angabe korrespondiert wie bereits beim Compiler und Interpreter mit der gleichnamigen Umgebungsvariable und teilt in diesem Fall dem Disassembler mit, in welchen Verzeichnissen nach den einzelnen Klassen zu suchen ist. Die Verzeichnisse werden in der Unix-Syntax durch Doppelpunkte getrennt, Windows benutzt das Semikolon. Auch ist für Pfadangaben zu beachten, dass die Komponenten eines Pfades unter Unix mittels des Slash und unter Windows mittels des Backslash voneinander getrennt werden. Die angegebenen Pfade werden in der Reihenfolge ihres Auftretens durchsucht.
-bootclasspath path
Spezifiziert den Pfad, von wo die bootstrap classes zu laden sind (Default ist jre\lib\rt.jar und jre\lib\i18n.jar).
Java 2 Kompendium
907
Anhang A Tabelle A.1: Die Javap-Optionen (Forts.)
Option
Beschreibung
-extdirs dirs
Überschreibt den Pfad, wo Erweiterungen für das JDK gesucht werden (default jre\lib\ext).
-verbose
Diese Option veranlasst den Disassembler, diverse Statusinformationen (z.B. Stack, Variablen, Argumente usw.) über den Quellcode anzuzeigen, während er gerade disassembliert wird.
-l
Gibt zusätzlich noch Zeilennummern und lokale Variablen aus. Aus dem Beispiel oben wird Compiled from ErstesJavaProgramm.java class HelloJava extends java.lang.Object { public static void main(java.lang.String[]); HelloJava(); } Line numbers for method void main(java.lang.String[]) line 3: 0 line 2: 8 Line numbers for method HelloJava() line 1: 0
-b
Stellt die Rückwärtskompatibilität zu früheren Versionen sicher.
-public
Zeigt nur die öffentlichen Klassen und Bestandteile an.
-protected
Zeigt die geschützten und die öffentlichen Klassen und Methoden an.
-private
Zeigt alle Klassen und Methoden an.
-s
Zeigt interne Typ-Signaturen an.
-package
Zeigt die Pakete, die geschützten und die öffentlichen Klassen und Methoden an (Default).
-J Flag
Gibt ein Flag direkt an das Runtime-System weiter. Beispiele: javap -J-version javap -J-Djava.security.manager -J-Djava.security.policy=MeinePolicy MeinKlassenName
Javap gibt die Ausgabe der Dissamblierung standardmäßig auf den Bildschirm aus. Um den Source in eine neue Datei auszugeben (etwa zur späteren Weiterverwendung), können Sie unter der DOS/Windows-Eingabeaufforderung die Technik des Umleitens nutzen. Mit dem Umleitungszeichen > leiten Sie die Ausgabe des Befehls in eine beliebige Datei um. Die Datei mit dem rechtsseitig angegebenen Namen wird erstellt, die dann den zurückübersetz-
908
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Anhang A
ten Quelltext enthält. Dieser kann dann beliebig weiter bearbeitet und unter Umständen sogar neu kompiliert werden (Achtung: Es fehlen je nach verwendeter Option Klassen oder einiger ihrer Bestandteile). javah – das Tool zur Erstellung von C-Header- und Stub-Dateien Der javah-Generator erstellt C-Header (Erweiterung .h) und C-Quelldateien (Erweiterung .c) für die angegebenen Klassen. Diese so generierten Dateien enthalten alle notwendigen Informationen zur Implementierung von nativen Methoden, beispielsweise #include- und #define-Anweisungen, typedefKonstrukte u.ä. Normalerweise generiert javah nur ein Headerfile für die angegebenen Klassen. Innerhalb dieses Headerfiles wird eine C-Struct deklariert, die alle notwendigen Felder enthält, die mit den Instanzfeldern der ursprünglichen Java-Klassen korrespondieren. Innerhalb dieser HeaderDatei wird ebenfalls bereits eine Funktion für jede native Methode definiert, die in der zugehörigen Quelldatei implementiert werden muss. Ein Aufruf von javah im klassischen HelloWorld-Programm in Java class HelloWorld { public static void main (String args[]) { System.out.println("Hello World!"); }}
wird die folgende Header-Datei erzeugen: /* DO NOT EDIT THIS FILE – it is machine generated */ #include /* Header for class HelloWorld */ #ifndef _Included_HelloWorld #define _Included_HelloWorld typedef struct ClassHelloWorld { char PAD;/* ANSI C requires structures to have a least one member */ } ClassHelloWorld; HandleTo(HelloWorld); #ifdef __cplusplus extern "C" { #endif #ifdef __cplusplus } #endif #endif
Auch so genannte Stub-Dateien, d.h. C-Dateien, die neben der HeaderDatei zusätzliche, notwendige Rumpffunktionen für die Einbindung von nativen Methoden in der Java-Umgebung enthalten, können von javah generiert werden. Bei Angabe der Option -stubs zur Erzeugung von StubDateien generiert bei unserem klassischen Java-HelloWorld-Programm eine .c-Datei, die wie folgt aussieht: Java 2 Kompendium
909
Anhang A /* DO NOT EDIT THIS FILE – it is machine generated */ #include <StubPreamble.h> /* Stubs for class HelloWorld */
Bei komplexeren Java-Programmen wird in demselben Maße das javahResultat entsprechend anspruchsvoller und nimmt Programmierern viel Arbeit ab. javah generiert standardmä ß ig eine C-Datei in dem aktuellen Verzeichnis,
deren Name identisch zu dem im Aufruf spezifizierten Klassenname ist. Wenn dieser Klassenname ein Paket enthält, so enthalten die C-Dateien sämtliche Komponenten des Paketnamens. Allerdings werden diese nicht durch Punkte, sondern durch Unterstriche getrennt. Die Syntax von javah: javah [Optionen] [Klassenname ]
Klassenname ist der Name der Java-Klassendatei ohne die Dateinamenserwei-
terung. Tabelle A.2: Die javah-Optionen
Option
Beschreibung
-help
Hilfe
-version
Die Version des JDK
-classpath [Verzeichnis]
Identisch mit den anderen Tools
-bootclasspath path Identisch mit den anderen Tools
910
-verbose
Identisch mit den anderen Tools
-d [Verzeichnis]
Mit dieser Option wird javah gezeigt, in welchem Verzeichnis sich die Header oder Stub-Dateien nach der Generierung befinden sollen. Normalerweise wird das aktuelle Verzeichnis verwendet. Diese Option und die Angabe der Option -o schließen sich aus.
-stubs
Diese Option veranlasst javah, Stub-Dateien statt der Standard-Header-Dateien zu erzeugen.
-o [Dateiname]
Mit dieser Option wird javah veranlasst, die Stubs- oder Header-Dateien in die mit Dateiname spezifizierte Datei (eine Datei, in der alle .h- oder .c-Dateien kombiniert werden) einzufügen. Bei der Datei kann es sich um eine normale Textdatei, um eine Header-Datei ([Dateiname].h) oder eine Stub-Datei ( [Dateiname].c) handeln.
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Option
Beschreibung
-jni
javah wird angewiesen, im erzeugten Header-File JNI-
Anhang A Tabelle A.2: Die javah-Optionen (Forts.)
style native method function prototypes zu generieren. -old
Gibt an, dass Header-Dateien nach dem alten JDK1.0Konzept erstellt werden sollen.
-force
Erzwingt die Erstellung einer Ausgabedatei.
Das Dokumentations-Tool javadoc Eines der am meisten unterschätzten Tools im JDK ist javadoc. Das JDK verfügt damit über ein feines, eigenes Dokumentations-Tool, das auf der Basis von speziellen Kommentar-Tags innerhalb einer Java-Quelldatei (es gibt drei Formen von Kommentaren in Java, hierfür wird die folgende Form benötigt: /** Kommentartext */ ) eine HTML-Datei als API-Dokumentation der angegebenen Datei oder des Paketes erzeugt. Vereinfacht gesagt, schreibt javadoc die innerhalb der Datei in den javadocKommentaren eingeschlossenen Texte kurzerhand in eine HTML-Datei. Darin können sich beliebige Informationen befinden. Es bleibt im Prinzip dem Programmierer überlassen, was er mit diesen Kommentaren über seinen Quelltext dokumentieren möchte. Außerdem kann man die Kommentare mit Steueranweisungen zur Erzeugung von Hyperlinks und speziellen Tags erweitern. Allerdings benötigt man dafür kein eigenes Tool, auch mit einem Editor ist dies halbwegs machbar. Es werden zusätzlich noch automatische Informationen über die Klassen, Interfaces, Vererbungshierarchien, Methoden, Variablen und Hyperlinks, die mit dem Java-Programm in Verbindung stehen können, aufgeführt. Die erzeugte Dokumentation enthält sämtliche als public und protected deklarierte Elemente. Javadoc wurde ursprünglich von den SUN-Entwicklern erstellt, um die Java-API-Dokumentation zu erzeugen. Die Leistungsfähigkeit dieses Tools wird besonders deutlich, wenn man Anspruch und Wirklichkeit bezüglich der Dokumentation in SoftwareProjekten gegenüberstellt. In der professionellen Software-Entwicklung sollte Planung bzw. Konzeption eines Projekts mit ca. 30 % oder mehr einkalkuliert werden (es kommt stark auf die Rahmenbedingungen an, aber die Grö ß enordnungen stimmen ungefähr). Die Realisierierung schlägt dann vielleicht mit 50 % zu Buche und der Rest (also ca. 20 %) sollte für die Dokumentation zur Verfügung stehen. Die Praxis sieht dann meist so aus, dass nach 30 % der Zeit mit der Realisierung zwar begonnen wird, die Planung und Konzeption aber noch nicht abgeschlossen ist und ständig Veränderungen in der Realisierungsphase bewirkt. Diese Phase benötigt dann
Java 2 Kompendium
911
Anhang A 90 % der Zeit1 und wo bleibt dann noch Kapazität für die Dokumentation? Oft wird sie als nicht so wichtig betrachtet. Je mehr Schitte der Dokumentation dann mit einem Tool erfolgen können, das automatisch die wichtigsten Details herausarbeitet, desto besser. In dieser Hinsicht ist javadoc vielleicht sogar das einzige Tool des JDK, das man in der Praxis nicht durch ergänzende oder alternative Tools ersetzt. Die zu dokumentierende Datei muss mindestens eine public oder protected deklarierte Klasse enthalten. Die Syntax von Javadoc: javadoc [Optionen] [Dateiname] [Paketname] [@files] Dateiname ist die .java-Datei bzw. der Paketname. Die Datei kann sowohl
ohne die Dateinamenserweiterung, als auch mit Erweiterung angegeben werden kann (leider wieder keine konsistente Syntaxlogik bzgl. der anderen JDK-Programme). Wenn ein Paketname angegeben worden ist, dann dokumentiert javadoc alle Java-Quelldateien innerhalb des aktuellen Verzeichnisses und anschließend das dazugehörige Paketverzeichnis (als Paketname zu verstehen und nicht etwa als ein physikalisches Verzeichnis). Für jede Klasse wird ein eigenes HTML-Dokument erzeugt und für die Klassen innerhalb des Paketes wird ein HTML-Index generiert. Standardmä ß ig werden also minimal vier HTML-Dateien (eine Index-Datei, [Name der Datei].html, packages.html und tree.html) generiert. Abbildung A.1: Aus einer JavaDatei werden per javadoc alle anderen Dateien generiert.
1
912
;-)
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Anhang A Abbildung A.2: Die Dokumentation erfolgt im Stil der Java-Online-Dokumentation.
In der ersten Version von javadoc gab es noch die unangenehme Eigenart, bei Aufruf ohne Parameter einfach anzufangen zu dokumentieren. Was? Das wußte javadoc ebenfalls nicht und so folgte zwangsläufig irgendwann eine Fehlermeldung. Durchaus vergleichbar mit einem Taxifahrer, der einfach losfährt, nachdem der Gast eingestiegen ist. Aber ohne nach dem Weg zu fragen. Erst ein paar Straßen weiter kommt dann die Rückfrage, wo es eigentlich hin gehen soll. Seit dem JDK 1.1.x ist das glücklicherweise vorbei und es wird eine Kurzbeschreibung mit allen Parametern auf dem Bildschirm ausgegeben. Die javadoc-Optionen Mittlerweile sind die Optionen des Dokumentations-Tools so umfangreich geworden, dass wir hier nur eine Zusammenfassung der wichtigsten Optionen angeben wollen. Diejenigen Optionen, die mit den bisher beschriebenen Optionen der anderen Tools übereinstimmern, haben auch die dort beschriebene Funktion. Das Dokumentations-Tool des JDK versteht u.a. folgende Optionen, die teilweise selbsterklärend sind und oft auch weggelassen werden können (für Details sei auf die Online-Dokumentation verwiesen): -1.1, -author, -bootclasspath, -bottom, -classpath, -d, -docencoding, -doclet, -docletpath, -doctitle, -encoding, -extdirs, -footer, -group, -header, -help, -helpfile, -J, -link, -linkoffline, -locale, -nodeprecated, -nodeprecatedlist, -nohelp, -noindex, -nonavbar, -notree, -overview, -package, -private, -protected, -public, -sourcepath, -splitindex, -stylesheetfile, -title, -use, -verbose, -version, -windowtitle
Java 2 Kompendium
913
Anhang A Das Dokumentations-Tool des JDK wurde für die Version 1.2 erheblich überarbeitet. Hier folgt eine Liste mit den wichtigsten Veränderungen: -use – Hinzufügen einer Cross-Referenzdokumentation (Beschreibung, dass das API andere APIs verwendet) -link und -linkoffline – Generierung von Links auf existierende Dokumentationen, die mit javadoc-generiert wurden -windowtitle – Angabe des Titels, der in das HTML -Tag geschrieben wird -doctitle – Titel der Seite (bisher die Option -title) -group – Unterteilung von Paketen in Gruppen.
Die Funktion eines Dokumentations-Tools muss sich naturgemäß von den übrigen Tools ein wenig unterscheiden. Insbesondere werden Querverweise (»siehe auch ...«) eine wichtige Rolle bei der Dokumentation spielen. Diese können jedoch nicht als Aufrufparameter übergeben werden, sondern müssen wie oben beschrieben Bestandteile des Quelltextes sein. Sie werden dort innerhalb der Kommentar-Tags mit einem speziellen Zeichen eingeleitet (die so genannten javadoc-Tags). Die Tags beginnen nach dem eigentlichen Kommentarbeginn alle mit dem Zeichen @. Die javadoc-Tags Tabelle A.3: Die javadoc-Tags
914
Tag
Beschreibung
@link
Setzt einen in-line-Link an diesem Punkt.
@version
Mit diesem Tag kann die Version des Programms spezifiziert werden.
@throws
Wie @exception.
@since
Spezifiziert, wann das Release erstellt wurde.
@serialField
Dokumentiert eine ObjectStreamField-Komponente eines serialPersistentFields-Mitglieds einer Serializable-Klasse.
@serialData
Beschreibt die Daten, die von der writeObject()- und der Externalizable.writeExternal()-Methode geschrieben werden.
@serial
Dokumentation eines serialisierbaren Defaultfeldes.
@see
Dieses Tag erzeugt einen Verweis in der HTML-Datei.
@return
Mit diesem Tag kann der Wert beschrieben werden, der von einer Methode zurückgegeben wird.
@param
Dieses Tag dient zu Dokumentation der Parameter.
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Tag
Beschreibung
@exception
Dieses Tag erzeugt ein Link auf Ausnahmen, die von der Klasse erzeugt werden.
@deprecated
Der Tag markiert ein Klasse, ein Interface, ein Feld oder eine Methode als nicht zu verwenden in weiteren Anwendungen. So gekennzeichneter Code wird dennoch weiter kompiliert und laufen, aber der Compiler – natürlich nur der einer neueren Generation – wird eine Warnung generieren, dass Sie Ihren Code bzgl. dieses Ausdrucks updaten sollten.
@author
Dieses Tag fügt den Namen des Autors in die HTML-Datei ein.
Anhang A Tabelle A.3: Die javadoc-Tags (Forts.)
Beispiel für einen Quelltext mit javadoc-Kommentar und javadoc-Tags: /** * Um was es geht. * @version3.42 * @authorRalph Steyer */ class HelloWorld { /* Kommentarform 2 – wird von javadoc nicht brücksichtigt*/ public static void main (String args[]) { System.out.println("Hello World!"); }}
Beachten Sie, dass der javadoc-Kommentar außerhalb der Klassendefinition steht. Dies ist wichtig, denn der Generator ignoriert den Kommentar bei einer Verschachtelung mit einer Klassendefinition. jdb - der Java-Debugger Als Nächstes soll der Java-Debugger jdb, das Debugging-Werkzeug des JDK, behandelt werden. Es handelt sich nicht um einen integrierten Debugger, wie er mittlerweile in den meisten Entwicklungsumgebungen üblich ist. Den Debugger können Sie nur über Befehlszeileneingaben steuern. Er verfügt nur über eine ähnliche Syntax wie die bekannten Unix-Debugger dbx oder gdb. Allerdings ist er in der Lage, neben den lokalen Dateien auch Dateien auf entfernten Systemen (Anwendungen und Applets) auf Fehler zu überprüfen. In diesem Fall müssen Sie die notwendigen -host und die -password-Optionen verwenden. Mit dem jdb können Sie beliebig Haltepunkte setzen und die Werte der Variablen ausgeben lassen. Steppen durch den Source – sprich das Programm in Einzelschritten auszuführen – wird ebenfalls unterstützt. Der Code in der Umgebung eines Haltepunktes kann jederzeit aufgelistet werden. Sie können jedes Mal, wenn das Programm angehalten oder unterbro-
Java 2 Kompendium
915
Anhang A chen wird, den Stack mit den Aufrufen untersuchen. Weiter unterstützt jdb die Bewegung durch die Threads in Ihrem Code. Exceptions sind ein weiterer Problemfall, wo Sie jdb unterstützt, indem er die Ausnahmen (auf Wunsch) auffängt, selbst wenn sie im Programm nicht aufgefangen werden. Erst einmal abgefangen, können Sie dann die Variablen und den Stack untersuchen. Im JDK 1.3 wurde der Debugger erheblich überarbeitet, sodass er in vielen Details nicht mehr mit seinem Vorgänger des JDK 1.2 übereinstimmt. Dafür kam im JDK 1.3 das Debugging-Tool oldjdb hinzu, was im Wesentliche dem Vorgänger des aktuellen jdb entspricht2. Dieses ist – wie alle mit old beginnenden Tools – für die Arbeit mit Quellcode vorhanden, der bestimmte Kriterien älterer Java-Versionen nutzt, die mit dem neuen Standard nicht vereinbar sind. Der neue Debugger jdb wurde gegenüber seinem Vorgänger bezüglich seiner erlaubten Optionen erheblich erweitert. Man muss trotz der unbestrittenen Leistungsfähigkeit von jdb festhalten, dass seine Bedienung wohl die meisten Wünsche im JDK offen lässt. Sie ist relativ umständlich. Außerdem setzt die Verwendung von jdb recht viel Hintergrundwissen voraus. Auch wer sich mit dem Debuggen schon auskennt, wird mit einem integrierten Debugger leichter zum Erfolg kommen. Vorbereitung einer Debugger-Sitzung Bevor Sie den Debugger verwenden, müssen Sie Ihre Klassen erst einmal entsprechend kompilieren, damit für den Debugger genügend Informationen zur Verfügung stehen. Dazu müssen Sie den Java-Compiler mit der Option -g ausführen. Die Syntax für die vorbereitende Kompilierung: javac -g [Name der Datei].java
Das Ergebnis dieser Kompilierung sollte ein nicht optimierter Bytecode sein, den jdb für den Zugriff auf die Symboltabellen für lokale Variablen benötigt. jdb hängt sich auf Grund dieser Information quasi in den Bytecode hinein. Eine jdb-Debugging-Sitzung lässt sich auf zwei (drei, wenn man Interpreter- und Appletviewer-Sitzung unterscheidet) verschiedene Arten starten. Sie können jdb veranlassen den Java-Interpreter mit den zu bearbeitenden Klassen im Debugging-Modus zu starten oder Sie können jdb starten und dazu auffordern, sich an einen laufenden Java-Interpreter (bzw. einen laufenden Appletviewer) anzuhängen. jdb versteht dieselben Optionen wie der Java-Interpreter. Sie können den Debugger also einfach anstelle des Interpreters aufrufen und sämtliche Optionen des Interpreters verwenden. 2
916
Sun gibt allerdings an, dass es kaum Gründe für die Verwendung dieser explizit abwärtskompatiblen Version gibt.
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Anhang A
Natürlich besitzt er aber auf Grund seiner weitergehenden Funktionen zusätzliche eigene Optionen. Auch hier gilt wieder, dass der Compiler zwingend die Erweiterung .java voraussetzt. jdb jedoch benötigt in der Folge die Erweiterung .class nicht zwingend. Es ist jedoch in diesem Fall auch kein Fehler, was wieder eine der vielen Inkosistenzen im JDK ist. Direkter Aufruf des Debuggers Eine Debugger-Sitzung kann man auf vielfältige Weise durchführen. Die üblichste Methode ist der direkte Aufruf des Debuggers mit der zu untersuchenden Klasse, die eine main()-Methode enthalten muss. Die eigentliche Syntax für den direkten Aufruf des Debugger ist nicht sonderlich umfangreich: jdb [Optionen] [KlassenName] oldjdb [Optionen] [KlassenName]
Die Optionen des Debuggers sind im JDK 1.3 gegenüber den Vorgängerversionen erheblich verändert und erweitert worden. Option
Beschreibung
-host [Hostname]
Diese Option informiert jdb bzw. oldjdb, auf welchem Rechner sich das entfernte Java-Programm befindet. Hostname steht dabei für den DNS-Namen des Remote-Computer.
-password [password]
Diese Option gibt das Passwort für die Java-Datei auf einem entfernten System, das vom Java-Interpreter mit der -Xdebug Option angegeben wurde, an jdb bzw. oldjdb weiter. Bei einer Arbeit auf einem entfernten Rechner ist ein solches Passwort oft zwingend. Außerdem muss das Passwort verwendet werden, wenn man den Debugger als Zweitprozess nach Start des Interpreters verwenden möchte.
-help
Hilfe (jdb bzw. oldjdb)
-version
Debuggerversion (jdb bzw. oldjdb)
-dbgtrace
Ausgabe von Informationen über jdb bzw. oldjdb
-sourcepath
Verzeichnisse, die durchsucht werden sollen (nur jdb)
Java 2 Kompendium
Tabelle A.4: Die Optionen von jdb und oldjdb im JDK 1.3
917
Anhang A Tabelle A.4: Die Optionen von jdb und oldjdb im JDK 1.3 (Forts.)
Option
Beschreibung
-attach
Anhängen an eine laufende VM an der spezifizierten Adresse mit dem Standard-Connector (nur jdb)
-listen
Warten auf eine laufende VM an der spezifizierten Adresse mit dem Standard-Connector (nur jdb)
-listenany
Warten auf eine laufende VM an einer beliebigen Adresse mit dem Standard-Connector (nur jdb)
-launch
Direkter Start statt auf das run-Kommando zu warten (nur jdb)
-connect :=, Connector mit dem aufgelisteten Argumenten auf... bauen (nur jdb). -thotspot
Die Applikation mit der im JDK 1.3 neu eingeführten Hotspot(tm) Performance Engine laufen lassen (nur jdb).
-tclassic
Die Applikation mit der klassischen VM laufen lassen (nur jdb).
Wenn Sie jdb direkt im Befehlszeilenmodus unter DOS (oder Xterm) mit der Angabe einer Klasse gestartet haben, wird die Ausgabe im entsprechenden Fenster etwa so aussehen: Initializing jdb...
Bis jetzt wurde der Code noch nicht ausgeführt. Deshalb sehen Sie im Ausgabefenster nicht viel mehr als diese Zeile. Wenn Sie jdb auf diese Weise benutzen, startet er den Java-Interpreter mit den jeweils in der Befehlszeile festgelegten Parametern, lädt die spezifizierte Klasse und hält an, bevor die erste ausführbare Anweisung der Klasse ausgeführt wird. Wie es weiter geht, werden wir gleich sehen. Vorher aber besprechen wir noch die weiteren Aufrufmöglichkeiten für den Debugger. Start des Debuggers als Folgeprozess des Interpreters Neben dem direkten Aufruf des jdb können Sie das Programm gleichermaßen mit dem Java-Interpreter starten und anschließend jdb als getrennten Prozess aufrufen, der Ihrem laufenden Programm dann hinzugefügt wird. Dieser Weg ist zwar nicht so üblich, wir wollen ihn dennoch skizzieren. Dazu müssen Sie den Interpreter mit der Option -Xdebug und Angabe der entsprechenden Klasse starten. Vor der Ausführung der angegebenen Klasse gibt java ein Passwort aus. Dieses Passwort muss dann als BefehlszeilenParameter für jdb benutzt werden und jdb wird sich dann mit diesem Pass-
918
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Anhang A
wort in den Prozess einklinken. Wenn Sie jdb und das Java-Programm auf zwei verschiedenen Rechnern laufen lassen wollen, müssen Sie zusätzlich beim Start von jdb die Option -host angeben. Die Syntax zum Aufruf des jdb nach Start des Java-Interpreters (in zwei Schritten): java -Xdebug [KlassenName] jdb -host [Hostname] -password [password]
Diese zweite Methode müssen Sie zum Debuggen des Programms verwenden, sofern Sie ein Betriebssystem haben, das die Eingabe nur über den Standard-Input bezieht. In diesem Fall besteht das Problem, dass jede Eingabe, die Sie eintippen, als Eingabe für jdb behandelt wird, wenn Sie jdb bereits gestartet haben. Bei der Benutzung der zweiten Methode können Sie zwei separate Eingabeströme verwenden: einen für Ihr Programm, einen für jdb. jdb zum Debuggen eines Applets starten Wenn Sie ein Applet mit jdb debuggen wollen, gehen Sie analog wie bei der zweiten Methode mit dem Interpreter vor. Sie starten nur in diesem Fall den Appletviewer aus der Befehlszeile mit der Option -debug3. Das Resultat ist jedoch sehr unterschiedlich gegenüber Methode 2. Es wird jdb gestartet, der wiederum den Appletviewer ohne weitere Aufrufe startet, jedoch anhält, bevor eine ausführbare Zeile im Appletviewer ausgeführt wird. Um die Ausführung im Appletviewer zu starten, müssen Sie run eingeben. Jedes Applet der HTML-Datei wird in einem einzelnen Fenster gestartet: Die Syntax zum Debuggen eines Applets: appletviewer -debug [Html-DateiName]
Wenn Sie den Appletviewer erst einmal in der Debug-Einstellung gestartet haben, antwortet jdb mit einigen Zeilen, die in etwa wie bei Methode 1 aussehen. Bis jetzt wurde der Code noch nicht ausgeführt. Deshalb sehen Sie im Ausgabefenster nicht viel mehr als diese Zeilen. Die jdb-Befehlszeile Egal wie Sie eine Debugger-Sitzung starten, in jedem Fall sollten Sie sich jetzt auf der Befehlszeilenebene des Debuggers befinden. Sie erkennen diess an dem jdb-Prompt, das auf Ihre Eingaben wartet – egal ob Sie eine Klasse geladen haben oder nicht. Sie können nur die diversen Kommandos an den Debugger geben, wobei diese zum einen erst einmal erkannt, aber auch in 3
Beachten Sie die Inkonsistenz zwischen -Xdebug beim Interpreter und -debug beim Appletviewer.
Java 2 Kompendium
919
Anhang A der richtigen Reihenfolge angewandt werden müssen. Wie schon angedeutet – der Debugger ist leistungsfähig, aber sehr unkomfortabel. jdb unterstützt folgende Kommandos für das Debuggen (Auflistung in alphabetischer Reihenfolge): Tabelle A.5: Die Kommandos an den Debugger
920
Kommando
Beschreibung
!!
Der zuletzt eingegebene Befehl wird nochmals wiederholt.
?
Erzeugt eine Liste aller gültigen Kommandos für den Debugger.
catch [exception]
Das Kommando bewirkt eine Unterbrechung des Programmablaufs, sobald die angegebene Exception ausgelöst wird. Wenn kein Parameter angegeben wird, erhält man eine Liste der Ausnahmen, die aufgefangen werden.
classes
Gibt die Namen und IDs der Klassen aus, die bereits geladen worden sind.
clear[class:line]
Entfernt Haltepunkte, die bei der angegebenen Klasse an der angegebenen Zeile gesetzt worden sind. Der Aufruf ohne Parameter generiert eine Angabe aller gesetzten Haltepunkte.
cont
Der aktuelle, unterbrochene Thread wird fortgesetzt (continue).
down[n]
Durch das Kommando wird der aktuelle Stack-Bereich eines Threads um n Frames nach unten gesetzt. Ohne Parameter wird der Frame um eine Einheit nach unten bewegt. Dazu gehört das umgekehrte Kommando up.
dump id(s)
Mit dump werden alle Felder (Werte der Variablen) einer Klasse oder der Instanz einer Klasse des angegebenen Objekts bzw. der Objekte angegeben, wenn Sie als id den Namen einer Klasse oder der Instanz einer Klasse angeben. dump gibt die Werte der Variablen der statischen Klasse aus, und dump this oder dump die Werte der Mitgliedsvariablen. Objekte und Klassen können sowohl durch Ihren Namen als auch durch eine achtstellige Hexadezimalzahl (ihrer Identifikator-ID) angegeben werden. Ebenso können Threads durch die Angabe der Kurzbezeichnung t@thread-number angegeben werden.
exit (oder quit)
Beenden des Debuggers
gc
Erzwingt die direkte Ausführung des Garbage Collectors.
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Kommando
Beschreibung
help
Identisch mit dem Kommando ?
ignore <exception>
Unterbindet die Ausführung einer Unterbrechung der angegebenen Ausnahme. Das bedeutet, sie wird aus der Ausnahmenliste des jeweiligen catch-Kommandos entfernt. Beachten Sie, dass der ignore-Befehl die spezifizierte Ausnahme nur im Fall des Debuggers ignoriert; der Java-Interpreter wird Sie weiterhin auffangen. ignore ohne Parameter listet die Ausnahmen in der catch-Liste auf.
list [Zeilennummer]
Zeigt den Quellcode an der angegebenen Zeilenummer an. Wenn keine Zeilennummer angegeben wird, wird die Zeile angegeben, die im aktuellen Frame des des aktuellen Threads steht. Um Quellcode-Zeilen aufzulisten, muss das Programm zunächst an einem Haltepunkt angehalten werden oder Sie müssen bereits einen Thread spezifiziert haben und diesen Thread dann unterbrechen. Andernfalls antwortet jdb mit einer Fehlermeldung (No thread specified or Current thread isn't suspended.)
Anhang A Tabelle A.5: Die Kommandos an den Debugger (Forts.)
Wenn ein Thread unterbrochen wurde, anstatt an einem Haltepunkt angehalten worden zu sein, müssen Sie den Zusammenhang mit dem Thread über den Thread-Befehl spezifisch festlegen. Sie müssen sich dann eventuell im Stack mit dem up-Befehl nach oben bewegen, um den Stack-Kontext in dem Code Ihres Programms festzulegen. Wenn sich die Quelle zu den Klassen, die Sie bearbeiten wollen, nicht im eingestellten Klassenpfad befindet (oder in dem Klassenpfad, der durch den Befehlszeilen-Parameter -classpath festgelegt wurde), kann jdb die Quelle nicht auflisten. Allerdings gibt es für diesen Fall den Befehl use, um einen Dateipfad zur Quelle festzulegen. load [Klassenname]
Die mit Klassenname angegebene Klasse wird in den Debugger geladen.
locals
locals gibt die lokalen Variablen des aktuellen Stack-
Bereichs aus. Dafür muss allerdings zwingend der Bytecode mit der Compileroption -g erzeugt worden sein. memory
memory zeigt den Speicherbedarf des aktuellen Pro-
gramms bis zum derzeitigen Zeitpunkt an. Sowohl der freie als auch der gesamte verfügbare Speicher wird angezeigt.
Java 2 Kompendium
921
Anhang A Tabelle A.5: Die Kommandos an den Debugger (Forts.)
Kommando
Beschreibung
methods
Durch diesen Befehl werden alle Methoden der angegebenen Klasse angezeigt.
print id(s)
Der Debugger zeigt mit diesem Befehl die Werte für eine angegebene Klasse, ein Objekt, ein Feld oder eine lokale Variable an. id kann sowohl ein Name als auch ein Identifikator – die Objekt-ID – sein. Die Objekt-ID einer Klasse wird beim Laden derselben angezeigt oder kann mit dem Kommando classes nachträglich abgefragt werden. Durch die Angabe der speziellen Syntax t@thread-number ist es möglich, sich auf spezielle Threads zu beziehen. Das Kommando print verwendet zur Ausgabe die Methode toString().
resume [thread1 ...threadN]
Veranlasst den Debugger, die angehaltenen Threads wieder fortzuführen. Wenn keine Parameter angegeben worden sind, werden alle mit dem Kommando suspend angehaltenen Threads wieder fortgeführt.
run [Klasse] [arg1...argN]
Dieses Kommando startet die Ausführung der angegebenen Klasse, d.h., es führt die main()-Methode der angegebenen Klasse aus. Die optionalen Parameter arg1 bis argN werden gegebenenfalls übergeben. Falls keine Argumente angegeben worden sind, wird die Klasse ausgeführt, die beim Start von dem Debugger angegeben worden ist.
step
Führt die aktuelle Zeile des aktuellen Threads aus und stoppt im Anschluss daran.
stop [at Klasse:Zeile] Setzt einen Haltepunkt in der angegebenen Zeile der
Klasse. Ohne Angabe der Argumente werden alle gesetzten Haltepunkte ausgegeben. stop [in Klasse:Zeile] Setzt einen Haltepunkt am Anfang der angegebenen
Methode der Klasse. Ohne Angabe der Argumente werden alle gesetzten Haltepunkte ausgegeben.
922
suspend [thread1...threadN]
Die angegebenen Threads werden unterbrochen. Falls kein Argument angegeben worden ist, werden alle aktiven Threads unterbrochen. Mit resume können die Zustände von Threads wieder geändert und die Threads wieder gestartet werden.
thread [thread]
Nach Aufruf des angegebenen Threads in diesem Kommando wird dieser zum aktuellen Thread.
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Kommando
Beschreibung
threadgroup [Name der threadgroup]
Wenn Sie sich einen Thread außerhalb der voreingestellten Thread-Gruppe ansehen wollen, müssen Sie zuerst die entsprechende Thread-Gruppe über den threadgroup-Befehl spezifizieren. Nach Aufruf dieses Kommandos ist die angegebene Thread-Gruppe die aktuelle Thread-Gruppe, und Sie können sich einen einzelnen Thread herauspicken.
threadgroups
Zeigt alle gerade aktiven Threadgruppen innerhalb eines Programms an. Sie werden mit aufsteigenden Nummern davor aufgelistet, gefolgt vom Klassennamen, wiederum gefolgt vom Handle der ThreadGruppe und dem Namen der Thread-Gruppe. Nur die Thread-Gruppen von Threads, die gestartet wurden, werden angezeigt.
threads [threadgroup1... threadgroupN]
Listet alle Threads in der angegebenen Gruppe auf. Wenn keine Gruppe angegeben worden ist, werden die Threads der aktuellen Gruppe angegeben. Threads werden mit vorangestellten Indexnummern aufgelistet. Der letzte Bestandteil einer Threadzeile ist der Zustand des Threads. Nur Threads, die bereits gestartet wurden, werden angezeigt.
up[n]
Durch das Kommando wird der aktuelle Stack-Bereich eines Threads um n Frames nach oben gesetzt. Ohne Parameter wird der Frame um eine Einheit nach oben bewegt. Dazu gehört das umgekehrte Kommando down.
use [Sourcefile Pfad]
Setzt den Pfad, den jdb verwenden soll, um nach eventuell benötigten Quellcodes zu suchen. Der Befehl use, ohne irgendwelche Parameter, veranlasst jdb dazu, den Pfad aufzulisten, den er gerade zum Finden des Quellcodes verwendet. use, gefolgt von einem Datei-Pfad, ändert den Pfad der Quelldatei auf den neu angegebenen Pfad. Die bei den anderen Tools verwendete Option -classpath hat eine ähnliche Bedeutung. jdb folgt dem spezifizierten Pfad, um den Quellcode zu finden.
where [thread] [all]
Bewirkt die Ausgabe des Stacks für den angegebenen Thread. Falls kein Thread angegeben worden ist, wird der Stack des aktuellen Threads angezeigt. Durch Angabe des Arguments all können alle Stacks angezeigt werden.
Java 2 Kompendium
Anhang A Tabelle A.5: Die Kommandos an den Debugger (Forts.)
923
Anhang A Ab der Version 1.2 unterstützt die Java Virtual Machine Low-Level-Services für das Debuggen. Die API für diese Low-Level-Services ist das Java Virtual Machine Debugger Interface (JVMDI). Deren detaillierte Behandlung sprengt jedoch den Rahmen des Buchs. In der JDK-Dokumentation finden Sie mehr dazu. jdb reagierte in der Version 1.2 (oldjdb tut es immer noch) ganz witzig auf einen Fehler bei der Befehlseingabe, wenn er einen Befehl nicht kennt: Auf > xit (Buchstabe vergessen) kommt folgende Antwort von jdb: huh? Try help... > Das Java Archive Tool (jar) Bereits ab der JDK-Version 1.1.1 können Java-Programme JAR-Dateien (Java-Archive mit der Erweiterung .jar) nutzen. Statt vieler einzelner und unkomprimierter Dateien kann man ein gepacktes Archiv verwenden und auch über Netze übertragen. Der Vorteil dieses Konzepts liegt bei Netzwerkaktionen darin. dass die Applikation in einer einzigen Transaktion übertragen wird und nicht in vielen einzelnen Übertragungsschritten. Außerdem wird die Datenmenge ziemlich komprimiert. JAR-Archive basieren auf der ZIP-Technologie und dem ZIP-Format. Zu der Theorie der Datenkomprimierung finden Sie einen kleinen Exkurs im Anhang C. Zum Erstellen der Java-Archive wird das Java Archive Tool jar genutzt. Man kann beliebig viele Java-Klassen und andere Ressourcen zu einer einzelnen JAR-Datei damit zusammenfassen. Die Syntax für jar: jar [Optionen] [manifest] [JAR-Datei] [Eingabedatei(en)]
Die Syntax besteht aus vier Bestandteilen nach dem Programmaufruf: Den Optionen. Der mit manifest bezeichneten Teil der Syntax gibt eine Datei an, die sämtliche Meta-Informationen über das Archiv enthält. Eine ManifestDatei wird immer automatisch vom jar-Tool generiert und ist immer der erste Eintrag in der .jar-Datei. Sie müssen diese Angabe nur dann machen, wenn Sie eine vom Defaultwert abweichende Datei als MetaInformationsdatei des Archives verwenden wollen. Andernfalls wird diese Meta-Informationsdatei mit Namen META-INF/MANIFEST.MF erstellt.
924
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Anhang A
JAR-Datei ist der Name des zu erstellenden oder zu dekomprimierenden Java-Archivs. Eingabedatei(en) bezeichnet die Datei(en), die komprimiert werden sollen. Dabei kann mit Wildcards gearbeitet werden (z.B. *.class für die Komprimierung alle Klassendateien im Verzeichnis in der angegebenen JAR-Datei). Die Dateien müssen beim Entkomprimieren nicht unbedingt angegeben werden.
Das jar-Tool ist in der Syntax den weit verbreiteten DOS-Komprimierungsprogrammen lha oder arj sehr ähnlich. Es gibt aber einen wichtigen Unterschied, denn die nachfolgenden Optionen sind immer gekoppelt. Dies bedeutet Folgendes: Eine der Optionen -{ctx} muss angegeben werden und wird dann meistens mit der entsprechenden weiteren Option aus [vfm0M] kombiniert. Dabei können zwar die Optionen aus den beiden Bereichen in der Reihenfolge vertauscht werden; sie dürfen aber nicht alleine verwendet werden, sondern nur in Verbindung mit zwei Optionen aus dem Trippel -{ctx}. Beispiele: jar -fc test.jar *.class jar -xf test.jar *.class
Dies ist etwas gewöhnungsbedürftig, jedoch wenn Sie sich nicht daran halten, kann es sogar passieren, dass eine Fehlermeldung (im günstigsten Fall) das Resultat ist oder sich das Tool sogar aufhängt (scheinbar). Wenn das Tool auf Befehlszeilenebene wartet, probieren Sie einmal die Tastenkombination STRG+Pause aus. Auf dem Standard-Ausgabegerät werden die Aktionen seitenweise ausgegeben. Optionen
Beschreibung
-c
Diese Option generiert ein neues oder leeres Archiv auf dem Standard-Ausgabegerät. Dies bedeutet, wenn Sie die Option alleine verwenden, füllt sich in der Regel der Bildschirm mit Sonderzeichen. Diese Option macht nur Sinn in Kombination mit einer anderen Option. Das folgende Kommando jar -fc test.jar *.class erstellt aus sämtlichen in dem Verzeichnis vorhandenen Dateien mit der Erweiterung .class ein JARArchiv test.jar.
-t
Listet eine Tabelle des Inhalts einer Datei auf dem StandardAusgabegerät auf. Allerdings macht diese Option nur Sinn in Kombination mit einer anderen Option, etwa jar -tf test.jar
Tabelle A.6: Die jar-Optionen
*.class
Java 2 Kompendium
925
Anhang A Tabelle A.6: Die jar-Optionen (Forts.)
Optionen
Beschreibung
-x [Datei]
Extrahiert alle Files oder die namentlich angegeben Dateien. Auch hier gilt, dass in Verbindung mit der weiteren Option -f die Hauptanwendung zu sehen ist.
-f
Nur als zweites Argument für die Aktionen Erstellen, Extrahieren oder Inhaltsangabe zu verwenden. Die Option spezifiziert die zu bearbeitende JAR-Datei.
-v
Generiert eine Statusausgabe auf das Standardausgabegerät. Die Option macht nur Sinn in Verbindung mit laufenden Aktionen, die mit anderen Optionen ausgelöst werden.
-m
Integriert Manifest-Informationen in die angegebene ManifestDatei.
-0
Nur Zusammenfassung von Dateien. Es wird keine ZIP-Kompression verwendet. Ein solches JAR-Archiv kann nicht im Suchpfad von Tools spezifiziert werden.
-M
Generiert keine Manifest-Datei.
-u
Update einer existierenden JAR-Datei, indem Dateien hinzugefügt werden. Beispiel: jar -uf mein.jar meineKlasse.class
-C
Wechselt Verzeichnisse während der Ausführung eines jarKommandos.
Wenn in der Angabe der Dateien ein Verzeichnis auftaucht, wird dieses Verzeichnis rekursiv verarbeitet.
Dieses Tool unterscheidet bei den Optionen zwischen Groß- und Kleinschreibung. Das Utility für jar-Konflikte – extcheck Zu den Basis-Tools zählt auch das Programm extcheck, ein Diagnose-Tool für JAR-File-Versionskonflikte. Damit können solche Probleme zwischen jeder JAR-Datei und den JAR-Dateien aufgedeckt werden, die als Erweiterung der JDK-Software installiert sind. Die Syntax für extcheck: extcheck [ -verbose ] targetfile.jar
Die Option -verbose listet alle JAR-Dateien auf, die überprüft wurden.
926
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
A.1.2
Anhang A
Internationalization Tools
Diese Art von Programmen umfasst nur das Programm native2ascii, das Text in Unicode nach der Norm Latin-1 konvertiert. Der Java-Compiler und andere Java-Tools können nur mit in Latin-1 und/oder Unicode-verschlüsselten (\udddd-Notation) Dateien arbeiten. Der Native-To-ASCII Converter konvertiert eine native-kodiertes Datei (nicht-Latin-1 und nicht-Unicode) in eine reine ASCII-Datei, das die \udddd-Unicode-Notation beinhaltet. Die umgekehrte Richtung funktioniert genauso. Syntax: native2ascii [Optionen] [inputfile [outputfile]]
Wenn die Output-Datei weggelassen wird, wird die Standard-Ausgabe verwendet. Wenn die Input-Datei weggelassen wird, wird die Standard-Eingabe verwendet. Option
Beschreibung
-reverse
Diese Option veranlassst native2ascii, Unicodeoder Latin-1-kodierten Text in native-kodierten Text zu übersetzen.
-encoding [Verschlüssel_name]
Spezifiziert den Verschlüsselungsnamen, der bei der Konvertierung verwendet wird. Dieser Verschlüssel_name-String muss als korrektes Argument der Klasse CharacterEncoding definiert sein.
Tabelle A.7: Die native2ascii Optionen
Hier ist eine kleine Liste mit möglichen Verschlüsselungsnamen: 8859_1 ISO Latin-1 8859_2 ISO Latin-2 8859_3 ISO Latin-3 8859_5 ISO Latin/Cyrillic 8859_6 ISO Latin/Arabic 8859_7 ISO Latin/Greek 8859_8 ISO Latin/Hebrew 8859_9 ISO Latin-5 Cp1250 Windows Eastern Europe / Latin-2 Cp1251 Windows Cyrillic Cp1252 Windows Western Europe / Latin-1 Cp1253 Windows Greek Cp1254 Windows Turkish Cp1255 Windows Hebrew Cp1256 Windows Arabic Cp1257 Windows Baltic Cp1258 Windows Vietnamese Cp437 PC Original
Java 2 Kompendium
927
Anhang A Cp737 PC Greek Cp775 PC Baltic Cp850 PC Latin-1 Cp852 PC Latin-2 Cp855 PC Cyrillic Cp857 PC Turkish Cp860 PC Portuguese Cp861 PC Icelandic Cp862 PC Hebrew Cp863 PC Canadian French Cp864 PC Arabic Cp865 PC Nordic Cp866 PC Russian Cp869 PC Modern Greek Cp874 Windows Thai EUCJIS Japanese EUC JIS JIS MacArabic Macintosh Arabic MacCentralEurope Macintosh Latin-2 MacCroatian Macintosh Croation MacCyrillic Macintosh Cyrillic MacDingbat Macintosh Dingbat MacGreek Macintosh Greek MacHebrew Macintosh Hebrew MacIceland Macintosh Iceland MacRoman Macintosh Roman MacRomania Macintosh Romania MacSymbol Macintosh Symbol MacThai Macintosh Thai MacTurkish Macintosh Turkish MacUkraine Macintosh Ukraine SJIS PC and Windows Japanese UTF8 Standard UTF-8
A.1.3
Security Tools
Das JDK beinhaltet diverse Sicherheitsprogramme. Diese Programme sind zum Setzen und Verwalten von Sicherheitspolicen auf Ihrem System gedacht. Sie können damit Applikationen entwickeln, die mit anderen Sicherheitspolicen zusammenarbeiten. keytool dient zum Schlüssel- und Zertifikat-Management. Es wird eine Datenbank mit privaten Schlüsseln und ihren zugehörigen X.509-Zertifikaten sowie den Zertifikaten von vertrauenswürdigen Entities verwaltet. Dieses Tool basiert auf dem DSA-Algorithmus mit der SHA-1 Signatur.
928
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Anhang A
jarsigner generiert oder verifiziert eine digitale Signatur für eine JAR-
Datei. policytool ist ein GUI-Tool (d.h. mit grafischer Oberfläche) für das Management von Policy-Dateien.
Das digitale Signierungs-Tool javakey verwaltet Datenbankentitäten, inklusive ihrer Schlüssel, Zertifikate und ihren Vertrauensebenen. Allerdings nach dem alten Java-Sicherheitsmodell. Dieses Tool ist als eine der bedeutendsten Neuerungen in der Version 1.2 durch die Programme keytool und jarsigner sowie policytool ersetzt worden. Mit dem Thema Verschlüsselung und Datensicherheit müssen wir ein Thema ansprechen, in dem Sun bei der Version 1.1 einen kleinen Flop landeten. Zwar ist Java auch hier mittlerweile erwachsen geworden und in der Version 1.1 kam mit javakey ein wichtiges Tool zum JDK. Dieses Programm hat sich allerdings als teilweise nicht voll befriedigend herausgestellt und ist als eine der bedeutendsten Neuerungen in der Version 1.2 durch die Programme keytool und jarsigner sowie policytool ersetzt worden. Wir wollen für die Besprechung der Hintergründe der Java-SicherheitsTools dennoch hauptsächlich javakey verwenden. Zum einen können wesentliche Hintergrundinformationen über Verschlüsselung und Datensicherheit daran recht gut erklärt werden und die Anwendung der neuen Programme ist weigehend analog. Zum anderen werden wohl noch einige Leser weiter mit javakey arbeiten wollen! Besonders diejenigen Anwender, die bereits mit javakey eine Datenbank an Schlüsseln aufgebaut haben, stehen sonst vor einem gewissen Problem. Die beiden Programme keytool und jarsigner ersetzen javakey zwar vollständig und bieten eine Fülle neuer Features (etwa Schutz der Datenbank und der privaten Schlüssel mit Passwörtern). Die beiden Programme sind jedoch ausdrücklich nicht abwärtskompatibel zu dem Keystore- und Datenbankformat, das von javakey in dem JDK 1.1 verwendet wurde. javakey javakey ist ein kommandozeilenorientiertes Sun-Sicherheits-Tool, dessen primäre Anwendung das Generieren von digitalen Signaturen für Archive ist. Eine Signatur verifiziert, dass eine Datei von einer bekannten Quelle kommt. Um eine Signatur für eine Datei zu generieren, muss zuerst ein öffentlicher Schlüssel und dann ein privater Schlüssel generiert werden, mit dem der öffentliche Schlüssel dann wieder dekodiert werden kann. javakey erstellt solche Schlüssel und eine Datenbank zur Verwaltung der Schlüssel und der zugeordneten Zertifikate, die den Status der Vertrauenswürdigkeit dokumentieren.
Java 2 Kompendium
929
Anhang A Die Syntax für javakey: javakey [Optionen]
Zu den Hintergründen und der Theorie der Datenverschlüsselung finden Sie einen Exkurs im Anhang. Sowohl javakey als auch die neueren Tools verwenden defaultmäß ig eine Verschlüsselung nach dem DSA-Standard (Digital Signature Algorithm) bzw. darauf aufbauenden Verfahren. Es gibt zwei Arten von Einträgen in der Datenbank, die von javakey verwaltet werden: Identities Signers Unter Identities (Identitäten) kann man sich jede Form juristischer Personen aus der realen Welt (Personen, Firmen, Organisationen) vorstellen. Diese haben von Ihnen einen öffentlichen Schlüssel bekommen. Weiter kann eine Verbindung zwischen dem öffentlichen Schlüssel einer Identity und einem oder mehreren Zertifikaten bestehen. Ein Zertifikat ist eine digitale Unterschrift dieser verwalteten juristischen Personen. Sie überprüft, von wem das mit dem öffentlichen Schlüssel kodierte Datenpaket stammt. Wenn also mehrere juristische Personen denselben öffentlichen Schlüssel zum Kodieren verwenden, können Sie durch die digitale Unterschrift eine eindeutige Zuordnung vornehmen. Signers sind juristische Personen, die zu einem öffentlichen Schlüssel auch einen passenden privaten Schlüssel haben. Um ein Datenpaket eindeutig zu signieren, müssen ein öffentlicher Schlüssel und ein passender privater Schlüssel vorhanden sein sowie ein Zertifikat zur Sicherstellung der Authentizität des öffentlichen Schlüssels. Wenn Ihnen jemand also ein Datenpaket zuschicken will, und Sie sollen es als vertrauenswürdig einstufen, dann läuft der Vorgang bei der Verwendung von einem Programm wie javakey normalerweise folgendermaßen ab.
930
1.
Mit dem Sicherheitsprogramm erstellen Sie einen öffentlichen Schlüssel und einen dazu passenden privaten Schlüssel. Beide werden vom Sicherheitsprogramm in einer Datenbank verwaltet.
2.
Das Objekt Ihrer Wahl aus der realen Welt (Personen, Firmen, Organisationen) bekommt den öffentlichen Schlüssel.
3.
Ein Zertifikat mit der speziellen digitalen Unterschrift für die jeweilige juristische Person wird ausgetauscht. Diese digitale Unterschrift kann von Ihnen mit dem Sicherheitsprogramm erstellt und dem Partner zugeschickt werden, sie kann aber auch vom Partner erstellt und Ihnen
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Anhang A
zugeschickt werden. In beiden Fällen tragen Sie mit dem Sicherheitsprogramm diese digitale Unterschrift in der Datenbank bei dem passenden Schlüsselpaar ein. 4.
Wenn Sie nun ein Datenpaket erhalten, können Sie den Absender auf Grund der digitalen Unterschrift identifizieren und nur Sie sind durch Ihren privaten Schlüssel in der Lage, das Datenpaket zu dekodieren.
Sie können in javakey wie in den meisten so arbeitenden Sicherheitsprogrammen sowohl Schlüssel und Zertifikate neu erstellen als auch bestehende Schlüssel importieren. Jede Datenbank benötigt zur Verwaltung einen internen Zugriffsschlüssel für die darin enthaltenen Datensätze. Alle Objekte (Identities und Signers) haben in der lokalen Datenbank einen von javakey verwalteten Username. Dieser Username ist der interne und eindeutige Schlüssel zum Verwalten der Datenbank. Das Hinzufügen eines Eintrags in die Datenbank mit der Option -c (create identity) oder -cs (create signer) ist eine Verwaltungsaktion, wo ein solcher Username vergeben wird. Alle javakey-Kommandos müssen anschließend diesen eindeutigen Username zur Referenz auf ein Objekt verwenden. Lassen Sie uns ein Beispiel – wieder unter der Verwendung von javakey – durchsprechen: Der Befehl javakey -cs safetyfirst true generiert einen als vertrauenswürdig eingestuften Signer und weist ihm den Username safetyfirst zu (Ausgabe: Created identity [Signer]safetyfirst[identitydb.obj][trusted]). Anschließend werden mit javakey ein öffentlicher und ein privater Schlüssel generiert. Für unser Beispiel soll ein Schlüssel mit 512 Bit nach DSA für diesen Signer generiert werden: javakey -gk safetyfirst DSA 512 javakey erlaubt es, bestimmte juristische Personen als vertrauenswürdig einzustufen. Der Appletviewer gestattete in dem ehemaligen Sicherheitskonzept jedem aus dem Netz geladenen Applet in JAR-Dateien, die als vertrauenswürdig eingestuft und mit diesem Tool entsprechend signiert wurden, mit denselben Rechten auf dem lokalen Rechner zu laufen wie eine lokale Applikation. Dies hatte extrem weitreichende Konsequenzen, denn ein solches Applet ist nicht mehr Bestandteil des »Laufstalls«, in den das Java Sicherheitsmodell Applets normalerweise zwingt. Wenn Sie also eine Organisation als prinzipiell vertrauenswürdig einstufen wollten, mussten Sie sich schon sehr sicher sein. Es gab übrigens bis zu den 1.1.x-Versionen nur die Möglichkeit »vertrauenswürdig« oder »nicht vertrauenswürdig«. Eine dif-
Java 2 Kompendium
931
Anhang A ferenziertere Abstufung war nicht möglich. Mittlerweile wurde eine solche weitergehende Differenzierung realisiert. Defaultwert ist »nicht vertrauenswürdig«. Wenn Sie also Einträge in der Datenbank vornehmen, können Sie folgende Vertrauensstufen setzen: javakey -cs safetyfirst true javakey -cs microsoft false javakey -cs cia
Fall eins steht für »vertrauenswürdig«, die anderen beiden für »nicht vertrauenswürdig«. Die Einträge können später natürlich wieder verändert werden. Klären wir nun, was Zertifikate sind. Mit den meisten Sicherheitsprogrammen sind Sie in der Lage, Zertifikate zu importieren, zu generieren, anzeigen und zu speichern. Ein Zertifikat ist eine digitale Unterschrift einer juristischen Person. Vereinfacht gesagt drückt ein Zertifikat nur aus, dass der öffentliche Schlüssel von einer anderen juristischen Person einen abweichenden Wert hat. javakey verwendet X.509-Zertifikate. Wenn Sie mit javakey ein Zertifikat generieren wollen, müssen Sie als Erstes eine Datei generieren, in die folgende Informationen gehören: Informationen über den Unterzeichner Informationen über die juristische Person selbst Informationen über das Zertifikat selbst Optional der Name des verwendeten Verschlüsselungsalgorithmus (falls Sie nicht DSA verwenden) Der Name der Datei, die eine Kopie des Zertifikats enthält Um ein Zertifikat zu generieren und eine Datei zu spezifizieren, verwenden Sie die Option -gc. Beispiel: javakey -gc bembelmaniaCertDirFile javakey wird ein Zertifikat generieren, indem es die Informationen verwendet, die in der angegebenen Datei abgelegt sind. Zusätzlich werden Informationen verwendet, die in der Datenbank vorhanden sind (öffentlicher und privater Schlüssel usw.).
Zum Anzeigen von Zertifikatinformationen verwenden Sie die Option -dc. Beispiel: javakey -dc certfile.cer
932
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Anhang A
Dies zeigt die Informationen über das Zertifikat an, die in der Datei certfile.cer abgelegt sind.
Zum Importieren von Zertifikaten verwenden Sie die Option -ic. Beispiel: javakey -ic joe jcertfile.cer Der Befehl importiert das Zertifikat in die Datei jcertfile.cer und ordnet es dem Username joe zu. Zum Exportieren von Zertifikaten verwenden Sie die Option -ec. Beispiel: javakey -ec jane 2 janecertfile.cer Der Befehl exportiert das zweite Zertifikat der mit dem Username jane bezeichneten juristischen Person in die Datei janecertfile.cer. Die Nummer des Zertifikats muss dieselbe Nummer sein, die bei der Generierung oder dem Import zugewiesen wurde. javakey kann JAR-Dateien auf jeden Fall mit DSA (Digital Signature Algorithm) signieren und verifizieren. In einigen Fällen funktioniert genauso der MD5/RSA-Algorithmus, was vom Typ der verwendeten Schlüssel des Signers und der Unterstützung durch den Provider abhängt.
Signieren einer JAR-Datei generiert für den angegebenen Signer eine digitale Unterschrift und schließt diese in die angegebene JAR-Datei ein. Dazu muss der Signer in der von javakey verwalteten Datenbank mit seinem Schlüsselpaar und einen X.509-Zertifikat vorhanden sein. Wie die Generierung eines Zertifikates basiert die Generierung einer digitalen Unterschrift auf den Anweisungen aus einer Datei, die das Profil eines Signers enthält. Wenn die JAR-Datei und die Anweisungsdatei generiert wurden, kann der javakeyBefehl zum signieren der JAR-Datei angewendet werden: javakey -gs directivefile jarfile directivefile ist der Name (und Pfad) der Anweisungsdatei, und jarfile ist der Name (und Pfad) der JAR-Datei.
Die Ausgabe dieses Befehls ist eine signierte JAR-Datei, deren Name von dem Wert in der Eigenschaft out.file spezifiziert wird, sofern dort ein Wert steht. Andernfalls wird der Name der signierten JAR-Datei identisch sein mit der Orginaldatei, aber mit der Erweiterung .sig. Auf die konkrete Auflistung aller Optionen von javakey wollen wir verzichten – immerhin ist das Tool nur noch für abwärtskompatible Arbeit sinnvoll. In der Dokumentation von Sun finden Sie aber mehr dazu. Beachten
Java 2 Kompendium
933
Anhang A Sie jedoch, dass Sie unter Umständen die Dokumentation eines 1.1.x-JDK laden müssen. keytool und jarsigner Wie bereits angedeutet, hat sich javakey nicht sonderlich bewährt und wurde deshalb durch einen Satz von Tools ausgetauscht. javakey dient indes immer noch als Basis für diese Tools. Viele Funktionalitäten und die Bedienung sind daran angelehnt oder gar identisch. Andere dagegen sind komplett neu. Zum Schlüssel- und Zertifikat-Management dient nun keytool. Wie bei javakey wird eine Datenbank (keystore) mit privaten Schlüsseln und ihren zugehörigen X.509-Zertifikaten sowie den Zertifikaten von vertrauenswürdigen Entities verwaltet. Auch dieses Tool basiert auf dem DSA-Algorithmus mit der SHA-1-Signatur. Die Schlüsselgrö ß e muss im Bereich zwischen 512 und 1024 Bits liegen, und muss ein Vielfaches von 64 sein. Die Defaultschlüsselgrö ß e ist 1024 Bits. Das jarsigner-Tool benutzt dieses Informationen, um eine digitale Signatur für eine JAR-datei zu generieren oder verifizieren. jarsigner verifiziert die digitale Signatur von einer JAR-Datei, indem das mitgeschickte Zertifikat dahingehend überprüft wird, ob der öffentliche Schlüssel vertrauenswürdig ist oder nicht (beispielsweise eine Übereinstimmung mit einem Eintrag im keystore). Das jarsigner-Tool unterstützt den DSA-Algorithmus und das RSA-Verfahren mit dem MD5-Algorithmus. Um es noch einmal zu betonen: Die beiden Programme keytool und jarsigner ersetzen javakey zwar vollständig und bieten eine Fülle neuer Features. Die beiden Programme sind jedoch ausdrücklich nicht abwärtskompatibel zu dem Keystore- und Datenbankformat, das von javakey in dem JDK 1.1 verwendet wurde. Wir werden Befehle und Optionen von keytool nur skizzieren. Mehr Informationen dazu finden Sie u.a. in der Dokumentation des JDK. Allen Befehlen und Optionen geht ein Minuszeichen voran. Die Optionen für die Befehle werden in jedem Befehl unterstützt. Klammern werden unterstützt. Klammern um eine Option bedeuten, dass der Defaultwert genutzt wird, wenn die Option in der Kommandozeile nicht spezifiziert wird. Klammern um eine Option bedeuten ebenfalls, dass der Anwender Werte eingeben kann, wenn die Option in der Kommandozeile nicht spezifiziert wird.
934
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Anhang A
Angaben in geschweiften Klammer bedeuten, dass der dort stehende Wert durch einen erlaubten Wert zu ersetzen ist. Beispiel: keytool -printcert {-file cert_file} {-v} Um ein Kommando einzugeben, muss cert_file mit dem aktuellen
Dateinamen ersetzt werden, etwa so: keytool -printcert -file VScert.cer
Optionswerte müssen mit einem Leerzeichen getrennt werden. Das Kommando -list ist die Defaulteinstellung und äquivalent zu dem Aufruf keytool. Wenn Sie keytool -list eingeben, erhalten Sie eine Auflistung der keytool-Befehle, der Optionen und der Syntax, auf die auch hier weiter verwiesen werden soll. Die Befehle und Optionen von jarsigner skizzieren wir ebenfalls nur grob. Mehr Informationen dazu finden Sie u.a. in der Dokumentation des JDK. Sie müssen mit dem Befehlsaufruf die Datei angeben, die Sie signieren oder verifizieren wollen. Wenn Sie jarsigner benutzen, um eine JARDatei zu signieren, wird die signierte JAR-Datei exakt identisch zu der Input-JAR-Datei sein, es sei denn, in dem META-INF-Directory sind zwei einander zugeordnete Dateien angegeben. Auch dazu finden Sie Informationen in der Dokumentation. Auch für die jarsigner-Optionen gilt, dass alle Optionen mit einem Minuszeichen beginnen. Die Optionen werden in den meisten Befehlen unterstützt. Angaben in geschweiften Klammer bedeuten, dass der dort stehende Wert durch einen erlaubten Wert zu ersetzen ist. Ein Beispiel soll das Signieren einer JAR-Datei demonstrieren. Die JARDatei heißt im Beispiel bundle.jar. Wir verwenden einen privaten Schlüssel mit dem Keystore-Aliasnamen (siehe dazu die Dokumentation) safety in dem Keystore mykeystore im daten-Directory. Das Keystore-Password soll mykeypass sein und das Passwort für den privaten Schlüssel abc. Mit diesen Voraussetzungen können Sie eine JAR-DAtei signieren und die signierte JAR-Datei sbundle.jar nennen: jarsigner -keystore "\daten\mykeystore" -storepass mykeypass -keypass abc signedjar sbundle.jar bundle.jar safety
Das Policytool Das Policytool ist eine grafische Benutzerschnittstelle (neben dem Appletviewer das einzige Programm des JDK mit GUI), die den Anwender beim Spezifizieren, Generieren, Editieren, Exportieren oder Importieren von einer Sicherheitspolice unterstützt. Damit kann unter Java auf fremde Hosts zugegriffen werden, ohne das Java-Sicherheitsmodell zu verlassen. Das Tool Java 2 Kompendium
935
Anhang A selbst ist weitgehend selbsterklärend und wird von der Kommandozeile mit policytool aufgerufen.
A.1.4
Java IDL- und RMI-Tools
Diese Programme dienen zum Generieren von Applikationen, die über das Web oder andere Netzwerken interagieren. Das bedeutet, es geht um die Unterstützung von Remote-Prozessen. Dabei kann entweder auf IDL und CORBA oder das RMI-Konzept zurückgegriffen werden. Im Wesentlichen geht es dabei um die Angabe einer gemeinsamen Schnittstelle für den Server und die Clients. Diese Schnittstelle wird vom Server implementiert, der sich bei einem Nameserver anmeldet. Von diesem können die Clients eine Referenz auf den Server erfragen, um ihn anschließend zu benutzen. Ziemlich einfach ist die Implementierung eines Clients: Der einzige Unterschied zu einem Client, der keine verteilten Objekte nutzt, ist das Holen einer Referenz auf den Server vom Nameserver. Das unter dem JDK 1.2 neu eingeführte Programm tnameserv unterstützt den Zugriff auf die benannten Services4. Neu in der Java-2-Plattform ist mit dem JDK 1.2 das Tool idltojava eingeführt worden. Dieses Tool generiert .java-Dateien, die ein OMG IDL-Interface mappen. Damit können in Java geschriebene Applikationen CORBA-Funktionalität nutzen. Dieses Tool war ursprünglich nicht direkt Bestandteil des JDK, sondern konnte von der Java IDLWebseite geladen werden. Im JDK ist ein solches Tool nun enthalten. Es heißt zwar idlj, hat jedoch die gleiche Funktionalität – es generiert aus der IDL-Metasprache Java-Schablonen für CORBA. Der Java RMI Stub Converter (rmic) generiert Stub-Dateien und Gerüststrukturen für so genannte Remote-Objekte (Remote Objects). Syntax: rmic [ Optionen ] package-qualified-class-name(s)
Das generierte Remote-Objekt ist ein Objekt, das die Schnittstelle java.rmi.Remote enthält. Die Stub-Dateien und Gerüststrukturen werden von den Namen der kompilierten Klassen, die Remote-Object-Implementationen enthalten, abgeleitet. Die im rmic-Kommando angegebenen Klassen müssen Klassen sein, die erfolgreich mit dem Java-Compiler javac kompiliert wurden und sie müssen vollständig qualifizierte Angabe bzgl. der Packages enthalten.
4
936
Die meisten der hier erklären Tools werden im Kapitel über die erweiterten Java-Techniken bei der Behandlung von CORBA verwendet.
Java 2 Kompendium
Ergänzungen zu den JDK-Tools
Anhang A
Beispiel: Das Kommando rmic hello.HelloImpl
generiert die Dateien HelloImpl_Skel.class und HelloImpl_Stub.class. Das Tool rmiregistry (Java Remote Object Registry) ist dafür da, auf dem angegebenen Port eines Hosts eine so genannte Remote Object Registry zu generieren und dann zu starten. Wie auch rmic – der Java RMI Stub Compiler – dient rmiregistry zur programmiertechnischen Umsetzung des RMI-Konzepts für die verteilte Programmierung. Das Tool rmid (Java RMI Activation System Daemon) ist in das RMIKonzept integriert und startet den so genannten Activation System Daemon, mit dessen Hilfe Objekte in einer JVM registriert und aktiviert werden können. Das dahinter liegende Konzept wird ActivationFramework genannt und erlaubt es beispielsweise, eine GIF-Datei als solche zu erkennen und ein passendes Objekt zu deren Bearbeitung zur Verfügung zu stellen. Syntax: rmid [-port port] [-log dir]
Für den Bereich der verteilten Programmierung (RMI) benötigt Java das Konzept der so genannten Object Serialization, auf dem das Tool serialver (Serial Version Command) aufbaut. Es ermöglicht das Abspeichern der Inhalte eines Objekts in einen Stream. Hauptanwendung hierfür ist das Versenden von Objekten über das Netzwerk im Zusammenhang mit dem RMI. Das Serialization-API wird benutzt, um die Daten eines Objekts in einen Datenstrom zu packen und anschließend, an anderer Stelle, wieder auszupacken. Dies kann aber auch im Prinzip eine beliebige Datei sein. Ein Objekt kann in einem Stream zwischengespeichert und zu einem späteren Zeitpunkt daraus wieder aufgebaut werden. Die Lebensdauer eines Objekts kann also über die eigentliche Laufzeit eines Programms hinaus verlängert werden. Dazu muss ein Java-Objekt normalerweise nur die Schnittstelle Serializable implementieren. In der einfachsten Form hat die Schnittstelle nur die Aufgabe einer Kennzeichnung. Die Serialisierung selbst wird in diesem Fall vom Laufzeitsystem vorgenommen. Sollte dies dem Programmierer nicht ausreichen, besteht die Möglichkeit, die gesamte Serialisierung des Objekts manuell durchzuführen. Das Tool serialver gibt u.a. die serialVersionUID für eine oder mehrere Klassen so zurück, dass man sie in eine konkrete Klasse kopieren kann.
Java 2 Kompendium
937
Anhang A
A.2
IDEs für Java
Grundsätzlich ist die Arbeit mit dem JDK ziemlich mühsam und unkomfortabel. Zwar wird das JDK kostenlos zur Verfügung gestellt und ein einfacher Texteditor genügt als Ergänzung (wir beschränken uns in dem Buch auch auf diese Konfiguration). In der Praxis wird man sich jedoch kaum damit zufrieden geben. Die Arbeit dauert einfach zu lange, ist zu fehleranfällig und vor allem muss man bei Standardvorgängen zu viele Dinge von Hand erledigen, die eine Entwicklungsumgebung mit wenigen Klicks auch kann. Es gibt mittlerweile zahlreiche kommerzielle IDEs für Java, die von wenigen Euro bis hin zu vierstelligen Beträgen kosten. Das ist vom Produkt und dem Hersteller, vor allem aber der Version des jeweiligen Tools abhängig. Die meisten IDEs gibt es in einer Grundversion sowie darauf aufbauenden umfangreicheren Versionen. Die Grundversionen unterstützen meist keine weitergehenden Techniken wie Datenbankanbindungen, JavaBeans, Servlets oder Netzwerktechniken. Sie sind aber für die einfachere Erstellung von Oberflächen meist ausreichend. Insbesondere gibt es von vielen Profi-Tools oft eine kostenlose Light-Version, die als Basis für die Arbeit mit Java bedeutend komfortabler ist als die Arbeit mit dem JDK direkt und einem einfachen Texteditor. Wir wollen hier keine kommerziellen Tools vorstellen, sondern einige kostenlose – oder zumindest über einen gewissen Zeitraum frei einsetzbare.
A.2.1
JCreator für Windows
Ein sehr brauchbarer, kompakter, in C++ geschriebener, Freeware-Editor für Java mit einem recht intuitiv bedienbaren User-Interface ist der JCreator von Xinox Software (http://www.jcreator.com). Der Editor ist insbesondere bei schwachbrüstigen Rechnern eine sehr gute Wahl, denn er benötigt keinen sonderlich leistungsfähigen Computer. Die keine zwei MByte große Installationsdatei wird als ZIP-File bereitgestellt. Die Installation vom JCreator ist unkompliziert. Das ZIP-Archive muss nur extrahiert werden. Danach steht Ihnen ein Windows-Standard-Setup zur Verfügung, in dem Sie wie üblich Verzeichnisse und ähnliche Details auswählen können. Der JCreator liefert kein JDK oder eigene Java-Tools mit und auch keine virtuelle Maschine. Deshalb muss der JCreator wissen, wo sich die virtuelle Maschine und die JDK-Tools befinden. Die Installationsroutine sucht danach.
938
Java 2 Kompendium
IDEs für Java
Anhang A Abbildung A.3: Hier gibt es den JCreator
Abbildung A.4: Der Download
Java 2 Kompendium
939
Anhang A Abbildung A.5: Gefundenes JDKVerzeichnis
Sie können auch individuell ein JDK-Verzeichnis auswählen. Abbildung A.6: Auswahl eines JDKVerzeichnisses
Das Programm kann dann nach der Installation ohne Neustart direkt aufgerufen werden. JCreator bietet ein Projektmanagement mit Projekt-Templates, Klassenbrowser, Syntax-Highlighting, Wizards und einem frei konfigurierbarem User-Interface. Mit dem JCreator kann direkt Java-Quelltext kompiliert
940
Java 2 Kompendium
IDEs für Java
Anhang A
und ausgeführt werden. Generell kann der Zugriff auf sämtliche Tools eines zugrunde liegenden JDK in die IDE integriert werden. Auch die Verwendung von verschiedenen Compiler-Tools ist möglich. Ansonsten bietet der Editor u.a. Zeilennummerierung, praktisch unbegrenztes Undo/Redo, Drag&Drop, gleichzeitige Arbeit mit verschiedenen Dokumenten, Autoeinrückung bei neuen Zeilen, Wortkomplettierung, automatische Kontrolle von externen Dokumentveränderungen und Lesezeichen. Abbildung A.7: Die Arbeitsoberfläche des JCreators
Ein kleiner Nachteil des JCreators bezüglich des Komforts ist, dass es sich nicht um ein visuelles Tool handelt, also eine IDE, wo Programme in gewissen Teilen per Maus erstellt werden können. Im Wesentlichen sind das zwar nur Komponenten der Oberfläche (bei einfachen Ausführungen), aber den Komfort einer IDE à la Visual Basic ist bei der Erstellung einer Applikation oder eines Applets auch nicht zu verachten. Auch solche Tools gibt es kostenlos, sogar bei Sun selbst. Der Preis für deren Verwendung ist jedoch der immense Bedarf an Ressourcen und die meist recht große Installationsdatei.
A.2.2
Forte 2.0 for Java
Eine visuelle IDE für das JDK, die in einer Grundversion von Sun frei zur Verfügung gestellt wird, ist Forte 2.0 for Java. Es gibt Versionen für Linux, Solaris und Windows sowie eine universelle Version für alle Plattformen, wo eine passende virtuelle Maschine vorhanden ist. Das Programm ist in Java geschrieben und funktioniert mit dem JDK ab der Version 1.3, das zusätzlich auf dem Rechner vorhanden sein muss. Sie können die ca.
Java 2 Kompendium
941
Anhang A 9 MByte große Installationsdatei (nur die IDE – ohne Java-Tools) in verschiedenen Versionen von den Sun-Servern unter der Adresse http:// www.sun.com/forte laden. Abbildung A.8: Hier gibt es Forte
Die IDE gibt es in verschiedenen Versionen, wobei die so genannte Community Edition kostenlos ist, was sich natürlich dann auch im Leistungsumfang zeigt. Grundsätzlich müssen Sie sich aber vor einem Download registrieren lassen, auch für die kostenlose Community-Version. Wenn Sie sich registriert haben, können Sie eine Download-Quelle und die gewünschte Version auswählen. Wenn Sie die Version für alle Plattformen laden, wird eine Java-class-Datei übertragen. Die Installation der Windows-Version des Programms ist unkompliziert, aber dennoch ein paar Erwähungen wert. Der Installationsvorgang ist Javabasierend, was sich auch optisch äußert.
942
Java 2 Kompendium
IDEs für Java
Anhang A Abbildung A.9: Auswahl der Version – die Community Editon ist kostenlos
Abbildung A.10: Die Registrierung ist unumgänglich.
Java 2 Kompendium
943
Anhang A Abbildung A.11: Download-Quelle und Plattformversion
Abbildung A.12: Ein Java-Installationsdialog
944
Java 2 Kompendium
IDEs für Java
Anhang A
Wie schon angedeutet, muss für die Arbeit mit der Forte-IDE mindestens ein JDK 1.3 (genau genommen eine passende virtuelle Maschine, aber auch die entsprechenden JDK-Tools wie der Compiler, wenn man aus der IDE heraus kompilieren möchte) zur Verfügung stehen. Der Installationsvorgang sucht selbstständig nach einem solchen JDK. Abbildung A.13: Gefundene JDKs ab einer JVM 1.3
Man kann aber auch – wenn mehrere JDKs ab der Version 1.3 installiert sind – selbst eine Version auswählen. Abschließend können alle Dateien mit der Endung .java mit dem Programm verknüpft werden. Das selbst in Java geschriebene Programm bietet eine visuelle Entwicklungsumgebung für Java mit diversen frei auf dem Bildschirm platzierbaren Fenstern. Von besonderer Bedeutung ist die Leiste mit Komponenten, mit der Sie per Mausklick AWT- und Swing-Elemente in einem grafischen Modus in ein Programm integrieren und mit passendem Eventhandling versehen können. Das wirkt sich natürlich auch in dem textbasierenden Modus aus, der synchron verwaltet wird und entsprechende Programmierung auf Quelltextebene erlaubt. Darüber hinaus finden Sie Debug-Unterstützung, Projektmanagement, einen Komponenteninspektor, Klassen- und Objektbrowser und zahlreiche weitere Möglichkeiten.
Java 2 Kompendium
945
Anhang A Abbildung A.14: Auswahl eines JDKs
Abbildung A.15: Verknüpfen der Dateierweiterung mit Forte
Zusammen mit dem Java-Editor wird auch ein JavaScript-Editor bereitgestellt.
946
Java 2 Kompendium
IDEs für Java
Anhang A Abbildung A.16: Der Arbeitsbereich von Forte
Abbildung A.17: Der JavaScriptEditor von Forte
Leider gilt für dieses Tool (wie für viele visuelle Entwicklungstools -besonders, wenn sie selbst in Java geschrieben sind), dass der Rechner nicht zu schwach sein sollte – vorsichtig ausgedrückt. Selbst bei einem Athlon 600 mit 256 Megabyte werden ungeduldige Naturen bei einigen Vorgängen nervös. Oder Kaffee holen. Da beißt die Maus keinen Faden ab. Das Tool ist kein Spielzeug und wer nicht ausreichend mit der Hardware ausgestattet ist, wird damit wenig Freude haben. Das gilt auch für das nachfolgend behandelte Tool JBuilder. Java 2 Kompendium
947
Anhang A
A.2.3
JBuilder
Von dem Hersteller des legendären Turbo Pascals Inprise (früher Borland) gibt es ein kommerzielles Java-Entwicklungstool namens JBuilder. Eine Lightversion (die Version JBuilder Foundation – die Installationsdatei im ZIP-Format ist für die Version 3.5 ca. 28 MByte groß) wird kostenlos zur Verfügung gestellt, wenn man sich bei Inprise registrieren lässt (http:// www.borland.com). Abbildung A.18: Ein auf Java basierender Installationsassistent installiert den JBuilder.
Diese Registrierung ist notwendig, denn beim ersten Start des installierten Programms muss man den von Inprise zugesandten Schlüssel zur Freischaltung eingeben. Abbildung A.19: Ohne Registrierung startet der JBuilder nicht.
948
Java 2 Kompendium
IDEs für Java
Anhang A
Dazu betätig man den ADD...-Button und erhält ein passendes Eingabefenster. Abbildung A.20: Beim ersten Start werden diese Angaben notwendig.
Der JBuilder bietet in der Foundation-Version einen weitgehend mit Forte 2.0 vergleichbaren Leistungsumfang. Besonderes Highlight ist die Verknüpfung von grafischem und textbasierendem Programmiermodus. Änderungen im Textmodus wirken sich sofort – sofern davon berührt – im grafischen Modus und umgekehrt aus. Auch diverse Schablonen und Wizards machen das Leben leichter. Abbildung A.21: Die Arbeitsoberfläche des JBuilders.
Insbesondere gilt für beide visuellen Tools, dass die Programmierung des Eventhandlings damit zum Kinderspiel wird. Der Doppelklick auf eine Komponente generiert bereits eine vollständige Event-Schablone zum Standardereignis der Komponente und auch die Auswahl anderer Ereignisse lässt sich mithilfe eines Komponenten-Inspektors mühelos realisieren. Marketing-Trick von Inprise ist die deselektierte Anzeige all der Leistungen, die in der Foundation-Version des JBuilders nicht funktionieren.
Java 2 Kompendium
949
Anhang A Abbildung A.22: Einfachste Generierung von Eventmethoden per Mausklick – auch Forte bietet einen solchen Inspektor
Abbildung A.23: Appetitthappen auf die kommerziellen Versionen des JBuilders
950
Java 2 Kompendium
IDEs für Java
A.2.4
Anhang A
Kawa
Abschließend soll noch das Entwicklungstool Kawa erwähnt werden. Eine längere Zeit war das Tool unter http://www.tek-tools.com zu laden. Es ist jedoch mittlerweile an die Firma Allaire übergegangen, von deren Seiten es nun zu laden ist (http://www.allaire.com/products/kawa). Abbildung A.24: Hier gibt es Kawa.
Vor einem Download muss man sich registrieren lassen (siehe Abbildung A.25). Danach können Sie zwischen verschieden Versionen für den Download auswählen. Die Benutzerführung dahin ist ziemlich unglücklich, denn es wird nur indirekt deutlich, wie man die kostenlose Evaluation laden kann. Diese darf zudem nur 30 Tage verwendet werden. Sie müssen mindestens in der einfachsten Version eine 15 MByte große Installationsdatei laden, die dann nach der Installation mindestens 46 MByte Platz auf der Festplatte verbraten wird (die Enterprise-Version multipliziert die Eckdaten nochmals um einige Faktoren). Die Installation von Kawa erfolgt inklusive der Extrahierung vollautomatisch. Beim ersten Start von Kawa müssen Sie unbedingt den Pfad zum JDK – insbesondere zum Compiler – setzen. Sie werden von Kawa darauf automatisch hingewiesen (siehe Abbildung A.26).
Java 2 Kompendium
951
Anhang A Abbildung A.25: Registrierung
Abbildung A.26: Hinweis, dass der Compiler spezifiziert werden muss
Wenn dann das Programm installiert ist, steht Ihnen – auch schon in der Evaluation-Variante – eine übersichtliche und intuitiv zu bedienende IDE zur Verfügung. Dennoch – die gigantischen Installationsdateien und der immense Platzbedarf auf der Festplatte (im Vergleich zu der Leistung der Evaluation-Version und anderen, kostenlosen Java-Tools) sowie die zeitliche Einschränkung lässt einen Download von Kawa nur dann empfehlenswert erscheinen, wenn man danach die kommerzielle Variante wählt.
952
Java 2 Kompendium
IDEs für Java
Anhang A Abbildung A.27: Der wichtigste Konfigurationsdialog von Kawa
Abbildung A.28: Der Compiler wird angegeben.
Java 2 Kompendium
953
Anhang A Abbildung A.29: Die KawaOberfläche
954
Java 2 Kompendium
B
Anhang B
Sie finden in diesem Anhang einen allgemeinen Überblick über das Standard-API des JDK 1.3. Dies bedeutet, die darin enthaltenen Klassen sind auf jeden Fall in jeder Java-Implementierung vorhanden. Dieser Abschnitt ist insbesondere deshalb interessant, weil sich nur so die ganzen 1.1- und 1.2Erweiterungen/Veränderungen von Java nachvollziehen lassen. Das JavaAPI ist mittlerweile jedoch so umfangreich geworden, dass wir einfach eine Auswahl treffen müssen. Wir werden uns also auf eine allgemeine Übersicht beschränken, was bedeuten soll, dass für eine vollständige Information über die einzelnen Klassen, Schnittstellen, die enthaltenen Felder, Konstruktoren und Methoden sowie die Ausnahmen auf weiterführende Literatur und vor allem die 1.3-APIDokumentation der Java-Webseite von Sun (http://java.sun.com) verwiesen sei. Auch einige Unterpakete der einzelnen Java-Hauptpakete werden nur knapp angerissen. Was tun wir dann aber überhaupt hier in diesem Kapitel? Nun, Sie werden sehen, dass die Übersicht auch ohne diese Angaben der Abschnitt ziemlich umfangreich ist. Sie finden hier: Die Paket-Hierarchie des Java-API 1.3 Die Veränderungen der Paket-Struktur zwischen den APIs vor dem JDK 1.2 (inklusive Beta 3) und den Nachfolgern Eine Beschreibung der Standardpakete des Java-API 1.3 Bei den meisten Paketen eine Beschreibung wichtiger Klassenbestandteile. Dabei werden wir die Schnittstellen und Klassen teils vollständig aufführen, teils eine Auswahl. Ausnahmen und Errors werden nur in wichtigen Spezialfällen aufgeführt. Die veralteten Elemente des Java-API 1.3. Dazu soll dieser Anhang einige wichtige Tabellen rund um Java enthalten. Insbesondere zu HTML, aber auch zu JavaScript.
Java 2 Kompendium
955
Anhang B
B.1
Das Java-1.3-API
Das SDK 2-API wurde erheblich gegenüber dem 1.1-API erweitert. Es hat sich aber nicht nur zwischen der Version 1.1 mit den diversen Zwischenversionen und der Version 1.2 verändert. Gravierende Modifikationen fanden über die einzelnen Betaversionen der 1.2-API statt. Das API wurde in der Struktur teilweise vollkommen umsortiert. Wir stellen daher zuerst das 1.2API der Beta 2/3 dem Final direkt gegenüber. Die nachfolgende Tabelle stellt die einzelnen Pakete des SDK 2-StandardAPI dar. Dabei wird die bisherige Paketstruktur der neuen Struktur gegenübergestellt. Die Zuordnungsnummer in der ersten Spalte soll die Orientierung erleichtern, wenn Sie ein Paket der einen Version in der jeweils anderen Struktur suchen. »*« steht dafür, dass eine eindeutige Zuordnung zu einem Paket in der anderen Struktur nicht eindeutig möglich ist. Entweder ist die Funktionalität dort nicht vorhanden oder es ist auf andere Pakete aufgeteilt. Tabelle B.1: Die Pakete des SDK 2 gegenüber Vorgängerversionen
956
Nr.
1.2-API Beta
1.2-API Final
1
java.applet
java.applet
2
java.awt
java.awt
3
java.awt.accessibility
52
4
java.awt.color
java.awt.color
5
java.awt.datatransfer
java.awt.datatransfer
6
java.awt.dnd
java.awt.dnd
7
java.awt.event
java.awt.event
8
java.awt.font
java.awt.font
9
java.awt.geom
java.awt.geom
10
java.awt.im
java.awt.im
11
java.awt.image
java.awt.image
12
*
java.awt.image.renderable
13
java.awt.print
java.awt.print
14
java.awt.swing
53
15
java.awt.swing.basic
59
16
java.awt.swing.beaninfo
*
Java 2 Kompendium
Das Java-1.3-API
Anhang B
Nr.
1.2-API Beta
1.2-API Final
17
java.awt.swing.border
54
18
java.awt.swing.event
56
19
java.awt.swing.jlf
*
20
java.awt.swing.motif
*
21
java.awt.swing.multi
61
22
java.awt.swing.plaf
58
23
java.awt.swing.table
62
24
java.awt.swing.target
*
25
java.awt.swing.text
63
26
java.awt.swing.tree
67
27
java.awt.swing.undo
68
28
java.beans
java.beans
29
java.beans.beancontext
java.beans.beancontext
30
java.io
java.io
31
java.lang
java.lang
32
java.lang.ref
java.lang.ref
33
java.lang.reflect
java.lang.reflect
34
java.math
java.math
35
java.net
java.net
36
java.rmi
java.rmi
37
java.rmi.activation
java.rmi.activation
38
java.rmi.dgc
java.rmi.dgc
39
java.rmi.registry
java.rmi.registry
40
java.rmi.server
java.rmi.server
41
java.security
java.security
42
java.security.acl
java.security.acl
43
java.security.cert
java.security.cert
44
java.security.interface
java.security.interfaces
Java 2 Kompendium
Tabelle B.1: Die Pakete des SDK 2 gegenüber Vorgängerversionen (Forts.)
957
Anhang B Tabelle B.1: Die Pakete des SDK 2 gegenüber Vorgängerversionen (Forts.)
958
Nr.
1.2-API Beta
1.2-API Final
45
java.security.spec
java.security.spec
46
java.sql
java.sql
47
java.text
java.text
48
java.util
java.util
49
java.util.jar
java.util.jar
50
java.util.mime
*
51
java.util.zip
java.util.zip
52
3
javax.accessibility
53
14
javax.swing
54
17
javax.swing.border
55
*
javax.swing.colorchooser
56
18
javax.swing.event
57
*
javax.swing.filechooser
58
22
javax.swing.plaf
59
*
javax.swing.plaf.basic
60
*
javax.swing.plaf.metal
61
21
javax.swing.plaf.multi
62
23
javax.swing.table
63
25
javax.swing.text
64
*
javax.swing.text.html
65
*
javax.swing.text.html.parser
66
*
javax.swing.text.rtf
67
26
javax.swing.tree
68
27
javax.swing.undo
69
org.omg.CORBA
org.omg.CORBA
70
*
org.omg.CORBA.DynAnyPackage
71
org.omg.CORBA.ContainedPackage
*
72
org.omg.CORBA.ContainerPackage
*
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Nr.
1.2-API Beta
1.2-API Final
73
org.omg.CORBA.InterfaceDefPackage
*
74
org.omg.CORBA.ORBPackage
org.omg.CORBA.ORBPackage
75
org.omg.CORBA.portable
org.omg.CORBA.portable
76
org.omg.CORBA.TypeCodePackage
org.omg.CORBA.TypeCodePackage
77
org.omg.CosNaming
org.omg.CosNaming
78
org.omg.CosNaming.NamingContextPackage
org.omg.CosNaming.NamingContextPackage
Tabelle B.1: Die Pakete des SDK 2 gegenüber Vorgängerversionen (Forts.)
Die große Umstrukturierung bei der Einführung der Java-2.0-Plattform sollte nach Aussage von Sun im Funktionsumfang der Kernplattform weitgehend komplett sein. Das kann man zwar bestätigen, wenn man die Betonung auf Kernplattform legt. Wenn man sich aber das API des JDK 1.3 betrachtet, wird man eine Vielzahl an neuen Paketen entdecken. Glücklicherweise sind diese aber Erweiterungen und beinhalten keine Umstrukturierungen, wie bei dem unglücklichen Wechsel vom JDK 1.2 Beta auf das JDK 1.2. Neu in dem API des JDK 1.3 sind folgende Pakete: java.awt.im.spi javax.naming javax.naming.directory javax.naming.event javax.naming.ldap javax.naming.spi javax.rmi javax.rmi.CORBA javax.sound.midi javax.sound.midi.spi javax.sound.sampled javax.sound.sampled.spi javax.transaction org.omg.CORBA_2_3 org.omg.CORBA_2_3.portable org.omg.SendingContext org.omg.stub.java.rmi
B.2
Beschreibung der Pakete
Hier folgt nun eine Beschreibung der Pakete, die zum offiziellen Umfang von SDK 2 mit dem JDK 1.3 zählen. Beachten Sie bitte, dass wir bei den Klassen und Schnittstellen oft nur eine Auswahl angeben, wenn die jeweiligen Überschriften dies aussagen.
Java 2 Kompendium
959
Anhang B
B.2.1
java.applet
Dieses Package beinhaltet die appletspezifischen Elemente, die ein Applet braucht, um mit seiner Umwelt zu kommunizieren. Schnittstellen Tabelle B.2: Schnittstellen
AppletContext
Methoden zum Zeigen auf den Applet-Kontext
AppletStub
Methoden zum Implementieren eines Appletviewers
AudioClip
Methoden zum Abspielen von Audiodateien
Klassen Tabelle B.3: Klassen
Applet-Basisklasse
Applet
B.2.2
java.awt
Dieses Paket enthält die Klassen und Schnittstellen des Abstract Window Toolkit. Seit der Version 1.0 haben sich gerade hier massive Erweiterungen und Veränderungen ergeben. Schnittstellen Tabelle B.4: Schnittstellen
960
ActiveEvent
Methoden für Ereignisse, die sich selbst ausführen können (SDK 2)
Adjustable
Methoden für Objekte mit einstellbaren numerischen Werten, um einen Bereich von akzeptablen Werten festzulegen (Java 1.1)
Composite
Methoden zum Erstellen von einem neuen, indivdiuell zusammengestellten 2D-Image (SDK 2)
CompositeContext
Methoden, um die Umgebung von einem indivdiuell zusammengestellten 2D-Image zu definieren (SDK 2)
EventSource
Methoden für Objekte, die Ereignisse generieren (Java1.1-Beta-Interface – schon im Finalrelease und erst recht nicht mehr im SDK 2 oder gar 1.3 vorhanden)
ItemSelectable
Methoden für Objekte, die einen Satz von Einträgen enthalten, wo kein, einer oder mehrere Einträge ausgewählt werden können (Java 1.1)
LayoutManager
Methoden, um Komponenten in einem Container zu platzieren
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
LayoutManager2
Minimale Repräsentation von Methoden in einem Bereich von Komponenten (basierend auf constraints), die exakt festlegen, wo eine Komponente in einem Container platziert werden soll (Java 1.1)
MenuContainer
Methoden für Menü-bezogene Container (die Methode postEvent() gilt seit SDK 2 als verworfen)
Paint
Methoden zum Definieren von gefüllten Bereichen für 2DZeichen-Operationen (SDK 2)
PaintContext
Methoden zum Definieren von der Umgebung von einer individuellen 2D-Zeichen-Operation (SDK 2)
PrintGraphics
Methoden für die Bereitstellung eines grafischen Kontexts zum Drucken (Java 1.1)
Shape
Methoden für Objekte, die geometrische Formen darstellen (neu eingeführt in Java 1.1 und als Teil von SDK 2-2DImage-Funktionalität überarbeitet)
Stroke
Methoden, um die Fläche von einem gezeichneten Strich in grafischen 2D-Operationen darzustellen (SDK 2)
Transparency
Methoden zum Definieren von verschiedenen Transparenz-Zuständen (SDK 2)
Tabelle B.4: Schnittstellen (Forts.)
Klassen AlphaComposite
Basisregeln für Alpha-Komposition
AWTEvent
Ursprung aller AWT-Ereignisse (Java 1.1)
AWTEventMulticaster
Ein Multicast-Ereignis-Absender (Java 1.1)
AWTPermission
Ein Mittel zum Erstellen von Zugriffsregeln, die den Zugriff auf AWT-Ressourcen kontrollieren (SDK 2)
BasicStroke
Ein gezeichneter Strich (Basisformat) (SDK 2)
BorderLayout
Ein Layoutmanager für die Anordnung von Komponenten in die vier Kompass-Richtungen (die Methode addLayoutComponent() gilt seit Java 1.1 als verworfen).
Button
Eine Schaltfläche
Canvas
Ein Zeichenbereich.
CardLayout
Ein Layoutmanager für die Anordnung von Komponenten in einem Kartenlayout (die Methode addLayoutComponent() gilt seit Java 1.1 als verworfen)
Checkbox
Ein Kontrollfeld.
Java 2 Kompendium
Tabelle B.5: Klassen
961
Anhang B Tabelle B.5: Klassen (Forts.)
CheckboxGroup
Eine Gruppe von sich gegenseitig ausschließenden Kontrollfeldern (Optionsfelder) (die Methoden getCurrent() und setCurrent() sind seit Java 1.1 verworfen)
CheckboxMenuItem
Umschaltbare Menüoption
Choice
Auswahlmenü (die countItems()-Methode gilt seit Java 1.1 als verworfen)
Color
Abstrakte Darstellung einer Farbe in einem standardisieten RGB-Farbarrangement oder einem alternativen ColorSpaceobject
Component
Der Ursprung aller Benutzerschnittstellen-Komponenten, die nicht Teil eines Pulldown-Menüs sind (zahlreiche Methoden gelten seit Java 1.1 als verworfen: action(), bounds(), deliverEvent(), disable(), enable(), getPeer(), gotFocus(), handleEvent(), hide(), inside(), keyDown(), keyUp(), layout(), locate(), location(), lostFocus(), minimumSize(), mousedown(), mousedrag(), mouseEnter(), mouseExit(), mouseMove(), mouseUp(), move(), nextFocus(), postEvent(), preferredSize(), reshape(), resize(), show() und size())
ComponentOrientation
Ausrichtung von Text in Komponenten (neu im JDK 1.2)
Container
Eine Benutzerschnittstellen-Komponenten, die andere Komponenten und Container enthalten kann (einige Methoden gelten seit Java 1.1 als verworfen: countComponents(), deliverEvent(), insets(), layout(), locate(), minimumSize() und preferredSize())
962
Cursor
Ein bitmapped Mauscursor (Java 1.1)
Dialog
Ein Dialogfeld für eine einfache Benutzerinteraktion
Dimension
Eine Repräsentation von der Höhe und Breite einer Komponente oder eines anderen Objekts
Event
Ein Objekt, das die Ereignisse in dem Java 1.0.2-Eventhandling-Modell repräsentiert (ersetzt in 1.1 und 1.2, aber aus Gründen der Abwärtskompatibilität weiter vorhanden)
EventQueue
Eine Liste von Ereignissen, die darauf warten, abgearbeitet zu werden (Java 1.1)
FileDialog
Ein Dialogfenster für Dateioperationen
FlowLayout
Ein Layoutmanager zum Anordnen von Objekten in Spalten von links nach rechts
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Font
Eine Repräsentation einer Schriftart
FontMetrics
Abstrakte Klasse mit Informationen über eine bestimmte Schriftart (Form, Höhe, usw.) (die getMaxDecent()Methode ist seit Java 1.1 verworfen und ersetzt durch getMaxDescent())
Frame
Ein top-level-Fenster mit Titel und Rand (die Methoden getCursorType() und setCursor() sind seit Java 1.1
Tabelle B.5: Klassen (Forts.)
verworfen; zahlreiche Variablen sind seit SDK 2 verworfen: CROSSHAIR_CURSOR, DEFAULT_CURSOR, E_RESIZE_CURSOR, HAND_CURSOR, MOVE_CURSOR, N_RESIZE_CURSOR, NE_RESIZE_CURSOR, NW_RESIZE_CURSOR, S_RESIZE_CURSOR, SE_RESIZE_CURSOR, SW_RESIZE_CURSOR, TEXT_CURSOR, W_RESIZE_CURSOR und WAIT_CURSOR) GradientPaint
Eigenschaften für das Zeichnen eines Gradientenpatterns innerhalb einer Form (SDK 2)
Graphics
Eigenschaften zur Darstellung von Grafiken und zum Erstellen von Zeichnungen und Formen (die Methode getClipRect() gilt seit Java 1.1 als verworfen)
Graphics2D
Eine Erweiterung von Graphics, die speziell die 2DGestaltung ermöglicht (SDK 2)
GraphicsConfigTemplate
Überprüfung einer gültigen GraphicsConfiguration
GraphicsConfiguration
Die physikalischen Charakteristiken eines grafischen Ausgabegeräts (SDK 2)
GraphicsDevice
Ein grafisches Ausgabegerät – beispielsweise ein Drucker oder ein Monitor (SDK 2)
GraphicsEnvironment
Eine Repräsentation von allen grafischen Ausgabegerät und font-Objekten, die in der aktuellen Umgebung vorhanden sind (SDK 2)
GridBagConstraints
Arrangement einer individuellen Zelle in einem GridBagLayout-Arrangement
GridBagLayout
Ein Layoutmanager, der Komponenten in einem Rasterlayout arrangiert (auf Grundlage der Werte in GridBagConstraints)
GridLayout
Ein Layoutmanager, der Komponenten in einem Rasterlayout arrangiert – die Elemente werden in die Zellen des Gitters eingefügt.
Image
Eine Repräsentation eines Bitmap-Bildes
Java 2 Kompendium
963
Anhang B Tabelle B.5: Klassen (Forts.)
Insets
Abstrakte Darstellung von Einsätzen, d.h. dem äußeren Rand eines Containers
JobAttributes
Attribute zur Kontrolle eines Druckjobs (JDK 1.3)
JobAttributes.DefaultSelectionType
Eine typensichere Enumeration von möglichen Defaultaufwahlzuständen (JDK 1.3)
JobAttributes. DestinationType
Eine typensichere Enumeration von möglichen Jobzielen (JDK 1.3)
JobAttributes. DialogType
Eine typensichere Enumeration von möglichen Dialogen für einen User (JDK 1.3)
JobAttributes. MultipleDocumentHandlingType
Eine typensichere Enumeration von möglichen Verhaltensweisen bei mehreren Dokumenten (JDK 1.3)
JobAttributes. SidesType
Eine typensichere Enumeration von möglichen Verhaltensweise bei mehreren Seiten (JDK 1.3)
Label
Beschriftung für Elemente einer Benutzeroberfläche
List
Ein scrollbares Listenfeld einer Benutzeroberfläche (Einige Methoden sind seit Java 1.1 verworfen: allowsMultipleSelections(), clear(), countItems(), delItems(), isSelected(), minimumSize(), preferredSize() und setMultipleSelections(); daneben sind die Methoden addItem() und delItem()seit SDK 2 als verwor-
fen gekennzeichnet)
964
MediaTracker
Ein Weg, um den Status von Medienobjekten zu verfolgen, die über das Netz geladen wurden
Menu
Ein Set von Pulldown-Menüs, die mit einer MenuBar verbunden werden (die Methode countItems() gilt seit Java 1.1 als verworfen)
MenuBar
Ein Container für Menüs (die countMenus()-Methode gilt seit Java 1.1 als verworfen)
MenuComponent
Der Ursprung von allen Menüelementen (die Methoden getPeer() und postEvent() sind seit Java 1.1 verworfen)
MenuItem
Einzelne Einträge in einem Pulldown-Menü (die Methoden disable() und enable() sind seit Java 1.1 verworfen)
MenuShortcut
Ein Tastatur-Shortcut für die Selektion von einem Pulldown-Menüeintrag (Java 1.1)
PageAttributes
Ein Satz von Attributen zur Kontrolle der Ausgabe einer gedruckten Seite (JDK 1.3).
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
PageAttributes.ColorType
Eine typensichere Enumeration von möglichen Farbzuständen (JDK 1.3)
PageAttributes.MediaType
Eine typensichere Enumeration von möglichen Papiergrö ß en (JDK 1.3).
PageAttributes. OrientationRequestedType
Eine typensichere Enumeration von möglichen Ausrichtungen (JDK 1.3).
PageAttributes.OriginType
Eine typensichere Enumeration von möglichen Quellen (JDK 1.3)
PageAttributes.PrintQualityType
Eine typensichere Enumeration von möglichen Druckqualitäten (JDK 1.3)
Panel
Ein anzeigbarer Container
Point
Eine Repräsentation von einem Punkt in (x, y)-Koordinaten
Polygon
Vieleck (die Methoden getBoundingBox() und inside() sind seit Java 1.1 verworfen)
PopupMenu
Ein Popup-Menü an einer spezifizierten Stelle (Java 1.1)
PrintJob
Eigenschaften zum Beginnen und Durchführen einer Druckoperation (Java 1.1).
Rectangle
Ein Rechteck, das durch die obere Ecke mit der (x, y)Koordinate plus Breite und Höhe repräsentiert wird (die Methoden inside(), move(), reshape() und resize() sind seit Java 1.1 verworfen).
RenderingHints
Neu in 1.2: Rendering-Hints, die u.a. von Graphics2D genutzt werden können
RenderingHints.Keys
Neu in 1.2: Schlüssel zur Kontrolle von Rendering- und Bildprozessen
Robot
Eine Klasse zum Generieren von nativen Systemen für Eingabe-Events. Dies kann zum Testen von automatischen Vorgängen, Demos usw. verwendet werden (JDK 1.3).
Scrollbar
Eine Bildlaufleiste in einer Benutzerschnittstelle (einige Methoden sind seit Java 1.1 verworfen:,
Tabelle B.5: Klassen (Forts.)
getLineIncrement(), getPageIncrement(), getVisible() setLineIncrement() und setPageIncrement())
Java 2 Kompendium
965
Anhang B Tabelle B.5: Klassen (Forts.)
ScrollPane
Ein Container, der sich um das horizontale und vertikale Scrollen für eine einzelne Child-Komponente kümmert, sofern es notwendig ist (Java 1.1; die layout()-Methode gilt seit Java 1.1 als verworfen)
SystemColor
Die Farben, die auf dem Betriebssystem des Anwenders zur Verfügung stehen (Java 1.1)
TextArea
Ein rechteckiger, mehrzeiliger Textbereich – editierbar und scrollbar (einige Methoden sind seit Java 1.1 verworfen : appendText(), insertText(), minimumSize(), preferredSize() und replaceText())
TextComponent
Der Ursprung von allen editierbaren Textkomponenten
TextField
Ein rechteckiger, einzeiliger Textbereich – editierbar (die Methoden minimumSize(), preferredSize(), und setEchoCharacter() sind seit Java 1.1 verworfen).
TexturePaint
Eigenschaften zum Zeichen von Text in einer Form (SDK 2)
Toolkit
Abstrakte Eigenschaften zum Binden der abstrakten AWT-Klassen an eine plattformspezifische Implementation
Window
Ein Fenster auf der obersten Ebene in einer Benutzerschnittstelle (die postEvent()-Methode gilt seit Java 1.1 als verworfen)
B.2.3
java.awt.color
Dieses Paket (neu eingeführt im SDK 2) beschreibt verschiedene Systeme um Farben zu identifizieren und die Konvertierung zwischen den Systemen zu ermöglichen. Klassen Tabelle B.6: Klassen
966
ColorSpace
Das spezifische Farbsystem eines Color-Objekts, eines Bilds oder eines Graphics-Objekts
ICC_ColorSpace
Ein Farbsystem – basierend auf der International Color Consortium (ICC) Profile Format Specification
ICC_Profile
Eine Repräsentation von Farbprofildaten von der ICCSpecification
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
ICC_ProfileGray
Eine Teilmenge von Profilen, beispielsweise für monochrome Eingabe- und Ausgabegeräte
ICC_ProfileRGB
Eine Teilmenge von Profilen, beispielsweise für Eingabeund Ausgabegeräte, die auf dem 3-Komponenten-RGBKonzept basieren
B.2.4
Tabelle B.6: Klassen (Forts.)
java.awt.datatransfer
Dieses Paket (vorhanden seit Java 1.1) erlaubt den Datenaustausch von Informationen innerhalb eines oder verschiedener Programme mittles Cut, Copy und Paste. Dazu wird ein Speicherbereich genutzt, der Clipboard oder Zwischenablage genannt wird. Nahezu jeder Anwender von Windowsprogrammen kennt diese Technik. Schnittstellen ClipboardOwner
Methoden für Objekte, die Daten in einem Clipboard speichern
FlavorMap
Mappen von nativen Stringtypen in MIME-Stringtypen
Transferable
Methoden für Objekte, die die Daten in einem Clipboard nutzen können
Tabelle B.7: Schnittstellen
Klassen Clipboard
Eigenschaften, um Informationen über ein Clipboard zu übertragen
DataFlavor
Eine Repräsentation von einem Datenformat, das den Datenaustausch über ein Clipboard verwenden kann
StringSelection
Eigenschaften, um Java-Strings als einfachen Text über ein Clipboard zu übertragen
SystemFlavorMap
Mappen von nativen Stringtypen in MIME-Stringtypen und zugehörigen DataFlavors
B.2.5
Tabelle B.8: Klassen
java.awt.dnd
Dieses Paket (neu eingeführt im SDK 2) behandelt beide Seiten einer Drag&Drop-Operation.
Java 2 Kompendium
967
Anhang B Ausgewählte Schnittstellen Tabelle B.9: Schnittstellen
DragSourceListener
Methoden für den Urheber von Drag&Drop-Operationen, um die zugehörigen Ereignisse zu behandeln
DropTargetListener
Methoden für den Empfänger von Drag&Drop-Operationen, um die zugehörigen Ereignisse zu behandeln
Ausgewählte Klassen Tabelle B.10: Schnittstellen
DnDConstants
Konstanten zur Behandlung einer Drag&Drop-Operation
DragSource
Urheber einer Drag&Drop-Operation
DragSourceContext
Umgebung des Urhebers, um die Drag&Drop-Aktion zu dokumentieren und unterstützen
DragSourceDragEvent
Ereignisse, auf die der Urheber bei einer Drag-Operation reagieren kann
DragSourceDropEvent
Ereignisse, auf die der Urheber bei einer Drop-Operation reagieren kann
DragSourceEvent
Parent von Dragsource-Ereignissen
DropTarget
Empfänger einer Drag&Drop-Operation
DropTargetContext
Umgebung des Empfängers, um die Drag&Drop-Aktion zu dokumentieren und unterstützen
DropTargetDragEvent
Ereignisse, auf die der Empfänger bei einer Drag-Operation reagieren kann
DropTargetDropEvent
Ereignisse, auf die der Empfänger bei einer Drop-Operation reagieren kann
DropTargetEvent
Parent von Droptarget-Ereignissen
B.2.6
java.awt.event
Dieses Paket (vorhanden seit Java 1.1) stellt ein vollständig überarbeitetes Ereignisbehandlungssystem dar.
968
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Schnittstellen ActionListener
Methoden, um auf action-Ereignisse zu reagieren
AdjustmentListener
Methoden, um auf adjustment-Ereignisse zu reagieren
AWTEventListener
Reaktion auf Ereignisse von Component oder MenuComponent (inkl. Subklassen)
ComponentListener
Methoden, um auf component-Ereignisse zu reagieren
ContainerListener
Methoden, um auf container-Ereignisse zu reagieren
FocusListener
Methoden, um auf Ereignisse zu reagieren, die vom Focus einer Tastatur herrühren
HierarchyBoundsListener
Empfang von Bewegungs- und Grö ß enveränderungsereignissen, die von Vorgängerobjekten ausgelöst werden (JDK 1.3)
HierarchyListener
Empfang von Veränderungsereignissen, die von Vorgängerobjekten ausgelöst werden (JDK 1.3).
InputMethodListener
Methoden, um auf Ereignisse einer Eingabemethode zu reagieren (SDK 2)
ItemListener
Methoden, um auf Ereignisse von Menüeinträgen zu reagieren
KeyListener
Methoden, um auf Tastatur-Ereignisse zu reagieren
MouseListener
Methoden, um auf Maus-Ereignisse zu reagieren
MouseMotionListener
Methoden, um auf Ereignisse einer Mausbewegung zu reagieren
TextListener
Methoden, um auf Text-Ereignisse zu reagieren
WindowListener
Methoden, um auf Fenster-Ereignisse zu reagieren
Tabelle B.11: Schnittstellen
Ausgewählte Klassen ActionEvent
Ein Komponenten-definiertes action-Ereignis
AdjustmentEvent
Ein adjustment-Ereignis, generiert von einem regulierbaren Objekt
ComponentAdapter
Abstrakte Klasse für die Erstellung von neuen Komponentenreaktions-Klassen
ComponentEvent
Ein component-Ereignis
Java 2 Kompendium
Tabelle B.12: Klassen
969
Anhang B Tabelle B.12: Klassen (Forts.)
ContainerAdapter
Abstrakte Klasse für die Erstellung von neuen Containerreaktions-Klassen
ContainerEvent
Ein Container-Ereignis
FocusAdapter
Abstrakte Klasse für die Erstellung von neuen Klassen, die auf Tastaturfokus regieren
FocusEvent
Ein Tastaturfokus-Ereignis
HierarchyBoundsAdapter
Abstrakte Adapterklasse für das zugehörige Hierarchieereignis (JDK 1.3)
HierarchyEvent
Abstrakte Adapterklasse für das zugehörige Hierarchieereignis (JDK 1.3)
InputEvent
Ein Komponentenzustand-Eingabeereignis
ItemEvent
Ein Item-Ereignis, das von einem selektierbaren Objekt generiert wurde
KeyAdapter
Abstrakte Klasse für die Erstellung von neuen Tastaturreaktions-Klassen
KeyEvent
Ein Tastatur-Ereignis
MouseAdapter
Abstrakte Klasse für die Erstellung von neuen Mausreaktions-Klassen
MouseEvent
Ein Maus-Ereignis
MouseMotionAdapter
Abstrakte Klasse für die Erstellung von neuen Klassen, die auf Mausbewegung reagieren
PaintEvent
Ein Komponentenzustand-Zeichnen-Ereignis
TextEvent
Ein Text-Ereignis-Objekt, das von einem passenden Objekt – beispielsweise TextComponent – generiert wurde
WindowAdapter
Abstrakte Klasse für die Erstellung von neuen Fensterreaktions-Klassen
WindowEvent
Ein Fenster-Ereignis
B.2.7
java.awt.font
Dieses Paket (neu eingeführt im JDK 1.2) unterstützt die Zusammenstellung, Anzeige und individuelle Gestaltung von Schriftarten.
970
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Dieses Paket wurde gegenüber seinen Vorgängern in den Betaversionen erheblich verändert (was leider unzureichend dokumentiert wurde). So finden Sie dort beispielsweise die folgenden Klassen nicht mehr: GlyphSet, StyledString, StyledStringIterator Schnittstellen MultipleMaster
Methoden zur Unterstützung von Type-1-Multiple-Master-Schriftarten
OpenType
Methoden zur Unterstützung von TrueType- und OpenType-Schriftarten
Tabelle B.13: Schnittstellen
Ausgewählte Klassen GlyphJustificationInfo
Einstellbare Eigenschaften eines Gestaltungstyps
GlyphMetrics
Abmessungen des Gestaltungstyps
TextAttribute
Attribute, die für das Textlayout benötigt werden
TextHitInfo
Informationen über Abstand und exakte Einfügeposition von Zeichen in Text
TextLayout
Ein grafische Repräsentation von gestalteten Strings
B.2.8
Tabelle B.14: Klassen
java.awt.geom
Hierbei handelt es sich um ein Paket (neu eingeführt im SDK 2) zur Unterstützung von 2D-Geometrie. Schnittstellen PathIterator
Methoden für Pfadwiederholungen
Tabelle B.15: Schnittstellen
Ausgewählte Klassen AffineTransform
Affine 2D-Transformation (die Methoden prepend() und append() sind seit SDK 2 verworfen)
Arc2D
Ein Bogen, definiert von einem begrenzenden Rechteck, Startwinkel, Winkelgrad, und Abschlusstyp
Arc2D.Float
Ein Bogen, definiert mit Gleitkommagenauigkeit (float)
Area
Eine Flächengeometrie
Java 2 Kompendium
Tabelle B.16: Klassen
971
Anhang B Tabelle B.16: Klassen (Forts.)
CubicCurve2D
Kubisch parametrisiertes Kurvensegment im (x, y)-Koordinatenraum
CubicCurve2D.Float
Kubisch parametrisiertes Kurvensegment mit float-(x, y)Koordinaten
Dimension2D
Eine Repräsentation einer Dimension in Höhe und Breite
Ellipse2D
Ellipse, definiert von begrenzendem Rechteck
Ellipse2D.Float
Ellipse, spezifiziert mit float-Genauigkeit
FlatteningPathIterator
Abgeflachter Blick eines anderen PathIterator-Objekts
GeneralPath
Geometrischer Pfad, konstruiert von geraden Linen und quadratischen, sowie kubischen Kurven
Line2D
Liniensegment im (x,y)-Koordinatenraum.
Line2D.Float
Liniensegment mit float-(x,y)-Koordinaten
Point2D
Punkte im (x,y)-Koordinatenraum
Point2D.Double
Punkte mit double-(x,y)-Koordinaten
Point2D.Float
Punkte mit float-(x,y)-Koordinaten
QuadCurve2D
Quadratische Kurvensegmente im (x,y)-Koordinatenraum
QuadCurve2D.Float
Quadratische Kurvensegmente mit float-(x,y)-Koordinaten
Rectangle2D
Rechtecke, definiert von (x,y)-Position und Dimension
Rectangle2D.Double
Rechtecke mit double-Koordinaten
Rectangle2D.Float
Rechtecke mit float-Koordinaten
Rectangular.Shape
Formen, die einen rechteckigen Satz von Außenbedingungen registrieren
RoundRectangle2D
2D-Rechtecke mit bogenförmigen Ecken
RoundRectangle2D. Float
2D-Rechtecke mit bogenförmigen Ecken mit float-Koordinaten
B.2.9
java.awt.im
Hierbei handelt es sich um ein Paket (neu eingeführt im SDK 2), das ganz wesentlich die neue Internationalisierung von Java unterstützt. Unterstützt werden Eingabemethoden, die tausende von verschiedenen Zeichen interpretieren können.
972
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Schnittstellen InputMethodRequests
Methoden, die Text-editierenden Komponenten erlauben, mit Eingabemethoden zu arbeiten
Tabelle B.17: Schnittstellen
Klassen InputContext
Eigenschaften zum Verwalten der Kommunikation zwischen Eingabemethoden und Text-editierenden Komponenten
InputMethodHighlight
Eingabemethode, die Text in highlight-Style darstellt
InputSubset
Zusätzliche Unicode-Definitionen
B.2.10
Tabelle B.18: Klassen
java.awt.im.spi
Neu im JDK 1.3 eingeführt wurde dieses Paket. Es stellt Interfaces für die Entwicklung von neutralen Eingabemethoden bereit (in jedem Java-Runtime-Environment zu verwenden). InputMethod
Unterstützung für komplexe Texteingaben
InputMethodContext
Unterstützung für Eingabemethoden, die mit ihren Clientkomponenten oder anderen Diensten kommunizieren wollen
InputMethodDescriptor
Unterstützung von diversen Zusatzinformationen für die Eingabemethoden
B.2.11
Tabelle B.19: Schnittstellen
java.awt.image
Hierbei handelt es sich um ein Paket zur Handhabung von Bitmap-Bildern. Dieses Paket wurde gegenüber den Vorgängern vom JDK 1.2 (inklusive der Betaversionen) erheblich umstrukturiert. So fehlen zahlreiche Schnittstellen und Klassen, die dort noch vorhanden waren.
Java 2 Kompendium
973
Anhang B Schnittstellen Tabelle B.20: Schnittstellen
BufferedImageOp
Methoden, um Operationen auf BufferedImage-Objekten zu beschreiben (SDK 2)
ImageConsumer
Methoden zum Empfangen von Bilddaten, die von einem Bilderzeuger (ImageProducer) erstellt wurden
ImageObserver
Methoden zum Verfolgen des Ladens und Aufbauens von einem Bild
ImageProducer
Methoden zum Aufbauen und Filtern von Imagedaten, die von einem ImageConsumer-Objekt empfangen werden
RasterOp
Methoden zum Beschreiben von Operationen auf RasterObjekten (SDK 2)
RenderImage
Methoden für Objekte, die Raster produzieren oder enthalten können (SDK 2)
TileObserver
Methoden, um zu beobachten, wenn Kacheln von einem WritableRenderedImage-Objekt modifiziert werden können (SDK 2)
WriteableRenderedImage
Methoden für Objekte, die Bilder produzieren oder enthalten, die modifiziert oder überschrieben werden können (SDK 2)
Klassen Tabelle B.21: Klassen
974
AffineTransformOp
Affine Transformation zur Unterstützung von linearem Mapping von einem Bild oder Raster in ein anderes (SDK 2)
AreaAveragingScaleFilter
Filter zum Skalieren von Bildern (SDK 2)
BandCombineOp
Willkürliche lineare Kombinationen von Rasterbändern, wobei zum Spezifizieren Matrizen genutzt werden (SDK 2)
BandedSampleModel
Eigenschaften zum Sampeln von Bildern (SDK 2)
BufferedImage
Images mit einem zugänglichen Puffer von Daten (SDK 2; die Methode getGraphics() gilt seit SDK 2 als verworfen)
BufferedImageFilter
Filter, die gepufferte Bilddaten von ImageProducer-Objekten nehmen und eine modifizierte Version an ImageConsumer-Objekte weitergeben (SDK 2)
ByteLookupTable
Nachschlagetabellen für Tilechannels oder Bildkomponentenstrukturen, beispielsweise RGB (SDK 2)
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
ColorConvertOp
Pixel-by-Pixel-Farbkonvertierung von Bildern (SDK 2)
ColorModel
Eigenschaften zum Verwalten von Farbinformationen für Bilder
ComponentColorModel
Eigenschaften zum Verwalten von Farbinformationen für Bilder (willkürliche ColorSpace Objekte, SDK 2)
ComponentSampleModel
Eigenschaften zum Sampeln von Bildern (SDK 2)
ConvolveOp
Verbiegung eines Images in ein anderes (SDK 2)
CropImageFilter
Filter zum Zuschneiden (crop) von Bildern
DataBuffer
Einkapselung von einem oder mehr Datenarrays in Reihen (SDK 2)
DataBufferByte
Datenpuffer gespeichert als Bytes (SDK 2)
DataBufferInt
Datenpuffer gespeichert als int-Werte (SDK 2)
DataBufferShort
Datenpuffer gespeichert als short-Werte (SDK 2)
DataBufferUShort
Datenpuffer gespeichert als short-Werte (SDK 2)
DirectColorModel
Farbmodell zum Verwalten von Pixelfarbwerten
FilteredImageSource
Ein Bilderzeuger (ImageProducer), der aus einem Bild und dem ImageFilter-Objekt ein Bild für ein ImageConsumerObjekt erzeugt
ImageFilter
Filter, der Bilddaten aus dem ImageProducer-Objekt nimmt, sie modifiziert und an das ImageConsumer-Objekt weitergibt
IndexColorModel
Farbmodell, wo Pixelwerte in feststehende Farbmaps in einem ColorSpace-Objekt übersetzt werden
Kernel
Matrizen, die beschreiben, wie ein Pixel und die umgebenden Pixels die Werte in einer Filteroperation beeinflussen (SDK 2)
LookupOp
Diese Klasse führt eine Nachschlageoperation von der Quelle (ein Bild) zu dem Bestimmungsort (ein anderes Bild) durch (SDK 2)
LookupTable
Diese abstrakte Klasse definiert ein LookupTable-Objekt (Daten für ein oder mehr Tilechannels oder Bildkomponenten-Strukturen, SDK 2)
MemoryImageSource
Ein ImageProducer-Objekt, das Bilder aus dem Speicher erhält
Java 2 Kompendium
Tabelle B.21: Klassen (Forts.)
975
Anhang B
MultibandPackedSampleModel
Eigenschaften zum Sampeln von Bilder (SDK 2)
PackedColorModel
Ein Farbmodell von Pixelwerten (SDK 2)
PixelGrabber
Die PixelGrabber-Klasse nimmt eine Teilmenge der Pixel in einem Bild
PixelInterleavedSampleModel
Bilddaten
Raster
Raster-Repräsentation von Bilddaten (SDK 2)
ReplicateScaleFilter
Filter zum Skalieren von Bildern (Java 1.1)
RescaleOp
Pixel-by-Pixel-Skalierung von Bildern oder Rastern (SDK 2)
RGBImageFilter
Filter, die Bilddaten von dem ImageProducer-Objekt nehmen und in RGB-ImageConsumer-Objekte umwandeln.
SampleModel
Klasse zum Extrahieren von Pixel aus einem Bild.
ShortLookupTable
Nachschlagetabellen mit einer knappen Auswahl von Daten für Tilechannels oder Bildkomponentenstrukturen (SDK 2)
SingleBandPackedSampleModel
Eigenschaften zum Sampeln von Bildern (SDK 2)
WritableRaster
Beschreibbare Rasterrepräsentation von Bilddaten (SDK 2)
B.2.12
java.awt.image.renderable
Dies ist ein Paket mit unterstützenden Klassen und Schnittstellen für die Produktion von Rendering-unabhängigen Bildern. Neu im Final 1.2 eingeführt.
B.2.13
java.awt.print
Hierbei handelt es sich um ein Druck-Package, das neu im SDK 2 eingeführt wurde. Ausgewählte Schnittstellen Tabelle B.22: Schnittstellen
976
Printable
Methoden zum Drucken in Verbindung mit PageFormatObjekten
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Klassen Book
Liste von Seiten für das Drucken (mit Informationen über eine Seite – etwa den druckbaren Bereich)
PageFormat
Die Grö ß e und Ausrichtung von Seiten für das Drucken
Paper
Physikalische Charakteristierung von dem Druckpapier
PrinterJob
Kontrolle des Druckauftrags
B.2.14
Tabelle B.23: Klassen
java.beans
Hierbei handelt es sich um ein Paket (eingeführt in Java 1.1), das wiederverwendbare Softwarekomponenten – die so genannten Beans – generiert und Programme zur Manipulation zur Verfügung stellt. Schnittstellen AppletInitializer
Methoden, um Beans zu korrekt zu initialisieren, die ebenso als Applets dienen (SDK 2).
BeanInfo
Methoden für ein Bean, um Informationenn über sich selbst zu liefern.
Customizer
Methoden, die den Aufwand für die Definition eines grafischen Konfigurationeditors für ein Bean beschreiben.
DesignMode
Methoden, die die Bean-Eigenschaft festlegen, wenn sie in einer Entwicklungsumgebung verwendet werden (SDK 2).
PropertyChangeListener
Methoden um auf Veränderungen von Eigenschaften zu reagieren.
PropertyEditor
Methoden für die Unterstützung grafischer Anwenderschnittstellen zur Editierung von Eigenschaftswerten eines gegebenen Typs.
VetoableChangeListener
Methoden um auf erzwungene Veränderungen von Eigenschaften zu reagieren.
Visibility
Methoden um festzulegen, ob ein Bean eine grafische Benutzerschnittstelle hat und sie verfügbar ist.
Java 2 Kompendium
Tabelle B.24: Schnittstellen
977
Anhang B Klassen Tabelle B.25: Klassen
BeanDescriptor
Globale Informationen über ein Bean.
Beans
Allgemeine Eigenschaften um ein Bean zu kontrollieren.
EventSetDescriptor
Gruppe von Ereignissen, auf die ein Bean in der Lage ist zu generieren.
FeatureDescriptor
Parent von allen Bean-Descriptor-Klassen.
IndexedPropertyDescriptor
Beschreibt eine Eigenschaft, die wie ein Array funktioniert und einen Index für Lese-/Schreibzugriffe zur Verfügung stellt.
Introspector
Eigenschaften um ein Bean zu anlysieren.
MethodDescriptor
Eigenschaften zum Beschreiben von Methoden, mit denen auf ein Bean von außen zugegriffen werden kann.
ParameterDescriptor
Eigenschaften um zusätzliche Information in Parameter zu beschreiben.
PropertyChangeEvent
Event, wenn sich Eigenschaften verändern.
PropertyChangeSupport
Hilfeklasse für das Managen von Eigenschaften.
PropertyDescriptor
Eigenschaften und Zugriffsmethoden.
PropertyEditorManager
Eigenschaften zum Lokaliseren von einem Eigenschaften-Editor.
PropertyEditorSupport
Hilfeklasse für die Erstellung von Eigenschaften-Editoren.
SimpleBeanInfo
Hilfe für das Bereitsstellen von BeanInfo-Klassen.
VetoableChangeSupport
Hilfeklasse für Beans bei erzwungenen Eigenschaften.
B.2.15
java.bean.beancontext
Hierbei handelt es sich um ein Paket (neu eingeführt im SDK 2) zur Unterstützung von allgemeinen Diensten für Beans.
978
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Ausgewählte Schnittstellen BeanContext
Methoden zur Bereitstellung von Informationen in einem Bean-Umgebungsstatus.
BeanContextChild
Methoden zur Festlegung einer Laufzeitumgebung für Beans.
BeanContextMembershipListener
Methoden, um auf Bean-Kontextereignisse zu reagieren.
Tabelle B.26: Schnittstellen
Ausgewählte Klassen BeanContextEvent
Ereignisse, die generiert werden, wenn sich der BeanContext-Status verändert.
BeanContextMembershipEvent
Ereignisse, die generiert werden, wenn sich der Status von einem oder allen Mitgliedern eines BeanContext verändert.
BeanContextSupport
Implementation des BeanContext-Interfaces.
B.2.16
Tabelle B.27: Klassen
java.io
Dieses Paket enthält Ein- und Ausgabeklassen und Schnittstellen für Ströme und Dateien. Beim IO-Paket werden wir ausnahmsweise die Exceptions angeben, da diese extrem wichtig sind. Schnittstellen DataInput
Methoden zum Lesen von maschinenunabhängigen Eingabeströmen.
DataOutput
Methoden zum Schreiben von maschinenunabhängigen Ausgabeströmen.
Externalizable
Methoden zum Schreiben und Lesen eines Objektinhalts in einen Stream (Java 1.1).
FileFilter
Filter für abstrakte Pfadnamen.
FilenameFilter
Methoden zum Filtern von Dateinamen.
ObjectInput
Methoden zum Lesen von Objekten (Java 1.1).
ObjectInputValidation
Methoden zum Validieren eines Objekts (Java 1.1).
Java 2 Kompendium
Tabelle B.28: Schnittstellen
979
Anhang B Tabelle B.28: Schnittstellen (Forts.)
ObjectOutput
Methoden zum Schreiben von Objekten (Java 1.1).
ObjectStreamConstants
Konstanten zum Schreiben in einen Object Serilization Stream.
Serializable
Feststellung, dass diese Klasse serialisiert sein kann (Java 1.1).
Klassen Tabelle B.29: Klassen
980
BufferedInputStream
Ein gepufferter Eingabestrom.
BufferedOutputStream
Ein gepufferter Ausgabestrom.
BufferedReader
Ein gepufferter Reader (Java 1.1).
BufferedWriter
Ein gepufferter Writer (Java 1.1).
ByteArrayInputStream
Ein Eingabestrom aus einem Byte-Array.
ByteArrayOutputStream
Ein Ausgabestrom in ein Byte-Array (die toString()Methode gilt seit Java 1.1 als verworfen).
CharArrayReader
Ein Reader von einem Array von Character (Java 1.1).
CharArrayWriter
Ein Writer in ein Array von Character (Java 1.1).
DataInputStream
Eigenschaften zum maschinenunabhängigen Lesen von primitiven Javatypen (beispielsweise int, char, und boolean) aus einem Stream (die readLine()-Methode gilt seit Java 1.1 als verworfen).
DataOutputStream
Eigenschaften zum maschinenunabhängigen Schreiben von primitiven Javatypen (beispielsweise int, char, und boolean) in einen Stream.
File
Repräsentiert eine Datei auf dem Host-Dateisystem.
FileDescriptor
Eigenschaften um einen plattformspezifischen Zugriff auf eine Datei oder Socket zu realisieren
FileInputStream
Ein Eingabestrom aus einer Datei; zusammengesetzt aus dem Dateinamen oder dem Bezeichner.
FileOutputStream
Ein Ausgabestrom an eine Datei; zusammengesetzt aus dem Dateinamen oder dem Bezeichner.
FilePermission
Zugriff auf eine Datei oder ein Verzeichnis.
FileReader
Ein Reader von einer Datei; zusammengesetzt aus dem Dateinamen oder dem Bezeichner (Java 1.1).
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
FileWriter
Ein Writer in eine Datei; zusammengesetzt aus dem Dateiname oder dem Bezeichner (Java 1.1).
FilterInputStream
Abstrakte Klasse zum Filtern oder Puffern von Eingabeströmen.
FilterOutputStream
Abstrakte Klasse zum Filtern oder Puffern von Ausgabeströmen.
FilterReader
Ein Klasse, die einen Filter für Reader enthält und zugehörige Funktionalität, beispielsweise buffering (Java 1.1).
FilterWriter
Ein Klasse, die einen Filter für Writer enthält und zugehörige Funktionalität, beispielsweise buffering (Java 1.1).
InputStream
Abstrakte Klasse, die einen Eingabestrom von Bytes darstellt; der Ursprung von allen Eingabeströmen in diesem Package.
InputStreamReader
Ein Reader zur Überführung von byte-Streams in character-Streams (Java 1.1).
LineNumberInputStream
Ein Eingabestrom zum Erzeugen von Zeilennummern (im SDK 2 verworfen).
LineNumberReader
Ein gepufferter Character-Eingabestrom-Reader zum Lesen von Zeilennummern (Java 1.1).
ObjectInputStream
Ein Klasse, die Daten und Objekte deserialisiert (Java 1.1; die readLine()-Methode gilt seit SDK 2 als verworfen).
ObjectInputStream. GetField
Unterstützung für den Zugriff auf ein persistentes Feld, das aus einem Eingabestrom gelesen wird.
ObjectOutputStream
Ein Klasse, die Daten und Objekte serialisiert (Java 1.1).
ObjectOutputStream. PutField
Unterstützung für die Ausgabe in ein persistentes Feld.
ObjectStreamClass
Ein Descriptor für Klassen, die serialisiert werden können (Java 1.1).
ObjectStreamField
Beschreibung eines Feldes in einer serialisierbaren Klasse.
OutputStream
Abstrakte Klasse, die einen Ausgabestrom von Bytes darstellt; der Ursprung von allen Ausgabeströmen in diesem Package.
OutputStreamWriter
Ein Brücke zwischen byte- und character-Streams (Java 1.1).
PipedInputStream
Ein Pipe-Eingabestrom, der mit einem Pipe-Ausgabestrom verbunden werden sollte.
Java 2 Kompendium
Tabelle B.29: Klassen (Forts.)
981
Anhang B Tabelle B.29: Klassen (Forts.)
982
PipedOutputStream
Ein Pipe-Ausgabestrom, der mit einem Pipe-Eingabestrom verbunden werden sollte, um eine sichere Kommunikation zwischen Threads zu gewährleisten.
PipedReader
Ein Pipe-Reader, der mit einem Pipe-Writer verbunden werden sollte, um eine sichere Kommunikation zwischen Threads zu gewährleisten (Java 1.1).
PipedWriter
Ein Pipe-Writer, der mit einem Pipe-Reader verbunden werden sollte, um eine sichere Kommunikation zwischen Threads zu gewährleisten (Java 1.1).
PrintStream
Ein Ausgabestrom zum Drucken (verwendet von System.out.println(); die PrintStream()-Konstruktoren sind seit Java 1.1 verworfen).
PrintWriter
Ein Writer zum Drucken (Java 1.1).
PushbackInputStream
Ein Eingabestrom mit einem Rückstellpuffer (1 Byte).
PushbackReader
Ein Reader mit einem Push-back-Puffer (Java 1.1).
RandomAccessFile
Eigenschaften für den Zufallszugriff auf eine Datei – besteht aus Dateiname, Bezeichner oder Objekten.
Reader
Abstrakte Klasse, die einen Zeicheneingabe-Strom repräsentiert; der Ursprung von allen Readers in diesem Package (Java 1.1).
SequenceInputStream
Eigenschaften um aus einer Sequenz von Eingabeströmen einen einzelnen Eingabestrom zu bilden.
SerializablePermission
Eigenschaften zum Behandeln von serialisierter Zugriffskontrolle (SDK 2).
StreamTokenizer
Eigenschaften zum Konvertieren eines Eingabestrom in eine Reihe von individuellen Token (der StreamTokenizer()-Konstruktor gilt seit Java 1.1 als verworfen).
StringBufferInputStream
Ein Eingabestrom von einem String-Objekt (verworfen seit SDK 2).
StringReader
Ein Reader von einem String-Objekt (Java 1.1).
StringWriter
Ein Writer in ein String-Objekt (Java 1.1).
Writer
Abstrakte Klasse, die einen Zeichenausgabestrom repräsentiert; der Ursprung aller Writer in diesem Package (Java 1.1).
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Die Ausnahmen Hier folgt ausnahmsweise (da Ein- und Ausgabe zu den gefährlichsten Aktionen eines Programms gehören) eine Liste der Standardausnahmen. Sie sind nicht näher erklärt und können in der Dokumentation nachgelesen werden. CharConversionException EOFException FileNotFoundException InterruptedIOException InvalidClassException InvalidObjectException IOException NotActiveException NotSerializableException ObjectStreamException OptionalDataException StreamCorruptedException SyncFailedException UnsupportedEncodingException UTFDataFormatException WriteAbortedException
B.2.17
java.lang
Das wohl wichtigste Paket von Java, denn dieses Paket beinhaltet den Kern der Java-Sprache. Wir werden es deshalb inklusive aller Klassen, Schnittstellen sowie Ausnahmen und Fehler besprechen. Schnittstellen Cloneable
Methoden, um anzuzeigen, dass ein Objekt kopiert oder geklont werden kann.
Comparable
Methoden zum Vergleichen von Objekten.
Runnable
Methoden, um Klassen als Threads zu verwenden.
Tabelle B.30: Schnittstellen
Klassen Boolean
Objekt-Wrapper für boolean-Werte.
Byte
Objekt-Wrapper für byte-Werte (Java 1.1).
Character
Objekt-Wrapper für char-Werte (die Methoden isJavaLetter(), isJavaLetterOrDigit()und isSpace() sind seit
Tabelle B.31: Klassen
Java 1.1 verworfen).
Java 2 Kompendium
983
Anhang B Tabelle B.31: Klassen (Forts.)
984
Character.Subset
Ein Subset des Unicode-Zeichensatzes.
Character.UnicodeBlock
Eine Familie von Zeichenuntergruppen des Unicode-Zeichensatzes (Unicode 2.0).
Class
Darstellung von Klassen zur Laufzeit.
ClassLoader
Abstrakte Eigenschaft zum Laden von Klassen (die Methode defineClass() gilt seit Java 1.1 als verworfen).
Compiler
Systemklasse, die Zugriff auf den Java-Compiler erlaubt.
Double
Objekt-Wrapper für double-Werte.
InheritableThreadLocal
Erweiterung von ThreadLocal zur Unterstützung der Weitergabe von Werten von dem Parent-Thread zum Kindprozess (JDK 1.2).
Float
Objekt-Wrapper für float-Werte.
Integer
Objekt-Wrapper für int-Werte.
Long
Objekt-Wrapper für long-Werte.
Math
Utility-Klasse für mathematische Operationen.
Number
Abstrakte Superklasse von allen Zahlenlassen (beispielsweise integer und float).
Object
Globale Objektklasse an der obersten Stelle der Vererbungshierarchie.
Package
Versioninformation über die Implementation und Spezifikation des Java-Packages (SDK 2).
Process
Abstrakte Eigenschaft für Prozesse der Systemklasse.
Runtime
Zugriff auf die Java-Runtime-Umgebung (die Methoden getLo.calizedInputStream() und getLocalizedOutputStream() sind seit Java 1.1 verworfen).
RuntimePermission
Eigenschaften zur Bereitstellung von Laufzeitzugriffskontrolle (SDK 2).
SecurityManager
Abstrakte Eigenschaft für die Implementation von Sicherheitsmaßnahmen (die inCheck-Variable und einige Methoden gelten seit SDK 2 als verworfen: getInCheck(), classDepth(), classLoaderDepth(), inClass() und inClassLoader()).
Short
Objekt-Wrapper für short-Werte (Java 1.1).
StrictMath
Numerische Basisoperationen (JDK 1.3).
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
String
Zeichenketten (2 String()-Konstruktoren und eine getBytes()-Methode sind seit Java 1.1 verworfen).
StringBuffer
Mutationsfähige Zeichenketten.
System
Zugriff (plattformunabhängig) auf Eigenschaften der Java-Systemebene (die Methode getEnv() gilt seit Java 1.1 als verworfen).
Thread
Methoden für die Verwaltung von Threads und Klassen, die in Threads laufen (die Methoden stop(), suspend() und resume() sind seit SDK 2 verworfen).
ThreadGroup
Eine Gruppe von Threads (die Methoden stop(), suspend() und resume() sind seit SDK 2 verworfen).
ThreadLocal
Eigenschaften zum Bereitstellen von unabhängig individualisierten Variablen für Threads (SDK 2).
Throwable
Globale Exception-Klasse.
Void
Objekt-Wrapper für void-Typen (Java 1.1).
Tabelle B.31: Klassen (Forts.)
Ausnahmen ArithmeticException
Ausgeworfen bei einer arithmetischen Regelverletzung.
ArrayIndexOutOfBoundsException
Ausgeworfen bei einem illegalen Array-Zugriff.
ArrayStoreException
Ausgeworfen bei einer Typverletzung während eines Zugriffs auf ein Objekt-Array.
ClassCastException
Ausgeworfen bei einem Casting eines Objekts in eine falsche Subklasse.
ClassNotFoundException
Ausgeworfen, wenn eine Applikation versucht, eine Klasse zu laden und es Konflikte bei dem angegebenen Namen gibt.
CloneNotSupportedException
Ausgeworfen, wenn es Probleme beim Klonen von Objekten gibt.
Exception
Mutterklasse von Ausnahmen.
IllegalAccessException
Ausgeworfen bei einem illegalen Zugriff auf eine Methode (falscher Name).
IllegalArgumentException
Ausgeworfen bei einem Zugriff mit illegalen Argumenten.
IllegalMonitorStateException
Ausgeworfen bei einem fehlgeschlagenen Zugriff eines Threads auf den Monitor eines Objekts.
Java 2 Kompendium
Tabelle B.32: Exceptions
985
Anhang B Tabelle B.32: Exceptions (Forts.)
986
IllegalStateException
Aufruf einer Methode zum falschen Zeitpunkt.
IllegalThreadStateException
Falscher Status des Threads für die betreffende Operation.
IndexOutOfBoundsException
Index außerhalb des gültigen Bereichs.
InstantiationException
Ausgeworfen, wenn eine Applikation eine Instanz einer Klasse erstellen will und die Klasse ist ein Interface oder eine abstrakte Klasse.
InterruptedException
Ausgeworfen, wenn ein Thread wartet, schläft oder sonst pausiert und ein anderer Thread diese Ruhephase über die interrupt()-Methode in der Klasse Thread unterbricht.
NegativeArraySizeException
Ausgeworfen, wenn ein Array mit negativer Grö ß e erstellt werden soll.
NoSuchFieldException
Es gibt kein Feld mit diesem Namen in einer Klasse.
NoSuchMethodException
Ausgeworfen, wenn keine Methode des angegebenen Namens gefunden werden kann.
NullPointerException
Ausgeworfen, wenn eine Applikation versucht null zu verwenden, obwohl ein Objekt zwingend ist.
NumberFormatException
Ausgeworfen bei einer Konvertierung eines Strings in einen numerischen Typ, wenn der String nicht das passende Format hat.
RuntimeException
Superklasse aller Laufzeitausnahmen der Java Virtual Machine.
SecurityException
Ausgeworfen durch den Securitymanager bei einer Sicherheitsverletzung.
StringIndexOutOfBoundsException
Ausgeworfen durch die charAt()-Methode in der Klasse String und anderer String-Methoden zur Anzeige, dass ein Index negativ ist oder grö ß er bzw. gleich der Grö ß e des Strings.
UnsupportedOperationException
Ausgeworfen, wenn eine Operation nicht unterstützt wird.
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Fehler AbstractMethodError
Aufruf einer abstrakten Methode.
ClassCircularityError
Zirkelbezug beim Initialisieren einer Klasse entdeckt.
ClassFormatError
Ausgeworfen, wenn die Java Virtual Machine beim Lesen einer Klasse feststellt, dass die Datei kein gültiges Javaclass-File ist.
Error
Subklasse von Throwable. Anzeige eines Problems, das nicht von der Applikation aufgefangen werden sollte.
ExceptionInInitializerError
Unerwartete Ausnahme in einen static initializer.
IllegalAccessError
Ausgeworfen bei Zugriff auf ein Feld oder eine Methode ohne Berechtigung.
IncompatibleClassChangeError
Ausgeworfen bei einer inkompatiblen Klassenveränderung.
InstantiationError
Ausgeworfen bei einer illegalen Instanzierung.
InternalError
Ausgeworfen bei einem unerwarteten internen Fehler in der Java Virtual Machine.
LinkageError
Subklassen dieser Klasse zeigen an, dass eine Klasse Abhängigkeiten auf andere Klassen beinhaltet und dort Inkompatiblitäten vorhanden sind.
NoClassDefFoundError
Ausgeworfen, wenn die Java Virtual Machine oder ein Classloader versucht eine Klasse zu laden und sie nicht findet.
NoSuchFieldError
Ausgeworfen bei einem Zugriff auf ein Feld, das nicht im Objekt enthalten ist.
NoSuchMethodError
Ausgeworfen bei einem Zugriff auf eine Methode, die nicht in der Klasse enthalten ist.
OutOfMemoryError
Ausgeworfen, wenn die Java Virtual Machine ein Objekt aus Speichermangel nicht allokieren kann.
StackOverflowError
Ausgeworfen bei einem Überlauf des Stacks.
ThreadDeath
Ausgeworfen, wenn für einen Thread die stop()-Methode ohne Argumente aufgerufen wird.
UnknownError
Ausgeworfen bei einer unbekannten Exception in der Java Virtual Machine.
Java 2 Kompendium
Tabelle B.33: Fehler
987
Anhang B Tabelle B.33: Fehler (Forts.)
UnsatisfiedLinkError
Ausgeworfen, wenn die Java Virtual Machine eine passende Native-Language-Definition von einer native deklarierten Methode nicht finden kann.
UnsupportedClassVersionError
Ausgeworfen, wenn die Java Virtual Machine versucht, eine class-Datei zu lesen und feststellt, dass die Versionsnummer in der Datei nicht unterstützt wird.
VerifyError
Ausgeworfen, wenn der Verifier entdeckt, dass die classDatei innere Inkonsistenzen oder Sicherheitsprobleme beinhaltet.
VirtualMachineError
Ausgeworfen, wenn die Java Virtual Machine abgestürzt ist bzw. nicht mehr genug Ressourcen zur Verfügung hat.
B.2.18
java.lang.ref
Hierbei handelt es sich um ein Paket (neu eingeführt im SDK 2), das es ermöglicht, Objektreferenzen wie jedes anderes Objekt zu behandeln. Klassen Tabelle B.34: Klassen
PhantomReference
Phantom-Objektreferenz.
Reference
Kernabstraktion für Objektreferenzen.
ReferenceQueue
Zusammenarbeit der Objektreferenz mit dem Garbage Collector.
SoftReference
Softreferenz.
WeakReference
Schwache Objektreferenz.
B.2.19
java.lang.reflect
Hierbei handelt es sich um ein Paket (eingeführt in Java 1.1) zur Unterstützung von Reflection, d.h., um Informationen über geladene Klassen zu bekommen (beispielsweise ihre Attribute). Schnittstellen Tabelle B.35: Schnittstellen
988
InvocationHandler
Unterstützung für im JDK 1.3 neu eingeführte ProxyInstanzen (JDK 1.3).
Member
Methoden, um Informationen über ein Mitglied herauszufinden.
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Klassen AccessibleObject
Basisklasse für Felder, Methoden und KonstruktorObjekten (SDK 2).
Array
Methoden zum dynamischen Generieren von und Zugreifen auf Arrays.
Constructor
Methoden für Informationen über und Zugriff auf Konstruktoren.
Field
Methoden für Informationen über und Zugriff auf Variablen.
Method
Methoden für Informationen über und Zugriff auf Methoden.
Modifier
Decoder für statische Klassen und Zugriffsmodifier.
Proxy
Die Klasse stellt statische Methoden zur Verfügung, um dynamisch Proxyklassen zu generieren (JDK 1.3).
ReflectPermission
Eigenschaften zur Bereitstellung von Zugriffkontrolle bei Reflection-Operationen (SDK 2).
B.2.20
Tabelle B.36: Klassen
java.math
Hierbei handelt es sich um ein Paket (eingeführt in Java 1.1) mit Klassen für große Zahlen. Klassen BigDecimal
Eine sehr große Fließkommazahl.
BigInteger
Eine sehr große Integerzahl.
B.2.21
Tabelle B.37: Klassen
java.net
Ein Package für die Durchführung von Netzwerkoperationen, beispielsweise Socket- und URL-Handling.
Java 2 Kompendium
989
Anhang B Schnittstellen Tabelle B.38: Schnittstellen
ContentHandlerFactory
Methoden zum Erstellen von ContentHandler-Objekten.
DatagramSocketImplFactory
Unterstützung von Datagram-Sockets (JDK 1.3).
FileNameMap
Methoden zum Mappen zwischen Dateinamen und MIME-Typen (Java 1.1).
SocketImplFactory
Methoden zum Erstellen von Socket-Implementationen.
SocketOptions
Socketoptionen.
URLStreamHandlerFactory
Methoden zum Erstellen von URLStreamHandler-Objekten.
Klassen Tabelle B.39: Klassen
990
Authenticator
Ein Objekt, das Netzwerk-Beglaubigungen gewährleistet (SDK 2).
ContentHandler
Abstrakte Eigenschaft zum Lesen von Daten in einer URL-Verbindung und Konstruieren des entsprechenden lokalen Objekts auf Grundlage des MIME-Typs.
DatagramPacket
Ein Datagram-Paket (UDP).
DatagramSocket
Ein Datagram-Socket.
DatagramSocketImpl
Abstrakte Basisklasse für Datagram- und MulticastSockets (Java 1.1).
HttpURLConnection
Eine Verbindung, die das HTTP-Protokoll verarbeiten kann (Java 1.1).
InetAddress
Eine Objektrepräsentation von einem Internethost (Hostname, IP-Adresse).
JarURLConnection
Eine URL-Verbindung zu einer JAR-Datei (SDK 2).
MulticastSocket
Ein serverseitiges Socket mit Unterstützung für übermittelte Daten an mehre Clientsockets (Java 1.1).
NetPermission
Eigenschaften zur Bereitstellung von Netzzugriffskontrolle (SDK 2).
PasswordAuthentication
Username und Passwort für die Verwendung bei Authenticator (SDK 2).
ServerSocket
Ein serverseitiges Socket.
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Socket
Ein Socket (2 Socket()-Konstruktoren sind seit Java 1.1 verworfen).
SocketImpl
Abstrakte Klasse für die spezifische Socketimplementation.
SocketPermission
Eigenschaften zur Bereitstellung von Netzzugriffen via Sockets in einem Netzwerk (SDK 2).
URL
Eine Objektrepräsentation einer URL.
URLClassLoader
Klassen und JAR-Archiv-Loader für eine URL-Suchpfad (SDK 2).
URLConnection
Abstrakte Eigenschaft für einen Socket, der verschiedene Web-basierende Protokolle (http, ftp usw.) handhaben kann.
URLDecoder
Konvertierung von MIME-Zeichen in String.
URLEncoder
Verwandelt Strings in das x-www-form-urlencoded-Format.
URLStreamHandler
Abstrakte Klasse zum Handhaben von Objektströmen, auf die URLs verweisen.
B.2.22
Tabelle B.39: Klassen (Forts.)
java.rmi
Hierbei handelt es sich um ein Paket (eingeführt in Java 1.1) zum Erstellen von verteilten Java-to-Java-Applikationen. Schnittstellen Remote
Methoden zum Identifizieren aller entfernten (Remote-) Objekte.
Tabelle B.40: Schnittstellen
Klassen MarshalledObject
Ein byte-Stream mit einer angeordneten Repräsentation in einem Objekt (SDK 2).
Naming
Eigenschaften zum Behandeln von Referenzen auf Remote-Objekte – basierend auf der URL-Syntax.
RMISecurityManager
Methoden zur Definition der RMI Stub Security Policy für Applikationen (keine Applets).
Java 2 Kompendium
Tabelle B.41: Klassen
991
Anhang B
B.2.23
java.rmi.activation
Dieses Paket (neu eingeführt im SDK 2) unterstützt persistente (beständige) Referenzen auf Remote-Objekte und die automatische Objekt-Reaktivierung über diese Referenzen. Schnittstellen Tabelle B.42: Schnittstellen
ActivationInstantiator
Methoden zum Erstellen von Gruppen von Objekten, die aktiviert werden können.
ActivationMonitor
Methoden zum Reagieren auf Veränderungen.
ActivationSystem
Methoden zum Registieren von Gruppen und aktivierbaren Objekten.
Activator
Methoden in einem aktivierten Remote-Objekt.
Klassen Tabelle B.43: Klassen
Activatable
Remote-Objekte, die über die Zeit ihres Zugriffs persistent gemacht werden und wieder aktiviert werden können.
ActivationDesc
Descriptor mit Informationen, die in aktivierten Objekten benötigt werden.
ActivationGroup
Eigenschaften zum Gruppieren und Verfolgen von aktivierten Objekten.
ActivationGroupDesc
Descriptor mit Information zum Erstellen und wiederaktivieren von Gruppen.
ActivationGroupDesc .CommandEnvironment
Statusinformationen für ActivationGroup-Implementationen.
ActivationGroupID
Identifier für eine registierte aktivierte Gruppe.
ActivationID
Identifier für aktivierte Objekte.
B.2.24
java.rmi.dgc
Dieses Paket (vorhanden seit Java 1.1) unterstützt den (distibuted) GarbageCollection-Algorithmus.
992
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Schnittstellen Methoden zum Bereinigen von Verbindungen für nicht mehr verwendete Clients.
DGC
Tabelle B.44: Schnittstellen
Klassen Lease
Beinhaltet einen einzigartigen VM-Identifier und eine Verwendungsdauer.
VMID
Eigenschaften zur Aufrechterhaltung der einzigartigen VM-ID über alle JVM hinweg.
B.2.25
Tabelle B.45: Klassen
java.rmi.registry
Hierbei handelt es sich um ein Paket (eingeführt in Java 1.1) zum Behandeln von entfernten Methoden-Aufrufen. Schnittstellen Registry
Methoden zur Bereitstellung der Registratur für verschiedene Hosts.
RegistryManager
Methoden für die Zusammenarbeit mit der privaten Implementation (die registryStub()- und registryImpl()Methoden sind seit SDK 2 verworfen).
Tabelle B.46: Schnittstellen
Klassen LocateRegistry
B.2.26
Eigenschaften zur Bereitstellung der Start-Routineregistrierung in einem Host.
Tabelle B.47: Klassen
java.rmi.server
Hierbei handelt es sich um ein Paket (eingeführt in Java 1.1) für den serverseitigen Aufruf von entfernten Methoden.
Java 2 Kompendium
993
Anhang B Ausgewählte Schnittstellen Tabelle B.48: Schnittstellen
LoaderHandler
Methoden zum Behandeln von Ladeaufrufen.
RMIFailureHandler
Methoden für das Handling, wenn die RMI-Runtime nicht zum Erstellen eines Sockets oder Server-ockets in der Lage ist.
RemoteCall
Methoden für Implementationsaufrufe in einem entfernten Objekt.
RemoteRef
Repräsentiert eine Behandlung für ein entferntes Objekt.
ServerRef
Repräsentiert die serverseitige Behandlung für eine entfernte Objekt-Implementation.
Skeleton
Repräsentiert die serverseitige Entity, die Rufe zu der entfernten Objekt-Implementation sendet.
Unreferenced
Methoden zum Empfangen von Nachrichten, wenn keine Remote-Referenz mehr da ist.
Klassen Tabelle B.49: Klassen
994
LogStream
Enthält einen Mechanismus für Logging-Fehler.
ObjID
Dient zur eindeutigen Identifizierung des entfernten Objekts in einer VM.
Operation
Enthält eine Beschreibung einer Java-Methode.
RMIClassLoader
Enthält statische Methoden für das Laden von Klassen über das Netzwerk.
RMISocketFactory
Verwendet vom RMI-Runtime zur Bereitstellung von Client- und Server-Sockets für RMI-Aufrufe.
RemoteObject
Enthält die entfernte Semantik von dem Objekt über die Implementation von Methoden für hashCode, equals und toString.
RemoteServer
Die Superklasse einer Server-Implementation.
RemoteStub
Unterstützung für Stub-Objekte.
UID
Abstraktion für das Generieren von eindeutigen Identifiern.
UnicastRemoteObject
Definiert ein nicht-replizierbares Remote-Objekt, dessen Referenze nur so lange gültig ist, wie der Serverprozess läuft.
Java 2 Kompendium
Beschreibung der Pakete
B.2.27
Anhang B
java.security
Hierbei handelt es sich um ein Paket (eingeführt in Java 1.1) zur Implementation von Zertifikaten und digitalen Signaturen in Java-Komponenten. Ausgewählte Schnittstellen Certificate
Methoden zum Verwalten eines Zertifikats, inklusive Verund Entschlüsselung (im SDK 2 als verworfen deklariert).
Guard
Methoden zum Schutz des Zugriffs auf ein Objekt (SDK 2).
Key
Methoden zum Definieren der gemeinsamen Funktionalität bei allen Schlüssel-Objekten (SDK 2).
Principal
Repräsentiert die prinzipielle Komponente eines Zertifikats.
PrivateKey
Methoden zum Gruppieren und Bereitstellen von PrivateKey-Schnittstellen (SDK 2).
PublicKey
Methoden zum Gruppieren und Bereitstellen von PublicKey-Schnittstellen (SDK 2).
Tabelle B.50: Schnittstellen
Ausgewählte Klassen AccessControlContext
Bedingungen für Zugangskontroll-Entscheidungen (SDK 2).
AccessController
Bereitsteller von kontrollierten Zugriffen – basierend auf einer Security Policy (SDK 2).
AlgorithmParameter Generator
Eigenschaften zum Erstellen von Parametern.
AlgorithmParameter GeneratorSpi
Serviceprovider-Interface für den AlgorithmusParameterGenerator (SDK 2).
AlgorithmParameters
Repräsentation von kryptografischen Parametern (SDK 2).
AlgorithmParametersSpi
Serviceprovider-Interface für den AlgorithmusParameter (SDK 2).
BasicPermission
Eigenschaften zur Bereitstellung von Basisrechten für die Zugriffskontrolle (SDK 2).
CodeSource
URL-Erweiterung zur Verwendung von öffentlichen Schlüsseln (SDK 2).
Java 2 Kompendium
Tabelle B.51: Klassen
995
Anhang B Tabelle B.51: Klassen (Forts.)
996
DigestInputStream
Repräsentiert einen Eingabestrom mit einer Zusammenfassung.
DigestOutputStream
Repräsentiert einen Ausgabestrom mit einer Zusammenfassung.
GuardedObject
Ein Objekt, das zum Zugriffsschutz eines anderen Objekts verwendet wird (SDK 2).
Identity
Methoden zum Verwalten von Identities (die Methoden addZertifikat() , removeZertifikat() und Zertifikats() sind seit SDK 2 verworfen).
IdentityScope
Methoden zum Definieren des Anwendungsbereichs für eine Identity inklusive dem Namen der Identity, der Schlüssel und der vereinbarten Zertifikate.
KeyFactory
Konvertor von Schlüssel-zu-Schlüssel-Spezifikationen (SDK 2).
KeyFactorySpi
Serviceprovider-Interface zur Schlüsselerstellung (SDK 2).
KeyPair
Ein einfacher Halter für ein Schlüsselpaar.
KeyPairGenerator
Generator zum Produzieren von Schlüsselpaaren (SDK 2).
KeyPairGeneratorSpi
Serviceprovider-Interface für Schlüsselpaar-Generatoren (SDK 2).
KeyStore
Im-Speicher-Halten von privaten Schlüsseln und zugehörigen Zertifikatsketten(SDK 2).
MessageDigest
Methoden zur Unterstützung von einem Message-DigestAlgorithmus zur Nachrichtenverarbeitung.
MessageDigestSpi
Serviceprovider-Interface für Nachrichtenverarbeitung (SDK 2).
Permission
Repräsentation von einem Zugriff in eine Systemressource (SDK 2).
PermissionCollection
Sammlung von Permission-Objekten (SDK 2).
Permissions
Heterogene Sammlung von Permission-Objekten (SDK 2).
Policy
Repräsentation einer Policy für ein Java-Runtime (SDK 2).
Provider
Repräsentiert ein SecurityPackageProvider (SPP) für die JavaSecurity-API.
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
SecureClassLoader
Extension des ClassLoader für Klassen mit Quellcode und Signer-Informationen (SDK 2).
SecureRandom
Generiert eine Zufallszahl.
Security
Metohoden zum Verwalten von SPPs (die Methoden setParameter() und getParameter() sind seit SDK 2 verworfen).
SecurityPermission
Eigenschaften zur Bereitstellung der Security-Zugriffskontrolle (SDK 2).
Signature
Enthält den Algorithmus für digitale Signaturen.
SignatureSpi
Serviceprovider-Interface für Signatur-Objekte (SDK 2; die Methoden engineGetParameter() und engineSetParameter() sind seit SDK 2 verworfen).
SignedObject
Authentisches Runtime-Objekt, dessen Integrität ohne Aufdeckung nicht verglichen werden kann (SDK 2).
Signer
Repräsentiert eine Identity, die ebenso signieren kann.
B.2.28
Tabelle B.51: Klassen (Forts.)
java.security.acl
Hierbei handelt es sich um ein Paket, das nur Schnittstellen enhält, mit denen Zugriffe auf Ressourcen durch Wächterfunktionen kontrolliert werden können. Schnittstellen Acl
Ein Interface, das eine Zugriffskontroll-Liste (Access Control List = ACL) darstellt.
AclEntry
Methoden zum Hinzufügen, Entfernen oder Setzen von Eigenschaften für die prinzipiellen Objekte von jeder ACLEntry in dem ACL.
Group
Methoden zum Hinzufügen oder Entfernen eines Mitglieds der Gruppe von prinzipiellen Objekten.
Owner
Repräsentiert den Eigentümer eines ACL.
Permission
Repräsentiert den Typ von gewährtem Zugriff.
B.2.29
Tabelle B.52: Schnittstellen
java.security.cert
Ein Package (neu eingeführt im SDK 2) für die Identity-Zertifikation.
Java 2 Kompendium
997
Anhang B Schnittstellen Tabelle B.53: Schnittstellen
X509Extension
Extensions definiert für X.509 v3 Zertifikat- und v2 Zertifikat-Aufruflisten.
Ausgewählte Klassen Tabelle B.54: Klassen
Certificate
Identity-Zertifikat mit verschiedenen Formaten und für emeinsamen Gebrauch.
RevokedCertificate
Widerrufenes Zertifikat in einer Zertifikat-Aufrufliste.
X509CRL
X.509 Zertifikat-Aufrufliste.
X509Certificat
X.509 Zertifikat.
B.2.30
java.security.interfaces
Hierbei handelt es sich um ein Paket (eingeführt in Java 1.1) für die RSAund DSA-Technologie. Ausgewählte Schnittstellen Tabelle B.55: Schnittstellen
DSAKey
Methoden für die Verwendung in beglaubigten Komponenten inklusive Java-Applets und ActiveX-Controls.
DSAKeyPairGenerator
Methoden zum Generieren eines DSA-Schlüsselpaars.
DSAParams
Methoden zum Zugriff auf DSA-Parameter.
DSAPrivateKey
Ein Interface für private DSA-Schlüssel.
DSAPublicKey
Ein Interface für öffentliche DSA-Schlüssel.
B.2.31
java.security.spec
Ein Package (neu eingeführt im SDK 2) zur Unterstützung von Verschlüsselung. Schnittstellen Tabelle B.56: Schnittstellen
998
AlgorithmParameterSpec
Methoden zum Spezifizieren von kryptografischen Parametern.
KeySpec
Methoden zum Spezifizieren von kryptografischen Schlüsseln.
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Ausgewählte Klassen DSAParameterSpec
Parameter für die Verwendung mit dem DSA-Algorithmus.
DSAPrivateKeySpec
Private DSA-Schlüssel und zugehörige Parameter.
DSAPublicKeySpec
Öffentliche DSA-Schlüssel und zugehörige Parameter.
EncodedKeySpec
Öffentliche oder private Schlüssel im verschlüsselten Format.
PKCS8EncodedKeySpec
DER-Verschlüsselung von privaten Schlüssel im PKCS #8-Standard.
X509EncodedKeySpec
DER-Verschlüsselung von privaten oder öffentlichen Schlüsseln im X.509-Standard.
B.2.32
Tabelle B.57: Klassen
java.sql
Hierbei handelt es sich um das zentrale Paket (eingeführt in Java 1.1), um mittels Structured Query Language (SQL) auf Datenbanken zuzugreifen. Sie finden darin die hauptsächliche JDBC-Funktionalität von Java. Ausgewählte Schnittstellen CallableStatement
Methoden für die Verwendung von Prozeduren und die Behandlung der Resultate.
Connection
Repräsentiert eine Session mit der Datenbank.
DatabaseMetaData
Ein Interface, um zusätzliche Informationen (Metadaten) über die Datenbank zu bekommen.
Driver
Methoden für den Aufbau einer Verbindung zu einer Datenbank.
PreparedStatement
Methoden für die Ausführung von vorkompilierten SQLStatements.
ResultSet
Methoden zum Empfangen von Werten der ausgeführten SQL-Statements.
ResultSetMetaData
Methoden mit Informationen über die Typen und Eigenschaften von den Spalten in einem Resultatsatz.
Statement
Methoden für die Verwendung von statischen SQL-Statements.
Java 2 Kompendium
Tabelle B.58: Schnittstellen
999
Anhang B Klassen Tabelle B.59: Klassen
Date
Enthält Methoden zum Formatieren und Referenzieren von Daten (SDK 2, ein Date()-Konstruktor und einige Methoden wurden verworfen: getHours(), getMinutes(), getSeconds(), setHours() setMinutes(), und setSeconds().
DriverManager
Erlaubt die Verwaltung von einem Satz von JDBC-Treibern.
DriverPropertyInfo
Enthält Methoden für die Verwaltung von verschiedenen Eigenschaften eines Treibers.
SQLPermission
Die Berechtigung, nach der der Securitymanager bei einem Applet suchen wird, wenn es eine der setLog-Writer-Methoden aufruft (JDK 1.3).
Time
Enthält Methoden zum Formatieren und Referenzieren von Zeit-Daten (Einige Methoden sind seit SDK 2 verworfen : getDate(), getDay(), getMonth(), getYear(), setDate(), setMonth() und setYear()).
Timestamp
Ein Wrapper mit den SQL-TIMESTAMP-Werten (Ein TimeStamp()-Konstruktor gilt seit SDK 2 als verworfen).
Types
Definiert Konstanten für die Identifikation von SQLTypen.
B.2.33
java.text
Dieses Paket ist die Basis der Arbeit mit Strings im Allgemeinen, zur Erstellung von Strings aus anderen Objekten und der Konvertierung von Strings in andere Objekte. Auch dieses Paket hatte einige neue Elemente und Strukturen in den Betaversionen des JDK 1.2 eingeführt, die für die Finalversion 1.2 wieder verworfen wurden. Schnittstellen Tabelle B.60: Schnittstellen
1000
AttributeCharacterIterator
Methoden zum Durchforsten des Texts und der AttributInformationen (SDK 2).
CharacterIterator
Methoden zum Analysieren eines Strings und der Rückgabe diverser Informationen über ihn.
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Ausgewählte Klassen Annotation
Wrapper für Textattribut-Werte (SDK 2).
AttributedString
Text- und zugehörige Attribut-Informationen (SDK 2).
BreakIterator
Eigenschaften zur Lokalisierung von Textgrenzen bzw. -Breaks (SDK 2).
ChoiceFormat
Methoden zum Anheften von Formaten an Werte.
CollationElementIterator
Iterator zum Durchlaufen von Zeichen in einem internationalen String (SDK 2).
Collation
Vergleichen von Unicode-Text.
CollationElementIterator
Iterieren eines internationalen Strings.
CollationKey
Behandelt einen String nach den Regeln eines spezifischen Vergleichsobjekts (SDK 2).
Collator
String-Vergleiche (SDK 2).
DateFormat
Abstrakte Klasse mit einigen Datum-/Uhrzeit-Formatierungs-Subklassen.
DateFormatSymbols
Lokale Datum-/Uhrzeit-Formatierungs-Daten (SDK 2).
DecimalFormat
Methoden zum Formatieren von Zahlen.
DecimalFormatSymbols
Symbole für die Verwendung des Dezimalformats bei der Formatierung von Zahlen (SDK 2).
FieldPosition
Eigenschaften zur Identifizierung von Feldern in formatierten Ausgaben (SDK 2).
Format
Eine Basisklasse für alle Formate.
MessageFormat
Methoden zum Erstellen von formatierten Nachrichten.
NumberFormat
Abstrakte Klasse für alle Zahlenformate.
ParseStatus
Der Status von der jeweiligen Gliederung, wenn man durch einen String mit verschiedenen Formaten geht.
RuleBasedCollator
Subklasse von Collator, um eine einfache Vergleichstabelle zu erstellen (SDK 2).
SimpleDateFormat
Methoden zum Formatieren eines Datums oder der Zeit in einem String.
StringCharacterIterator
Methoden für die bidirektionale Iteration über einen String.
Java 2 Kompendium
Tabelle B.61: Klassen
1001
Anhang B
B.2.34
java.util
Das Paket stellt – wie der Namen schon sagt – Hilfsmittel für die verschiedensten Zwecke bereit. Es beinhaltet verschiedene Utility-Klassen und -Schnittstellen, etwa Zufallszahlen oder Systemeigenschaften. Ausgewählte Schnittstellen Tabelle B.62: Schnittstellen
Collection
Methoden zur Implementation von Sammlungen (SDK 2).
Comparator
Methoden zur Anforderung einer Sammlung von Objekten (SDK 2).
Enumeration
Ein Objekt, das eine Aufzählungsschnittstelle implementiert. Auf die einzelnen Elementen einer Serie kann sukzessive mit der nextElement-Methode zugegriffen werden.
EventListener
Methoden zum Beobachten von Ereignissen (Java 1.1).
Iterator
Methoden zur Iteratation einer Sammlung (SDK 2).
List
Methoden um eine angeforderte Sammlung aufzuzählen (SDK 2).
ListIterator
Methoden zum Aufzählen einer Liste in jedem Verzeichnis (SDK 2).
Map
Methoden zum Mappen von Schlüsseln in Werte (SDK 2).
Map.Entry
Ein Map-Eintrag (ein Paar an Schlüsselwerten).
Observer
Methoden für die Unterstützung von Klassen mit Observable-Objekten.
Set
Sammlung ohne doppelte Elemente (SDK 2).
Ausgewählte Klassen Tabelle B.63: Klassen
1002
AbstractCollection
Skelettartige Implementation von einer Sammlung (SDK 2).
AbstractList
Skelettartige Implementation von einer Liste (SDK 2).
AbstractMap
Skelettartige Implementation von einer Map (SDK 2).
AbstractSequentialList
Skelettartige Implementation von einer Liste in Verbindung mit einem sequenziellen Zugriff (SDK 2).
AbstractSet
Skelettartige Implementation von einem Set (SDK 2).
ArrayList
Implementation von einer Liste mit in der Grö ß e veränderbarem Array (SDK 2).
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Arrays
Eigenschaften für die Array-Sortierung und anderer Manipulationen (SDK 2).
BitSet
Eine Bitmenge.
Calendar
Ein allgemeiner Kalender (Java 1.1).
Collections
Eigenschaften zum Manipulierung oder Zurückgeben von Collection-Objekten (SDK 2).
Date
Das aktuelle Systemdatum, sowie Methoden zum Generieren und Abgleichen von Datumsangaben (Java 1.1, 4 Date()-Konstruktoren, und zahlreiche Methoden gelten mittlerweile als verworfen: UTC(), parse(), getYear(),
Tabelle B.63: Klassen (Forts.)
setYear(), getMonth(), setMonth(), getDate(), setDate(), getDay(), getHours(), setHours(), getMinutes(), setMinutes(), getSeconds(), setSeconds(), toLocaleString(), toGMTString(), und getTimeZoneOffset()). Dictionary
Abstrakte Klasse, die zwischen Schlüsseln und Werten konvertiert (Superklasse von HashTable).
EventObject
Ein Ereignis-Objekt (Java 1.1).
GregorianCalendar
Eine Repräsentation des Gregorianischen Kalenders (Java 1.1).
HashMap
Implementation einer Map – basierend auf Hash-Tabellen (SDK 2).
HashSet
Implementation eines Sets – basierend auf Hash-Tabellen (SDK 2).
Hashtable
Eine Hash-Tabelle.
LinkedList
Implementation von einer gelinkten Liste mit in der Grö ß e veränderbarem Array (SDK 2).
ListResourceBundle
Ein Ressourcen-Versorger für einen Ort (Java 1.1).
Locale
Eine Beschreibung eines geografischen Orts (Java 1.1).
Observable
Abstrakte Klasse für observable-Objekte.
Properties
Eine Hash-Tabelle, die die Eigenschaften zum Setzen und Abrufen von persistenten Merkmalen des Systems oder einer Klasse enthält.
PropertyPermissions
Eigenschaften um den Zugang zu Eigenschaften zu kontrollieren (SDK 2).
PropertyResourceBundle
Ein Ressoursenversorger, der Eigenschaften von einer Datei verwenden kann (Java 1.1).
Java 2 Kompendium
1003
Anhang B Tabelle B.63: Klassen (Forts.)
Random
Utilities für die Erzeugung von Zufallszahlen.
ResourceBundle
Ein Satz von Objekten, die einem Ort zugeordnet sind (Java 1.1).
SimpleTimeZone
Eine vereinfachte Zeitzone (Java 1.1).
Stack
Ein Stack.
StringTokenizer
Utilities für das Splitting von Zeichenketten in einzelne Token.
TimeZone
Eine allgemeine Zeitzone (Java 1.1).
TreeMap
Implementation einer Map – basierend auf einer Baumstruktur (SDK 2).
Vector
Ein Array von Objekten, das dynamisch wachsen kann.
B.2.35
java.util.jar
Hierbei handelt es sich um ein Paket (neu eingeführt im SDK 2) für die Behandlung von Java Archive Resource (JAR)-Dateien. Klassen Tabelle B.64: Klassen
Attribute
Mapping von manifestierten Attributnamen in zugehörigen String-Werten.
Attributes.Name
Repräsentiert einen Attributnamen in dieser Map.
JarEntry
JAR-Datei-Entry.
JarFile
Eigenschaften zum Lesen von JAR-Dateien.
JarInputStream
Eingabestrom, der JAR-Dateien liest.
JarOutputStream
Ausgabestrom, der JAR-Dateien schreibt.
Manifest
Manifestierte Entry-Namen und zugehörige Attribute.
B.2.36
java.util.zip
Dieses Paket enthält Klassen für den Zugriff auf komprimierte Dateiarchive (Zip- und gZip-Algorithmus). Schnittstellen Tabelle B.65: Schnittstellen
1004
Checksum
Methoden zur Berechnung einer Checksumme.
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Klassen Adler32
Berechnet eine Adler 32-Checksumme.
CRC32
Berechnet eine CRC 32-Checksumme.
CheckedInputStream
Eingabestrom mit zugehöriger Checksumme.
CheckedOutputStream
Ausgabestrom mit zugehöriger Checksumme.
Deflater
Komprimierer für unkomprimierte Dateien.
DeflaterOutputStream
Ausgabestrom, der auch komprimiert.
GZIPInputSteam
Eingabestrom von einer gZip-Datei.
GZIPOutputStream
Ausgabestrom in eine gZip-Datei.
Inflater
Dekomprimierer für komprimierte Dateien.
InflaterInputStream
Eingabestrom, der auch dekomprimiert.
ZipEntry
Eine Datei-Entry innerhalb einer Zip-Datei.
ZipFile
Eine vollständige Zip-Datei.
ZipInputStream
Eingabestrom von einer Zip-Datei.
ZipOutputStream
Ausgabestrom in eine Zip-Datei.
B.2.37
Tabelle B.66: Klassen
javax.accessibility
Dieses Paket (ehemals java.awt.accessibility), eingeführt im SDK 2, beinhaltet Möglichkeiten, um mit innovativen Hilfstechnologien (etwa Spracheingabesystemen, Touchscreens oder Blindensprache-Terminals) interagieren zu können. Die daraus resultierenden Applikationen sind nicht auf bestimmte technische Plattformen beschränkt, sondern können auf jeder Maschine eingesetzt werden, die die virtuelle Javamaschine unterstützt.
Ausgewählte Schnittstellen Accessible
Methoden, um ein Benutzerschnittstellen-Element für die verschiedenen Zugriffsmöglichkeiten zugänglich zu machen.
AccessibleText
Methoden, um Text exakt an einer angegebenen Koordinate auf einem grafischen Ausgabegerät auszugeben.
Java 2 Kompendium
Tabelle B.67: Schnittstellen
1005
Anhang B Ausgewählte Klassen Tabelle B.68: Klassen
AccessibleRole
Eine präzise Beschreibung von Regeln, die ein Element in einem User-Interface innerhalb der Schnittstelle einhalten muss.
AccessibleState
Eine präzise Beschreibung des Status von einem Element in einem User-Interface.
AccessibleStateSet
Das Set von allen AccessibleState-Objekten, die den vollständigen Status von einem Schnittstellenelement repräsentieren.
B.2.38
javax.naming
Unterstützung von Nameservices. Neu im JDK 1.3 eingeführt. Das Paket stellt insbesondere eine große Anzahl von spezialisierten Ausnahmen bereit Ausgewählte Schnittstellen Tabelle B.69: Schnittstellen
Context
Namen-Kontext mit einem Satz von Name-zu-ObjektBindungen.
Name
Eine namentliche Repräsentation einer Sequenz von Komponenten.
NameParser
Parsing von Namen.
Ausgewählte Klassen Tabelle B.70: Klassen
Binding
Eine Name-zu-Objekt-Bindung in einem Kontext.
Reference
Eine Referenz auf ein außerhalb des Namens/Verzeichnis-Systems befindlichen Objekt.
Exceptions AuthenticationException AuthenticationNotSupportedException CannotProceedException CommunicationException ConfigurationException ContextNotEmptyException InsufficientResourcesException InterruptedNamingException InvalidNameException LimitExceededException
1006
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
LinkException LinkLoopException MalformedLinkException NameAlreadyBoundException NameNotFoundException NamingException NamingSecurityException NoInitialContextException NoPermissionException NotContextException OperationNotSupportedException PartialResultException ReferralException ServiceUnavailableException SizeLimitExceededException TimeLimitExceededException
B.2.39
javax.naming.directory
Eine im JDK 1.3 eingeführte Erweiterung von javax.naming zur Unterstützung des Zugriffs auf Directory-Services. Schnittstellen Attribute
Mit dem benannten Objekt assoziierte Attribute.
Attributes
Eine Collection von Attributen.
DirContext
Ein Directory-Service-Interface fürVerzeichnisoperationen.
Tabelle B.71: Schnittstellen
Ausgeswähte Klassen BasicAttribute
Basisimplementation des Attribute-Interfaces.
BasicAttributes
Basisimplementation des Attributes-Interfaces.
InitialDirContext
Start-Kontext zur Unterstützung von Verzeichnisoperationen.
B.2.40
Tabelle B.72: Klassen
javax.naming.event, javax.naming.ldap und javax.naming.spi
Drei weitere Pakete zur Unterstützung des Naming-Services von Java. Das Paket javax.naming.event bietet Unterstützung für Ereignisse beim Zugriff auf Naming- und Directory-Services, javax.naming.ldap für erweiterte Operationen und Controls auf Basis von LDAPv3 und javax.naming.spi fungiert als Service-Provider-Interface (SPI).
Java 2 Kompendium
1007
Anhang B
B.2.41
javax.rmi und javax.rmi.CORBA
Das im JDK 1.3 eingeführte Paket javax.rmi umfasst ein User-API für RMIIIOP. Es beinhaltet nur die Klasse PortableRemoteObject. Auch das ebenfalls im JDK 1.3 eingeführte Paket javax.rmi.CORBA dient der Unterstützung von RMI-IIOP.
B.2.42
javax.sound.midi
Im JDK 1.3 wurde die Unterstützung für Multimedia unter Java erheblich erweitert. Dies Paket stellt Interfaces und Klassen zur Ein- und Ausgabe, Sequencing und MIDI (Musical Instrument Digital Interface)-Daten bereit. Ausgewählte Schnittstellen Tabelle B.73: Schnittstellen
MidiChannel
Ein MidiChannel-Objekt, das einen einzelnen MIDI-Channel darstellt.
MidiDevice
Basis-Interface für alle MIDI-Devices.
Receiver
Empfänger für MidiEvent-Objekte.
Sequencer
Ein Hardware- oder Software-Device, der als MIDISequencer zur Wiedergabe dient.
Soundbank
Ein Satz von Instrumenten zum Laden in einen Synthesizer.
Synthesizer
Ein Synthesizer zur Tonerzeugung.
Transmitter
Ein Transmitter, der MidiEvent-Objekte an einen oder mehrere Receiver sendet.
Ausgewählte Klassen Tabelle B.74: Klassen
1008
Instrument
Ein Algorithmus zum Erzeugen eines Instrumentenklangs.
MidiEvent
Eine MIDI-Botschaft mit korrespondierendem Timestamp in Ticks.
MidiFileFormat
Allgemeine Informationen über MIDI-Dateitypen und Timing.
MidiMessage
Basisklasse für alle MIDI-Messages.
MidiSystem
Zugriff auf die installiereten MIDI-Systemressourcen.
Patch
Repräsentation eines einzelnen Instruments in einem MIDI-Synthesizer.
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Sequence
Datenstruktur mit Musikinformationen, die von einem Sequencer-Objekt wiedergegeben werden kann.
Track
Ein unabhängiger Strom von MIDI-Events, der zusammen mit anderen Tracks in einem Standard-MIDI-File abgelegt werden kann.
B.2.43
Tabelle B.74: Klassen (Forts.)
javax.sound.midi.spi, javax.sound.sampled und javax.sound.sampled.spi
Diese drei im JDK 1.3 eingeführten Pakete bieten das Paket javax.sound.midi erweiternde Unterstützung für MIDI. javax.sound.midi.spi ist ein Serviceprovider zur Implementierung von neuen MIDI-Devices, MIDI-Filereaders und MIDI-Filewriters sowie neuen Sounds. javax.sound.sampled unterstützt gesampelde Audiodaten und javax.sound.sampled.spi ist ein weiterer Serviceprovider zur Implementierung von neuen MIDI-Devices, MIDI-Filereaders und MIDI-Filewriters sowie neuen Audioformaten.
B.2.44
javax.swing & Co
Bei Swing handelt es sich um eine Paketsammlung – oder besser: ein ganzes Konzept – (in der aktuellen Form neu eingeführt im SDK 2), das ein ganzes Set von neuen grafischen Benutzerschnittstellen-Komponenten und andere Interface-Erweiterungen enthält. Swing Komponenten können automatisch das Look and Feel einer Plattform (beispielsweise Windows 95, Macintosh und Solaris) annehmen. Swing gab es schon vor dem SDK 2. Wurde das Swing-Paket in den den Vorgängerversionen des JDK 1.2 Final noch unter java.awt.swing geführt, ist es jetzt – wie zahlreiche damit verbundenen Pakete in der javax-Struktur eingeordnet. Insgesamt ist die Struktur des Swing-APIs vollkommen überarbeitet worden und hat vielfach nichts mehr gemein mit dem Konzept der Vorgängerversionen. Zahlreiche Klassen und Schnittstellen, die selbst in den Beta-Ausführungen des JDK 1.2 vorgestellt wurden, waren in dessen Finalrelease nicht realisiert. Entweder ist deren Funktionalität in anderen Paketen und Klassen aufgegangen oder die Realisierung hat sich als nicht sinnvoll herausgestellt. Die Swing-Komponenten beinhalten Duplikate von allen AWT-Komponenten und unzählige zusätzliche Komponenten. Das Konzept umfasst neben dem Hauptpaket javax.swing im JDK 1.3 die folgenden Unterpakete: javax.swing.border javax.swing.colorchooser
Java 2 Kompendium
1009
Anhang B javax.swing.event javax.swing.filechooser javax.swing.plaf javax.swing.plaf.basic javax.swing.plaf.metal javax.swing.plaf.multi javax.swing.table javax.swing.text javax.swing.text.html javax.swing.text.html.parser javax.swing.text.rtf javax.swing.tree javax.swing.undo javax.transaction
Das Paket javax.transaction kam erst im JDK 1.3 hinzu. Die anderen Pakete stehen bereits seit dem JDK 1.2 zur Verfügung. Wir werden hier nur das Hauptpaket javax.swing behandeln und nachfolgend einige ausgewählte Subpakete. Eine vollständige Auflistung sprengt unseren Rahmen. Detaillierte Informationen finden Sie in der Dokumentation. Ausgewählte Schnittstellen Tabelle B.75: Schnittstellen
1010
Action
Methoden in Verbindung mit verschiedenen Komponenten mit action-Kommandofunktionalität.
BoundedRangeModel
Methoden, um die Daten in einer Bildlaufleiste oder einer Slider-Komponente darzustellen.
ButtonModel
Methoden, um den Status eines Buttons darzustellen.
CellEditor
Methoden zum Editieren von Tabellen in Komponenten, beispielsweise ComboBoxen und Auswahllisten.
ComboBoxEditor
Methoden zum Editieren von ComboBoxen.
ComboBoxModel
Methoden, um die Daten in einer in einer ComboBox darzustellen.
DesktopManager
Methoden, um die Look-and-Feel-Eigenschaft für JDesktopPane zu implementieren.
Icon
Kleines Bild für die Verwendung bei Komponenten.
JComboBox.KeySelectionManager
Methode zum Definieren von einem KeySelectionManager.
ListCellRenderer
Methoden zum Zeichnen der Zellen in einer Liste.
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
ListModel
Methoden zum Darstellen der Daten in einer Liste.
ListSelectionModel
Methoden zum Darstellen der aktuellen Selektion in einer Liste.
Renderer
Tabelle B.75: Schnittstellen (Forts.)
Enthält die abstrakten Methoden getComponent() setValue(Object, boolean).
ScrollPaneConstants
Konstanten für die Verwendung mit JscrollPane-Objekten.
Scrollable
Methoden zur Verwaltung von Informationen in einem scrollenden Container.
SingleSelectionModel
Methoden zum Darstellen der ausschließlich erlaubten Selektion in einer Liste.
SwingConstants
Konstanten für die Verwendung mit Swing-Komponenten
UIDefaults.ActiveValue
Methoden zum Speichern eines Eintrags in der DefaultTabelle.
UIDefaults.LazyValue
Methoden zum Speichern eines Eintrags in der DefaultTabelle, die nicht konstruiert wird, bis sie angewendet wird.
WindowConstants
Konstanten für die Verwendung mit Jwindow-Komponenten.
Ausgewählte Klassen AbstractAction
Eigenschaften für action-Objekte.
AbstractButton
Gemeinsame Eigenschaften für button-Komponenten (die Methoden getLabel() und setLabel()gelten im SDK 2 als veraltet).
AbstractListModel
Datenmodell, das eine Listenkomponente füllt.
BorderFactory
Erzeuger von Standardrand-Objekten.
Box
Container, der den Boxlayoutmanager verwendet.
Box.Filler
Boxcontainer mit keiner sichtbaren Oberfläche.
BoxLayout
Box-Layoutmanager.
ButtonGroup
Eine Klasse, die innerhalb einer Gruppe von Schaltflächen nur einem Button erlaubt, selektiert zu werden.
CellRendererPane
Eine Klasse, die zwischen Zellinterpretationen und Komponenten eingefügt wird, um repaint() und invalidate()Aufrufe abzublocken.
Java 2 Kompendium
Tabelle B.76: Klassen
1011
Anhang B Tabelle B.76: Klassen (Forts.)
1012
DebugGraphics
Unterstützung von grafischem Debuggen.
DefaultBoundedRangeModel
Allgemein begrenztes Bereichsmodell.
DefaultButtonModel
Defaultversion eines Button-Datenmodells.
DefaultCellEditor
Default-Editor für Tabellen und Baumzellen.
DefaultDesktopManager
Allgemeiner Desktopmanager.
DefaultFocusManager
Allgemeiner Focusmanager.
DefaultListModel
Defaultversion eines Listkomponenten-Datenmodells.
DefaultListSelectionModel
Default-Version der selektierten Einträgen in einer Liste.
DefaultSingleSelectionModel
Default-Version des ausschließlich selektierten Eintrags in einer Liste.
FocusManager
Ein Komponenten-Fokusmanager.
GrayFilter
Unterbinden der Selektion einer Komponente, indem sie grau gesetzt wird.
ImageIcon
Implementation eines Icon-Interfaces.
JApplet
Swing-Appletfenster.
JButton
Swing – anklickbarer Button.
JCheckBox
Swing-Checkbox.
JCheckBoxMenuItem
Swing-Checkbox-Eintrag in einem Pulldown-Menü.
JColorChooser
Swing-Farbauswahldialog.
JComboBox
Swing – kombinierte Listbox und Textfeld.
JComponent
Swing-Komponenten-Ursprungsklasse.
JDesktopPane
Swing-Desktoppanel.
JDialog
Swing-Dialogfenster.
JEditorPane
Swing-Editorpanel.
JFileChooser
Swing-Dateiauswahldialog.
JFrame
Swing-Framefenster.
JInternalFrame
Swing-internes Frame.
JLabel
Swing-Label.
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
JLayeredPane
Swing – schichtenweiser Panecontainer.
JList
Swing-Auswahlliste.
JMenu
Swing-Pulldown-Menü.
JMenuBar
Swing-Menubar in einer Fensterkomponente.
JMenuItem
Swing-Menüeintrag in einem Pulldown-Menü.
JOptionPane
Swing-Options-Panelcontainer.
JPanel
Swing-Panelcontainer.
JPasswordField
Swing-Passwort-Textfeld mit verdeckten Zeichen.
JPopupMenu
Swing-Popup-Menü.
JProgressBar
Swing-Progressbar Komponente.
JRadioButton
Swing-Radiobutton.
JRadioButtonMenuItem
Swing-Radiobutton in einem Pulldown-Menü.
JRootPane
Swing-Rootpanecontainer.
JScrollBar
Swing-Bildlaufleiste.
JScrollPane
Swing – scrollbarer Panecontainer.
JSeparator
Swing-Menü-Separatorkomponente.
JSlider
Swing-Slider.
JSplitPane
Swing-geteilter Panelcontainer.
JTabbedPane
Swing-Panelcontainer mit Tabulator.
JTable
Swing-Tabelle.
JTextArea
Swing – multiline Textfeld.
JTextField
Swing – singleline Textfeld.
JTextPane
Swing-Text-Panecontainer.
JToggleButton
Swing – umschaltbarer Button.
JToggleButton. ToggleButtonModel
Das ToggleButton-Modell.
JToolBar
Swing-Toolbar.
JToolBar.Separator
Seperator für die Swing-Toolbar.
JToolTip
Swing-Popup-ToolTip-Komponente.
Java 2 Kompendium
Tabelle B.76: Klassen (Forts.)
1013
Anhang B Tabelle B.76: Klassen (Forts.)
JTree
Swing-Verzeichnisbaum.
JTree.DynamicUtilTreeNode
Unterstützung von dynamischen Vorgängen bei der Veränderung eines Verzeichnisbaums.
JTree.EmptySelectionModel
Ein Baumselektionsmodell, das keine Selektion erlaubt (nur Anzeige).
JTree. TreeSelectionRedirector
Unterstützung der Generierung eines neuen TreeSelectionEvent mit JTree als Source und Weiterreichen an alle betroffenen Listener.
JViewport
Swing-Viewportcontainer.
JWindow
Swing-Fenstercontainer.
KeyStroke
Ein Schlüssel, der mit der Tastatur eingegeben wird.
OverlayLayout
Overlay-Layoutmanager.
ProgressMonitor
Eigenschaften zum Verfolgen von Fortschritten bei einer Operation.
ProgressMonitorInputStream
Eigenschaften zum Verfolgen von Fortschritten bei einem Eingabestrom.
RepaintManager
Manager für die Komponenteanzeige.
ScrollPaneLayout
Scrollpanel-Layoutmanager.
SizeRequirements
Grö ß enangaben für die Verwendung in einem Layoutmanager.
SwingUtilities
Utility-Methoden für die Verwendung unter Swing.
Timer
Ein Event, das ein Intervall steuert.
ToolTipManager
Manager von ToolTip-Komponenten.
UIDefaults
Defaultwerte für Swing-Komponenten.
UIManager
Klasse zum Kennzeichnen des aktuellen Look and Feel.
ViewportLayout
Viewport-Layoutmanager.
B.2.45
javax.swing.event
Hierbei (vormals java.awt.swing.event) handelt es sich um ein Swing-Unterpaket (neu eingeführt im SDK 2) zur Unterstützung der neuen Ereignisse, die von den Swing-Komponenten ausgelöst werden.
1014
Java 2 Kompendium
Beschreibung der Pakete
Anhang B
Ausgewählte Schnittstellen AncestorListener
Methoden, um eine Veränderung in einer Swing-Komponente oder den Superklassen zu bemerken.
ChangeListener
Methoden, um auf Textveränderungen zu reagieren.
DocumentEvent
Methoden, um auf Dokumentveränderungen zu reagieren.
DocumentEvent. ElementChange
Beschreibt die Veränderungen eines Elements.
DocumentListener
Weitere Methoden, um auf Dokumentveränderungen zu reagieren.
ListDataListener
Ein ListData-Listener.
ListSelectionListener
Ein Listener, der beobachtet, wenn sich in einer Listenselektion Werte ändern.
MenuListener
Ein Menu-Listener.
TableColumnModelListener
Methoden, um auf Veränderungen in einer Tabellenspalte zu reagieren
TableModelListener
Methoden, um auf Verän.derungen in einer Tabellen zu reagieren.
TreeExpansionListener
Methoden, um auf die Erweiterung von einem Verzeichnisbaum zu reagieren.
TreeModelListener
Methoden, um auf die Veränderungen von einem Verzeichnisbaum zu reagieren.
TreeSelectionListener
Methoden, um auf die Selektionen in einem Verzeichnisbaum zu reagieren.
Tabelle B.77: Schnittstellen
Ausgewählte Klassen AncestorEvent
Ereignisse, wenn sich JComponent oder die Superklassen verändern.
ChangeEvent
Text-Veränderungsereignisse.
EventListenerList
Liste der Ereignis-Listener.
ListDataEvent
ListData-Ereignisse.
ListSelectionEvent
Ereignisse, wenn eine Listenselektion durchgeführt wird.
MenuEvent
Menü-Ereignisse.
Java 2 Kompendium
Tabelle B.78: Klassen
1015
Anhang B Tabelle B.78: Klassen (Forts.)
TableColumnModelEvent
Ereignisse bei Tabellenspalten-Veränderungen.
TableModelEvent
Ereignisse bei Tabellen-Veränderungen.
TreeExpansionEvent
Ereignisse bei Tabellen-Erweiterungen.
TreeModelEvent
Ereignisse bei Veränderungen im Verzeichnisbaum.
TreeSelectionEvent
Ereignisse bei Selektionen im Verzeichnisbaum.
B.2.46
javax.swing.undo
Hierbei (vormals java.awt.swing.undo) handelt es sich um ein Swing-Unterpaket (neu eingeführt im SDK 2) zur Unterstützung für Undo-Befehlen beim Editieren. Schnittstellen Tabelle B.79: Schnittstellen
StateEditable
Methoden für Objekte, deren Status Undo unterstützt.
UndoableEdit
Methoden zur Darstellung einer vollständigen Veränderung, die rückgängig gemacht werden kann.
Klassen Tabelle B.80: Klassen
AbstractUndoableEdit
Eigenschaften, die die UndoableEdit-Schnittstelle implementieren.
CompoundEdit
Eigenschaften, um einige kleinere Rückgängig-Schritte mit grö ß eren zu kombinieren.
StateEdit
Allgemeine Veränderungen für Objekte, die den Status verändern können.
UndoManager
Listener für rückgängig zu machende Ereignisse.
UndoableEditSupport
Unterstützung der Ereignisse von UndoableEditEvent.
B.2.47
Die CORBA-Pakete
Neben den bisher behandelten Paketen der SDK 2-Standard-API zählen noch die nachfolgenden Pakete zum SDK 2-Kern. Sie stellen die Funktionalitäten für die Zusammenarbeit zwischen Java und dem CORBA-Konzept bereit: org.omg.CORBA org.omg.CORBA_2_3 1016
Java 2 Kompendium
Die veralteten Elemente des Java-API 1.3
Anhang B
org.omg.CORBA_2_3.portable org.omg.CORBA.DynAnyPackage org.omg.CORBA.ORBPackage org.omg.CORBA.portable org.omg.CORBA.TypeCodePackage org.omg.CosNaming org.omg.CosNaming.NamingContextPackage org.omg.SendingContext org.omg.stub.java.rmi
Die Pakete org.omg.CORBA_2_3, org.omg.CORBA_2_3.portable, org.omg.SendingContext und org.omg.stub.java.rmi kamen im JDK 1.3 neu hinzu.
B.2.48
Nicht enthalten – das Servlets-API
Die vor dem SDK 2 zur Standard-API des JDK gehörenden Pakete javax.servlet und javax.servlet.http für die Realisierung von ServletFunktionalität sind in ein eigenständiges API ausgelagert. Das eigenständige Produkt – das Java Servlet Development Kit – wird wie das JDK frei von Sun zur Verfügung gestellt und kann von der Servlet-Produkt-Seite auf den Java-Software-Webseiten geladen werden (siehe dazu auch Kapitel 16).
B.3
Die veralteten Elemente des Java-API 1.3
Die nachfolgenden Schnittstellen, Klassen, Methoden, Konstruktoren und Felder werden mittlerweile von Sun als veraltet bzw. verworfen (deprecated) bezeichnet, werden aber dennoch in der 1.3-API mit aufgenommen, um die Kompatibilität mit den vorangegangenen Versionen sicherzustellen. Es gibt jedoch für fast jedes der veralteten Elemente mittlerweile neuere Lösungen oder die Funktionalität wird nicht mehr benötigt. Auch dazu finden Sie detailiertere Informationen in der API-Dokumention. Um die Veränderungen in der Java-API auf einen Blick zur Verfügung zu haben, folgt hier die Auflistung der als veraltet oder verworfen gekennzeichneten Elemente – die deprecated APIs. Um es noch einmal deutlich zu machen – die Verwendung dieser veralteten Elemente ist keinenfalls verboten (und in vielen Fällen sogar noch notwendig – beispielsweise für Applets, denn viele Browser unterstützten den 1.3Standard oder gar den 1.1-Standard von Java noch nicht). Eine als deprecated gekennzeichnete API ist eine Klasse oder ein Klassenbestandteil einer Vorgängerversion der aktuellen Java-Plattform, für die es in der neuen API eine bessere Lösung gibt und die – wenn es geht – nicht mehr verwendet werden soll.
Java 2 Kompendium
1017
Anhang B Hier folgt die Liste mit deprecated gekennzeichneten Elementen des 1.3-API (Final):
B.3.1
Klassen (deprecated)
javax.accessibility.AccessibleResourceBundle javax.swing.text.DefaultTextUI java.security.Identity java.security.cert package java.security.Principal. java.security.IdentityScope java.io.LineNumberInputStream java.rmi.server.LogStream java.rmi.server.Operation org.omg.CORBA.Principal org.omg.CORBA.PrincipalHolder java.security.Signer java.io.StringBufferInputStream javax.swing.text.TableView.TableCell
B.3.2
Interfaces (deprecated)
java.security.Certificate java.rmi.server.LoaderHandler java.rmi.registry.RegistryHandler java.rmi.server.RemoteCall java.rmi.server.Skeleton
B.3.3
Exceptions (deprecated)
java.rmi.RMISecurityException java.rmi.ServerRuntimeException java.rmi.server.SkeletonMismatchException java.rmi.server.SkeletonNotFoundException
B.3.4
Felder (deprecated)
javax.swing.JViewport.backingStore javax.swing.plaf.basic.BasicDesktopPaneUI.closeKey java.awt.Frame.CROSSHAIR_CURSOR java.awt.Frame.DEFAULT_CURSOR javax.swing.JRootPane.defaultPressAction javax.swing.JRootPane.defaultReleaseAction javax.swing.plaf.basic.BasicSplitPaneUI.dividerResizeToggleKey javax.swing.plaf.basic.BasicTabbedPaneUI.downKey
1018
Java 2 Kompendium
Die veralteten Elemente des Java-API 1.3
Anhang B
javax.swing.plaf.basic.BasicToolBarUI.downKey javax.swing.plaf.basic.BasicSplitPaneUI.downKey java.awt.Frame.E_RESIZE_CURSOR javax.swing.plaf.basic.BasicSplitPaneUI.endKey java.awt.Frame.HAND_CURSOR javax.swing.plaf.basic.BasicSplitPaneUI.homeKey java.net.HttpURLConnection.HTTP_SERVER_ERROR java.lang.SecurityManager.inCheck javax.swing.plaf.basic.BasicSplitPaneUI.keyboardDownRightListener javax.swing.plaf.basic.BasicSplitPaneUI.keyboardEndListener javax.swing.plaf.basic.BasicSplitPaneUI.keyboardHomeListener javax.swing.plaf.basic.BasicSplitPaneUI.keyboardResizeToggleListener javax.swing.plaf.basic.BasicSplitPaneUI.keyboardUpLeftListener javax.swing.plaf.basic.BasicTabbedPaneUI.leftKey javax.swing.plaf.basic.BasicToolBarUI.leftKey javax.swing.plaf.basic.BasicSplitPaneUI.leftKey javax.swing.plaf.basic.BasicDesktopPaneUI.maximizeKey javax.swing.plaf.basic.BasicDesktopPaneUI.minimizeKey java.awt.Frame.MOVE_CURSOR java.awt.Frame.N_RESIZE_CURSOR javax.swing.plaf.basic.BasicDesktopPaneUI.navigateKey javax.swing.plaf.basic.BasicDesktopPaneUI.navigateKey2 java.awt.Frame.NE_RESIZE_CURSOR java.awt.Frame.NW_RESIZE_CURSOR javax.swing.plaf.basic.BasicInternalFrameUI.openMenuKey java.awt.datatransfer.DataFlavor.plainTextFlavor getTransferData(DataFlavor.plainTextFlavor). javax.swing.text.html.FormView.RESET javax.swing.table.TableColumn.resizedPostingDisableCount javax.swing.plaf.basic.BasicTabbedPaneUI.rightKey javax.swing.plaf.basic.BasicToolBarUI.rightKey javax.swing.plaf.basic.BasicSplitPaneUI.rightKey java.awt.Frame.S_RESIZE_CURSOR java.awt.Frame.SE_RESIZE_CURSOR javax.swing.text.html.FormView.SUBMIT java.awt.Frame.SW_RESIZE_CURSOR java.awt.Frame.TEXT_CURSOR javax.swing.plaf.basic.BasicTabbedPaneUI.upKey javax.swing.plaf.basic.BasicToolBarUI.upKey javax.swing.plaf.basic.BasicSplitPaneUI.upKey java.awt.Frame.W_RESIZE_CURSOR java.awt.Frame.WAIT_CURSOR
B.3.5
Methoden (deprecated)
java.awt.Component.action(Event, Object) java.awt.List.addItem(String) java.awt.List.addItem(String, int) java.awt.CardLayout.addLayoutComponent(String, Component)
Java 2 Kompendium
1019
Anhang B java.awt.BorderLayout.addLayoutComponent(String, Component) java.awt.List.allowsMultipleSelections() java.lang.ThreadGroup.allowThreadSuspension(boolean) java.awt.TextArea.appendText(String) java.awt.Component.bounds() java.lang.SecurityManager.classDepth(String) java.lang.SecurityManager.classLoaderDepth() java.awt.List.clear() java.awt.image.renderable.RenderContext.concetenateTransform(AffineTransform) java.awt.Container.countComponents() java.awt.Menu.countItems() java.awt.List.countItems() java.awt.Choice.countItems() java.awt.MenuBar.countMenus() java.lang.Thread.countStackFrames() org.omg.CORBA.ORB.create_recursive_sequence_tc(int, int) javax.swing.plaf.basic.BasicSplitPaneUI.createKeyboardDownRightListener() javax.swing.plaf.basic.BasicSplitPaneUI.createKeyboardEndListener() javax.swing.plaf.basic.BasicSplitPaneUI.createKeyboardHomeListener() javax.swing.plaf.basic.BasicSplitPaneUI.createKeyboardResizeToggleListener() javax.swing.plaf.basic.BasicSplitPaneUI.createKeyboardUpLeftListener() javax.swing.JTable.createScrollPaneForTable(JTable) javax.swing.text.TableView.createTableCell(Element) java.lang.SecurityManager.currentClassLoader() java.lang.SecurityManager.currentLoadedClass() java.lang.ClassLoader.defineClass(byte[], int, int) java.awt.List.delItem(int) java.awt.List.delItems(int, int) java.awt.Component.deliverEvent(Event) java.awt.Container.deliverEvent(Event) java.awt.Component.disable() java.awt.MenuItem.disable() javax.swing.table.TableColumn.disableResizedPosting() java.rmi.server.Skeleton.dispatch(Remote, RemoteCall, int, long) java.rmi.server.RemoteCall.done() java.rmi.server.RemoteRef.done(RemoteCall) java.awt.Component.enable() java.awt.MenuItem.enable() java.awt.Component.enable(boolean) java.awt.MenuItem.enable(boolean) javax.swing.table.TableColumn.enableResizedPosting() java.security.SignatureSpi.engineGetParameter(String) java.security.SignatureSpi.engineSetParameter(String, Object) java.awt.datatransfer.DataFlavor.equals(String) org.omg.CORBA.ServerRequest.except(Any) java.rmi.server.RemoteCall.executeCall() org.omg.CORBA.Any.extract_Principal() org.omg.CORBA.ORB.get_current() java.security.Security.getAlgorithmProperty(String, String) java.sql.CallableStatement.getBigDecimal(int, int)
1020
Java 2 Kompendium
Die veralteten Elemente des Java-API 1.3
Anhang B
java.sql.ResultSet.getBigDecimal(int, int) java.sql.ResultSet.getBigDecimal(String, int) java.awt.Polygon.getBoundingBox() java.lang.String.getBytes(int, int, byte[], int) java.awt.Graphics.getClipRect() javax.swing.JPopupMenu.getComponentAtIndex(int) javax.swing.JMenuBar.getComponentAtIndex(int) java.awt.CheckboxGroup.getCurrent() java.awt.Frame.getCursorType() java.sql.Time.getDate() java.util.Date.getDate() java.sql.Time.getDay() java.util.Date.getDay() java.net.URLConnection.getDefaultRequestProperty(String) java.rmi.server.LogStream.getDefaultStream() javax.swing.plaf.basic.BasicSplitPaneUI.getDividerBorderSize() java.lang.System.getenv(String) java.awt.Toolkit.getFontList() javax.swing.text.LabelView.getFontMetrics() java.awt.Toolkit.getFontMetrics(Font) java.awt.Toolkit.getFontPeer(String, int) java.sql.Date.getHours() java.util.Date.getHours() java.lang.SecurityManager.getInCheck() java.rmi.server.RemoteCall.getInputStream() javax.swing.KeyStroke.getKeyStroke(char, boolean) javax.swing.AbstractButton.getLabel() java.awt.Scrollbar.getLineIncrement() java.lang.Runtime.getLocalizedInputStream(InputStream) java.lang.Runtime.getLocalizedOutputStream(OutputStream) java.sql.DriverManager.getLogStream() java.awt.FontMetrics.getMaxDecent() javax.swing.JInternalFrame.getMenuBar() javax.swing.JRootPane.getMenuBar() java.sql.Date.getMinutes() java.util.Date.getMinutes() java.sql.Time.getMonth() java.util.Date.getMonth() java.rmi.server.Operation.getOperation() java.rmi.server.Skeleton.getOperations() java.rmi.server.RemoteCall.getOutputStream() java.rmi.server.LogStream.getOutputStream() java.awt.Scrollbar.getPageIncrement() java.security.Signature.getParameter(String) java.awt.Component.getPeer() java.awt.MenuComponent.getPeer() java.awt.Font.getPeer() java.rmi.server.RemoteCall.getResultStream(boolean) java.sql.Date.getSeconds() java.util.Date.getSeconds()
Java 2 Kompendium
1021
Anhang B java.rmi.server.RMIClassLoader.getSecurityContext(ClassLoader) java.rmi.server.LoaderHandler.getSecurityContext(ClassLoader) javax.swing.JPasswordField.getText() javax.swing.JPasswordField.getText(int, int) java.util.Date.getTimezoneOffset() Calendar.get(Calendar.DST_OFFSET). java.net.MulticastSocket.getTTL() java.net.DatagramSocketImpl.getTTL() java.sql.ResultSet.getUnicodeStream(int) java.sql.ResultSet.getUnicodeStream(String) javax.swing.ScrollPaneLayout.getViewportBorderBounds(JScrollPane) java.awt.Scrollbar.getVisible() java.sql.Time.getYear() java.util.Date.getYear() java.awt.Component.gotFocus(Event, Object) java.awt.Component.handleEvent(Event) java.awt.Component.hide() javax.swing.JComponent.hide() java.lang.SecurityManager.inClass(String) java.lang.SecurityManager.inClassLoader() org.omg.CORBA.Any.insert_Principal(Principal) javax.swing.text.html.HTMLEditorKit.InsertHTMLTextAction.insertAtBoundry(JEdi torPane, HTMLDocument, int, Element, String, HTML.Tag, HTML.Tag) java.awt.TextArea.insertText(String, int) java.awt.Container.insets() java.awt.Component.inside(int, int) java.awt.Polygon.inside(int, int) java.awt.Rectangle.inside(int, int) java.rmi.server.RemoteRef.invoke(RemoteCall) javax.swing.JViewport.isBackingStoreEnabled() java.lang.Character.isJavaLetter(char) java.lang.Character.isJavaLetterOrDigit(char) java.awt.List.isSelected(int) java.lang.Character.isSpace(char) java.rmi.dgc.VMID.isUnique() java.awt.Component.keyDown(Event, int) java.awt.Component.keyUp(Event, int) java.awt.Component.layout() java.awt.Container.layout() java.awt.ScrollPane.layout() java.rmi.server.RMIClassLoader.loadClass(String) java.rmi.server.LoaderHandler.loadClass(String) java.rmi.server.LoaderHandler.loadClass(URL, String) java.awt.Component.locate(int, int) java.awt.Container.locate(int, int) java.awt.Component.location() java.rmi.server.LogStream.log(String) java.awt.Component.lostFocus(Event, Object) java.awt.Component.minimumSize() java.awt.Container.minimumSize()
1022
Java 2 Kompendium
Die veralteten Elemente des Java-API 1.3
Anhang B
java.awt.List.minimumSize() java.awt.TextField.minimumSize() java.awt.TextArea.minimumSize() java.awt.List.minimumSize(int) java.awt.TextField.minimumSize(int) java.awt.TextArea.minimumSize(int, int) javax.swing.text.View.modelToView(int, Shape) java.awt.Component.mouseDown(Event, int, int) java.awt.Component.mouseDrag(Event, int, int) java.awt.Component.mouseEnter(Event, int, int) java.awt.Component.mouseExit(Event, int, int) java.awt.Component.mouseMove(Event, int, int) java.awt.Component.mouseUp(Event, int, int) java.awt.Component.move(int, int) java.awt.Rectangle.move(int, int) org.omg.CORBA.Principal.name() org.omg.CORBA.Principal.name(byte[]) java.rmi.server.RemoteRef.newCall(RemoteObject, Operation[], int, long) java.awt.Component.nextFocus() java.awt.datatransfer.DataFlavor.normalizeMimeType(String) java.awt.datatransfer.DataFlavor.normalizeMimeTypeParameter(String, String) org.omg.CORBA.ServerRequest.op_name() org.omg.CORBA.ServerRequest.params(NVList) java.util.Date.parse(String) java.rmi.server.LogStream.parseLevel(String) java.awt.Component.postEvent(Event) java.awt.MenuComponent.postEvent(Event) java.awt.MenuContainer.postEvent(Event) java.awt.Window.postEvent(Event) java.awt.image.renderable.RenderContext.preConcetenateTransform(AffineTransfo rm) java.awt.Component.preferredSize() java.awt.Container.preferredSize() java.awt.List.preferredSize() java.awt.TextField.preferredSize() java.awt.TextArea.preferredSize() java.awt.List.preferredSize(int) java.awt.TextField.preferredSize(int) java.awt.TextArea.preferredSize(int, int) org.omg.CORBA.portable.InputStream.read_Principal() java.io.ObjectInputStream.readLine() java.io.DataInputStream.readLine() java.rmi.registry.RegistryHandler.registryImpl(int) java.rmi.registry.RegistryHandler.registryStub(String, int) java.rmi.server.RemoteCall.releaseInputStream() java.rmi.server.RemoteCall.releaseOutputStream() java.awt.TextArea.replaceText(String, int, int) java.awt.Component.reshape(int, int, int, int) java.awt.Rectangle.reshape(int, int, int, int) java.awt.Component.resize(Dimension)
Java 2 Kompendium
1023
Anhang B java.awt.Component.resize(int, int) java.awt.Rectangle.resize(int, int) org.omg.CORBA.ServerRequest.result(Any) java.lang.ThreadGroup.resume() java.lang.Thread.resume() java.lang.System.runFinalizersOnExit(boolean) java.lang.Runtime.runFinalizersOnExit(boolean) java.util.Properties.save(OutputStream, String) javax.swing.JViewport.setBackingStoreEnabled(boolean) java.awt.CheckboxGroup.setCurrent(Checkbox) java.awt.Frame.setCursor(int) java.sql.Time.setDate(int) java.util.Date.setDate(int) java.net.URLConnection.setDefaultRequestProperty(String, String) java.rmi.server.LogStream.setDefaultStream(PrintStream) java.awt.TextField.setEchoCharacter(char) java.sql.Date.setHours(int) java.util.Date.setHours(int) javax.swing.AbstractButton.setLabel(String) java.awt.Scrollbar.setLineIncrement(int) java.sql.DriverManager.setLogStream(PrintStream) javax.swing.JInternalFrame.setMenuBar(JMenuBar) javax.swing.JRootPane.setMenuBar(JMenuBar) java.sql.Date.setMinutes(int) java.util.Date.setMinutes(int) java.sql.Time.setMonth(int) java.util.Date.setMonth(int) java.awt.List.setMultipleSelections(boolean) java.rmi.server.LogStream.setOutputStream(OutputStream) java.awt.Scrollbar.setPageIncrement(int) java.security.Signature.setParameter(String, Object) java.rmi.server.RemoteStub.setRef(RemoteStub, RemoteRef) java.sql.Date.setSeconds(int) java.util.Date.setSeconds(int) java.net.MulticastSocket.setTTL(byte) java.net.DatagramSocketImpl.setTTL(byte) use setTimeToLive instead. java.sql.PreparedStatement.setUnicodeStream(int, InputStream, int) java.net.URLStreamHandler.setURL(URL, String, String, int, String, String) java.sql.Time.setYear(int) java.util.Date.setYear(int) java.awt.Component.show() java.awt.Component.show(boolean) java.awt.Component.size() javax.swing.JTable.sizeColumnsToFit(boolean) java.lang.ThreadGroup.stop() java.lang.Thread.stop() java.lang.Thread.stop(Throwable) java.lang.ThreadGroup.suspend() java.lang.Thread.suspend()
1024
Java 2 Kompendium
HTML-Elemente und -Attribute
Anhang B
java.util.Date.toGMTString() java.util.Date.toLocaleString() java.rmi.server.Operation.toString() java.rmi.server.LogStream.toString() java.io.ByteArrayOutputStream.toString(int) java.util.Date.UTC(int, int, int, int, int, int) javax.swing.text.View.viewToModel(float, float, Shape) org.omg.CORBA.portable.OutputStream.write_Principal(Principal) java.rmi.server.LogStream.write(byte[], int, int) java.rmi.server.LogStream.write(int)
B.3.6
Constructors (deprecated)
java.sql.Date(int, int, int) java.util.Date(int, int, int) java.util.Date(int, int, int, int, int) java.util.Date(int, int, int, int, int, int) java.util.Date(String) java.rmi.server.Operation(String) java.rmi.RMISecurityException(String) java.rmi.RMISecurityException(String, String) java.rmi.ServerRuntimeException(String, Exception) java.rmi.server.SkeletonMismatchException(String) java.net.Socket(InetAddress, int, boolean) java.net.Socket(String, int, boolean) java.io.StreamTokenizer(InputStream) java.lang.String(byte[], int) java.lang.String(byte[], int, int, int) org.omg.CORBA.TCKind(int) java.sql.Time(int, int, int) java.sql.Timestamp(int, int, int, int, int, int, int)
B.4
HTML-Elemente und -Attribute
HTML 4.0 beinhaltet eine umfangreiche Anzahl von offiziellen Befehlen und noch mehr Attribute. Hier finden Sie die Tags und zugehörige Attribute.
B.4.1
HTML-Tags
Nachfolgend sollen alphabetisch sämtliche Elemente, die zum offiziellen HTML 4.0-Sprachstandard gehören, aufgelistet werden. Die Auflistung beruht auf der offiziellen Veröffentlichung des W3C (http://www.w3.org/TR/ REC-html40/index/elements.html).
Java 2 Kompendium
1025
Anhang B Wenn im Start- und Ende-Tag keine Angaben stehen, werden die Tags jeweils gefordert. Dabei gilt, dass es Situationen geben kann, wo das EndeTag zwar gefordert wird, es aber in der Praxis ohne negative Auswirkung weggelassen werden kann. Umgekehrt gilt: Sofern ein Ende-Tag verboten ist, gibt es keinen Container, der dem Start-Tag nachfolgenden Inhalt enthalten kann. Ein solches Element, wo das Ende-Tag verboten ist, kann aber ohne Probleme mit einem fiktiven Ende-Tag ausgestattet werden (etwa aus dokumentarischen Gründen). Das Ende-Tag wird keinen Fehler erzeugen – er wird durch das Prinzip der Fehlertoleranz einfach ignoriert. Als deprecated gekennzeichnete Elemente gelten als veraltet. Dafür gibt es im Rahmen des neuen Sprachstandards andere Elemente, die statt dessen verwendet werden sollen. Die Verwendung ist aber keinenfalls verboten. Viele der Elemente können durch Attribute erweitert werden. Die Tabelle mit den Attributen finden Sie direkt anschließend. Tabelle B.81: HTML-Tags
1026
Tag
Beschreibung
A
Anker.
ABBR
Abkürzung (z.B., WWW, HTTP, USA, RJS usw.)
ACRONYM
Acronym.
ADDRESS
Adressangabe für Informationen über den Autor.
APPLET
Einbindung eines Java-Applets. Deprecated!
AREA
Clientseitiger Imagemap-Bereich. Kein Ende-Tag vorgesehen.
B
Textstil fett.
BASE
Basis-URL des Dokuments. Kein Ende-Tag vorgesehen.
BASEFONT
Basisschriftgrö ß e. Kein Ende-Tag vorgesehen. Deprecated.
BDO
Deaktivierung der Standardausrichtung des Textes.
BIG
Textstil groß.
BLOCKQUOTE
Zitat (lang).
BODY
Dokumentenkörper.
BR
Zeilenumbruch. Kein Ende-Tag vorgesehen.
BUTTON
Push-Button.
CAPTION
Tabellenbeschriftung.
Java 2 Kompendium
HTML-Elemente und -Attribute
Tag
Beschreibung
CENTER
Zentrieren. Deprecated.
CITE
Zitat.
CODE
Textstil für Computercode.
COL
Tabellenspalte. Kein Ende-Tag vorgesehen.
COLGROUP
Tabellenspaltengruppe. Optionales Ende-Tag.
DD
Definitionsbeschreibung. Optionales Ende-Tag.
DEL
Gelöschter Text.
DFN
Instanzdefinition.
DIR
Verzeichnisliste. Deprecated.
DIV
Allgemeiner Stilcontainer zur Gestaltung (Ausrichtung) von Elementen.
DL
Definitionsliste.
DT
Definitionsterm. Optionales Ende-Tag.
EM
Textstil für eine Betonung.
FIELDSET
Formularkontrollgruppe. Gruppiert gleichartige Steuerelemente.
FONT
Lokale Schriftveränderung. Deprecated.
FORM
Formular.
FRAME
Frame. Kein Ende-Tag vorgesehen.
FRAMESET
Die Oberstruktur einer Framegruppe.
H1
Überschrift der Ordung 1.
H2
Überschrift der Ordung 2.
H3
Überschrift der Ordung 3.
H4
Überschrift der Ordung 4.
H5
Überschrift der Ordung 5.
H6
Überschrift der Ordung 6.
HEAD
Dokumenten-Header (Kopfbereich).
HR
Horizontale Trennlinie. Kein Ende-Tag vorgesehen.
HTML
Äußerer Container einer Webseite.
Java 2 Kompendium
Anhang B Tabelle B.81: HTML-Tags (Forts.)
1027
Anhang B Tabelle B.81: HTML-Tags (Forts.)
1028
Tag
Beschreibung
I
Textstil italic (kursiv).
IFRAME
Innenliegendes Unterfenster.
IMG
Bildreferenz. Kein Ende-Tag vorgesehen.
INPUT
Formulareingabe. Kein Ende-Tag vorgesehen.
INS
Eingefügter Text.
ISINDEX
Einzeiliges Prompt (Eingabeaufforderung). Kein Ende-Tag vorgesehen. Deprecated.
KBD
Stil für Text, der von dem Anwender eingegeben wird.
LABEL
Beschriftungstext für ein Formularfeld.
LEGEND
Fieldset-Legende.
LI
Definition eines Listenelements. Optionales Ende-Tag.
LINK
Ein Medien-unabhängiger Link. Kein Ende-Tag vorgesehen.
MAP
Clientseitige Imagemap.
MENU
Menüliste. Deprecated.
META
Allgemeine Metainformationen. Kein Ende-Tag vorgesehen.
NOFRAMES
Alternativ angezeigter Inhalt, wenn keine Frames angezeigt werden.
NOSCRIPT
Alternativ angezeigter Inhalt, wenn keine Scripts ausgeführt und angezeigt werden.
OBJECT
Allgemeines Einbettungsobjekt.
OL
Sortierte Liste.
OPTGROUP
Optionsgruppe.
OPTION
Selektierbare Auswahl in einem Formular. Optionales Ende-Tag.
P
Absatz. Optionales Ende-Tag.
PARAM
Initialisierungswert für ein Objekt. Kein Ende-Tag vorgesehen.
PRE
Vorformatierter Text.
Q
Kurzes Zitat in einer Zeile.
Java 2 Kompendium
HTML-Elemente und -Attribute
Tag
Beschreibung
S
Textstil durchgestrichen. Deprecated.
SAMP
Textformatierung für die Darstellung von Beispielen.
SCRIPT
Einbindung von Scriptanweisungen.
SELECT
Auswahlmöglichkeiten in einem Formular.
SMALL
Textstil klein.
SPAN
Allgemeiner Sprach- und Stilcontainer.
STRIKE
Textstil durchgestrichen. Deprecated.
STRONG
Textstil starke Hervorhebung.
STYLE
Einbindung von Stilinformationen (Style Sheets).
SUB
Textstil tiefstellen.
SUP
Textstil hochstellen.
TABLE
Tabelle.
TBODY
Tabellenkörper. Nach offizieller W3C-Aussage ist sowohl die Angabe optional als auch das Ende-Tag.
TD
Tabellendatenzelle. Optionales Ende-Tag.
TEXTAREA
Mehrzeiliges Textfeld in einem Formular.
TFOOT
Angabe eines Fußbereichs bei Tabellen. Optionales Ende-Tag.
TH
Tabellenkopfzellen. Optionales Ende-Tag.
THEAD
Angabe eines Kopfbereichs bei Tabellen. Optionales Ende-Tag.
TITLE
Dokumententitel.
TR
Tabellenreihe. Optionales Ende-Tag.
TT
Textstil nichtproportional.
U
Textstil unterstrichen. Deprecated.
UL
Unsortierte Liste.
VAR
Instanz einer Variablen oder eines Programmarguments.
Java 2 Kompendium
Anhang B Tabelle B.81: HTML-Tags (Forts.)
1029
Anhang B
B.4.2
Die HTML 4.0-Attribute
Viele der Tags des HTML 4.0-Sprachstandards können durch Attribute erweitert werden, die sie genauer spezifizieren oder gar erst sinnvoll machen. Die nachfolgende Auflistung gibt alphabetisch sämtliche Attribute, die zum dem HTML 4.0-Sprachstandard gehören. Viele Attribute werden mehrfach angegeben. Dies ist deshalb zwingend, weil sich ein identisch benanntes Attribut verschieden auswirken kann. Zusätzlich werden in den Spalten nach dem Attribut Angaben zum Typ des Attributs, ob das Attribut optional eingesetzt werden kann, ob es bei den zugeordneten Elementen gefordert wird und ob es deprecated ist, gemacht. Auch dies kann sich bei identisch bezeichneten Attributen unterscheiden. Sofern sich Attribute bei verschiedenen HTML-Befehlen gleich auswirken, werden die Elemente zusammengefasst. Die Auflistung enthält die vollständige Liste der Attribute des HTML 4.0Standards und basiert auf der offiziell vom W3C (http://www.w3.org/TR/REChtml40/index/attributes.html) ausgegebenen Liste. Beachten Sie, dass nicht alle der Attribute in den verschiedenen Browserversionen (auch den aktuellen) umgesetzt sind. Dies gilt insbesondere für die Event-Handler, die hauptsächlich der Netscape Navigator nur sehr eingeschränkt unterstützt. Tabelle B.82: HTML-Attribute
Attribut
Beschreibung
abbr
Anwendung bei: TD, TH Typ: %Text; Verwendung: #OPTIONAL Abkürzung für die Kopfzelle einer Tabelle. accept-charset
Anwendung bei: FORM Typ: %Charsets; Verwendung: #OPTIONAL Liste der unterstützten Zeichensätze bei einem Formular.
1030
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
accept
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: INPUT Typ: %ContentTypes; Verwendung: #OPTIONAL Liste der unterstützten MIME-Typen für den DateiUpload. accesskey
Anwendung bei: A, AREA, BUTTON, INPUT, LABEL, LEGEND, TEXTAREA
Typ: %Character; Verwendung: #OPTIONAL Zeichen für den Zugriff auf ein Element (Hotkey). action
Anwendung bei: FORM Typ: %URL; Verwendung: #GEFORDERT Serverseitige Formularbehandlung align
Anwendung bei: CAPTION Typ: %CAlign; Verwendung: #OPTIONAL Deprecated Ausrichtung einer Beschriftung relativ zu einer Tabelle. align
Anwendung bei: APPLET, IFRAME, IMG, INPUT, OBJECT Typ: %IAlign; Verwendung: #OPTIONAL Deprecated Vertikale oder horizontale Ausrichtung der Position eines Objekts in einer Webseite. Java 2 Kompendium
1031
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
align
Anwendung bei: LEGEND Typ: %LAlign; Verwendung: #OPTIONAL Deprecated Relative Angabe zu einem Fieldset. align
Anwendung bei: TABLE Typ: %TAlign; Verwendung: #OPTIONAL Deprecated Position einer Tabelle relativ zum Anzeigefenster im Browser. align
Anwendung bei: HR Typ: (left | center | right) Verwendung: #OPTIONAL Deprecated Ausrichtung der Überschrift. align
Anwendung bei: DIV, H1 – H6, P
Typ: (left | center | right | justify) Verwendung: #OPTIONAL Deprecated Ausrichtung von Elemenen.
1032
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
align
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: COL, COLGROUP, TBODY, TD, TFOOT, TH, THEAD, TR
Typ: (left | center | right | justify | char) Verwendung: #OPTIONAL Ausrichtungsangaben bei einer Tabelle. alink
Anwendung bei: BODY Typ: %Color; Verwendung: #OPTIONAL Deprecated Farbe eines besuchten Links. alt
Anwendung bei: APPLET Typ: %Text; Verwendung: #OPTIONAL Deprecated Alternative Kurzbeschreibung. alt
Anwendung bei: AREA, IMG Typ: %Text; Verwendung: #GEFORDERT Alternative Kurzbeschreibung. alt
Anwendung bei: INPUT Typ: CDATA Verwendung: #OPTIONAL Alternative Kurzbeschreibung.
Java 2 Kompendium
1033
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
archive
Anwendung bei: OBJECT Typ: %URL; Verwendung: #OPTIONAL Durch Leerzeichen getrennte Archivliste. archive
Anwendung bei: APPLET Typ: CDATA Verwendung: #OPTIONAL Deprecated Durch Komma getrennte Archivliste. axis
Anwendung bei: TD, TH Typ: CDATA Verwendung: #OPTIONAL Namengruppen von verbundenen Kopfzeilen. background
Anwendung bei: BODY Typ: %URL; Verwendung: #OPTIONAL Deprecated Hintergrund einer Webseite. bgcolor
Anwendung bei: TABLE Typ: %Color; Verwendung: #OPTIONAL Deprecated Hintergrundfarbe für alle Zellen einer Tabelle.
1034
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
bgcolor
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: TR Typ: %Color; Verwendung: #OPTIONAL Deprecated Hintergrundfarbe für eine Zeile einer Tabelle. bgcolor
Anwendung bei: TD, TH Typ: %Color; Verwendung: #OPTIONAL Deprecated Hintergrundfarbe für genau diese Zelle einer Tabelle. bgcolor
Anwendung bei: BODY Typ: %Color; Verwendung: #OPTIONAL Deprecated Hintergrundfarbe des Dokuments. border
Anwendung bei: IMG, OBJECT Typ: %Length; Verwendung: #OPTIONAL Deprecated Wenn ein Bild oder ein anderes Objekt als ein Link fungiert, die Angabe, ob es umrandet dargestellt wird. border
Anwendung bei: TABLE Typ: %Pixels;
Java 2 Kompendium
1035
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung Verwendung: #OPTIONAL Die Angabe, wie dick der Rahmen um eine Tabelle ist.
cellpadding
Anwendung bei: TABLE Typ: %Length; Verwendung: #OPTIONAL Platzangaben innerhalb der Zellen einer Tabelle. cellspacing
Anwendung bei: TABLE Typ: %Length; Verwendung: #OPTIONAL Raum zwischen Zellen einer Tabelle. char
Anwendung bei: COL, COLGROUP, TBODY, TD, TFOOT, TH, THEAD, TR
Typ: %Character; Verwendung: #OPTIONAL Anordnungszeichen, z.B. char=':'. charoff
Anwendung bei: COL, COLGROUP, TBODY, TD, TFOOT, TH, THEAD, TR
Typ: %Length; Verwendung: #OPTIONAL Offset für das Anordnungszeichen. charset
Anwendung bei: A, LINK, SCRIPT Typ: %Charset; Verwendung: #OPTIONAL Zeichenverschlüsselung der verbundenen Ressource.
1036
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
checked
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: INPUT Typ: (checked) Verwendung: #OPTIONAL Angabe, ob ausgewählt oder nicht bei Radiobutton und Checkboxen innerhalb eines Formulars. cite
Anwendung bei: BLOCKQUOTE, Q Typ: %URL; Verwendung: #OPTIONAL URL für ein Dokument mit Quelltext oder eine Mitteilung. cite
Anwendung bei: DEL, INS Typ: %URL; Verwendung: #OPTIONAL Eine allgemeine Information über den Grund für den Wechsel der letzten Dokumentenversion. class
Anwendung bei allen Tags bis auf: BASE, BASEFONT, HEAD, HTML, META, PARAM, SCRIPT, STYLE, TITLE Typ: CDATA Verwendung: #OPTIONAL Durch Leerraum getrennte Liste von Klassen. classid
Anwendung bei: OBJECT Typ: %URL; Verwendung: #OPTIONAL Identifiziert eine Implementation.
Java 2 Kompendium
1037
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
clear
Anwendung bei: BR Typ: (left | all | right | none) Default: none Deprecated Kontrolle des Textflusses. code
Anwendung bei: APPLET Typ: CDATA Verwendung: #OPTIONAL Deprecated Die Klassendatei eines Applets. codebase
Anwendung bei: OBJECT Typ: %URL; Verwendung: #OPTIONAL Die Angabe, wo sich ein Objekt befindet. codebase
Anwendung bei: APPLET Typ: %URL; Verwendung: #OPTIONAL Deprecated Die Angabe, wo sich ein Applet befindet codetype
Anwendung bei: OBJECT Typ: %ContentType; Verwendung: #OPTIONAL Typ des Codes.
1038
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
color
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: BASEFONT, FONT Typ: %Color; Verwendung: #OPTIONAL Deprecated Textfarbe. cols
Anwendung bei: FRAMESET Typ: %MultiLengths; Verwendung: #OPTIONAL Eine Liste von Längenangaben bei einem Frameset. cols
Anwendung bei: TEXTAREA Typ: NUMBER Verwendung: #GEFORDERT Anzahl von Spalten. colspan
Anwendung bei: TD, TH Typ: NUMBER Verwendung: Angabe grö ß er 0 Anzahl von Spalten, die zu einem Colspan zusammengefasst werden. compact
Anwendung bei: DIR, MENU Typ: (compact) Verwendung: #OPTIONAL Deprecated Reduziert den Platz zwischen Zeichen.
Java 2 Kompendium
1039
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
compact
Anwendung bei: DL, OL, UL Typ: (compact) Verwendung: #OPTIONAL Deprecated Reduziert den Platz zwischen Zeichen. content
Anwendung bei: META Typ: CDATA Verwendung: #GEFORDERT Mit dem <META>-Tag assoziierte Information. coords
Anwendung bei: AREA Typ: %Coords; Verwendung: #OPTIONAL Durch Komma getrennte Liste von Koordinatenangaben. coords
Anwendung bei: A Typ: %Coords; Verwendung: #OPTIONAL Durch Komma getrennte Liste von Koordinatenangaben, die bei clientseitigen Imagemaps verwendet werden. data
Anwendung bei: OBJECT Typ: %URL; Verwendung: #OPTIONAL Referenz auf die Objektdaten.
1040
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
datetime
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: DEL, INS Typ: %Datetime; Verwendung: #OPTIONAL Datum und Uhrzeit einer Veränderung. declare
Anwendung bei: OBJECT Typ: (declare) Verwendung: #OPTIONAL Möglichkeit, ein Objekt zu deklarieren, aber noch nicht zu instanzieren. defer
Anwendung bei: SCRIPT Typ: (defer) Verwendung: #OPTIONAL Verzögern der Ausführung eines Scripts. dir
Anwendung bei allen Tags bis auf: APPLET, BASE, BASEFONT, BDO, BR, FRAME, FRAMESET, HR, IFRAME, PARAM, SCRIPT
Typ: (ltr | rtl) Verwendung: #OPTIONAL Fließrichtung für gewöhnlichen Text (von rechts nach links oder links nach rechts). dir
Anwendung bei: BDO Typ: (ltr | rtl) Verwendung: #GEFORDERT Fließrichtung für gewöhnlichen Text (von rechts nach links oder links nach rechts).
Java 2 Kompendium
1041
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
disabled
Anwendung bei: BUTTON, INPUT, OPTGROUP, OPTION, SELECT, TEXTAREA Typ: (disabled) Verwendung: #OPTIONAL Die Angabe, dass das Element in diesem Kontext nicht aktivierbar ist. enctype
Anwendung bei: FORM Typ: %ContentType; Verwendung: »application/x-www- form-urlencoded" face
Anwendung bei: BASEFONT, FONT Typ: CDATA Verwendung: #OPTIONAL Deprecated Durch Komma getrennte Liste von Schriftnamen. for
Anwendung bei: LABEL Typ: IDREF Verwendung: #OPTIONAL Der ID-Wert eines auszuwählenden Feldes. frame
Anwendung bei: TABLE Typ: %TFrame; Verwendung: #OPTIONAL Angabe, welcher Teil eines Frames ausgegeben werden soll.
1042
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
frameborder
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: FRAME, IFRAME Typ: (1 | 0) Gibt an, ob ein Frame einen Rahmen hat oder nicht. headers
Anwendung bei: TD, TH Typ: IDREFS Verwendung: #OPTIONAL Liste von IDs von Kopfzellen. height
Anwendung bei: IFRAME Typ: %Length; Verwendung: #OPTIONAL Framehöhe. height
Anwendung bei: IMG, OBJECT Typ: %Length; Verwendung: #OPTIONAL Explizite Höhenangabe für ein Bild oder Objekt. Überschreibt die natürliche Höhe. height
Anwendung bei: APPLET Typ: %Length; Verwendung: #GEFORDERT Deprecated Initialisierunghöhe eines Applets.
Java 2 Kompendium
1043
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
height
Anwendung bei: TD, TH Typ: %Pixels; Verwendung: #OPTIONAL Deprecated Höhe einer Zelle. href
Anwendung bei: A, AREA, LINK Typ: %URL; Verwendung: #OPTIONAL URL zu der gelinkten Ressource. href
Anwendung bei: BASE Typ: %URL; Verwendung: #OPTIONAL URL, die als Basis-URL fungiert. hreflang
Anwendung bei: A, LINK Typ: %LanguageCode; Verwendung: #OPTIONAL Sprachcode. hspace
Anwendung bei: APPLET, IMG, OBJECT Typ: %Pixels; Verwendung: #OPTIONAL Deprecated Horizontale Abstandsangabe.
1044
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
http-equiv
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: META Typ: NAME Verwendung: #OPTIONAL HTTP-Antwort auf den den Header-Namen. id
Anwendung bei allen Tags bis auf: BASE, HEAD, HTML, META, SCRIPT, STYLE, TITLE ID
Typ: NAME Verwendung: #OPTIONAL Dokumentenweit eindeutige ID. ismap
Anwendung bei: IMG Typ: (ismap) Verwendung: #OPTIONAL Verwendung des Bildes als serverseitige Imagemap. label
Anwendung bei: OPTION Typ: %Text; Verwendung: #OPTIONAL Eine Beschriftung für die Verwendung in hierarchischen Menüs. label
Anwendung bei: OPTGROUP Typ: %Text; Verwendung: #GEFORDERT Eine Beschriftung für die Verwendung in hierarchischen Menüs.
Java 2 Kompendium
1045
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
lang
Anwendung bei allen Tags bis auf: APPLET, BASE, BASEFONT, BR, FRAME, FRAMESET, HR, IFRAME, PARAM, SCRIPT
Typ: %LanguageCode; Verwendung: #OPTIONAL Typ: Sprachcode. language
Anwendung bei: SCRIPT Typ: CDATA Verwendung: #OPTIONAL Deprecated Name der Scriptsprache. link
Anwendung bei: BODY Typ: %Color; Verwendung: #OPTIONAL Deprecated Farbe der Links. longdesc
Anwendung bei: IMG Typ: %URL; Verwendung: #OPTIONAL Link zu einer langen Beschreibung. longdesc
Anwendung bei: FRAME, IFRAME Typ: %URL; Verwendung: #OPTIONAL Link zu einer langen Beschreibung.
1046
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
marginheight
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: FRAME, IFRAME Typ: %Pixels; Verwendung: #OPTIONAL Randhöhe in Pixel. marginwidth
Anwendung bei: FRAME, IFRAME Typ: %Pixels; Verwendung: #OPTIONAL Randbreite in Pixel. maxlength
Anwendung bei: INPUT Typ: NUMBER Verwendung: #OPTIONAL Maximale Anzahl von Zeichen in einem Textfeld. media
Anwendung bei: STYLE Typ: %MediaDesc; Verwendung: #OPTIONAL Angabe des angegebenen Medientyps. media
Anwendung bei: LINK Typ: %MediaDesc; Verwendung: #OPTIONAL Angabe des angegebenen Medientyps für Rendering auf diesem Media-Element.
Java 2 Kompendium
1047
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
method
Anwendung bei: FORM Typ: (GET | POST) Verwendung: #OPTIONAL Defaulteinstellung: GET HTTP-Methode zur Anwendung bei der Übertragung eines Formulars. multiple
Anwendung bei: SELECT Typ: (multiple) Verwendung: #OPTIONAL Angabe, ob eine Mehrfachauswahl bei einer Selektionsliste in einem Formular erlaubt ist. Defaulteinstellung ist Einfachauswahl. name
Anwendung bei: BUTTON, TEXTAREA Typ: CDATA Verwendung: #OPTIONAL Name eines Elements. Es kann bei einer Zuweisung über diesen Namen von deren Strukturen (etwa Scripten) angesprochen werden. name
Anwendung bei: APPLET Typ: CDATA Verwendung: #OPTIONAL Deprecated Name eines Applets. Applets können sich über den Namen identifizieren.
1048
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
name
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: SELECT Typ: CDATA Verwendung: #OPTIONAL Feldname. name
Anwendung bei: FRAME, IFRAME Typ: CDATA Verwendung: #OPTIONAL Name eines Frames. name
Anwendung bei: A Typ: CDATA Verwendung: #OPTIONAL Benennung eines Links. name
Anwendung bei: INPUT, OBJECT Typ: CDATA Verwendung: #OPTIONAL Name im Rahmen eines Formulars. name
Anwendung bei: MAP Typ: CDATA Verwendung: #GEFORDERT Name zur Referenzierung durch eine Usemap.
Java 2 Kompendium
1049
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
name
Anwendung bei: PARAM Typ: CDATA Verwendung: #GEFORDERT Eigenschaftenname. name
Anwendung bei: META Typ: NAME Verwendung: #OPTIONAL Name einer Meta-Information. nohref
Anwendung bei: AREA Typ: (nohref) Verwendung: #OPTIONAL Angabe, dass innerhalb diese Region keine Aktion stattfindet. noresize
Anwendung bei: FRAME Typ: (noresize) Verwendung: #OPTIONAL Angabe, ob ein Anwender einen Frame in der Grö ß e verändern darf oder nicht. Wenn das Attribut gesetzt ist, ist es nicht erlaubt. noshade
Anwendung bei: HR Typ: (noshade) Verwendung: #OPTIONAL Deprecated Angabe, ob eine Linie mit einem Schaden dargestellt wird oder nicht (Flag verwendet). 1050
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
nowrap
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: TD, TH Typ: (nowrap) Verwendung: #OPTIONAL Deprecated Unterdrückung von Wortummantelung. object
Zugeordnete HTML-Befehle: APPLET Typ: CDATA Verwendung: #OPTIONAL Deprecated Serialisiert ein Applet. onblur
Anwendung bei: A, AREA, BUTTON, INPUT, LABEL, SELECT, TEXTAREA
Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Zuordnung einer Aktion, die stattfindet, wenn das Element den Fokus verloren hat. onchange
Anwendung bei: INPUT, SELECT, TEXTAREA Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Zuordnung einer Aktion, die stattfindet, wenn der Wert des Elements sich ändert.
Java 2 Kompendium
1051
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
onclick
Anwendung bei allen Tags bis auf: APPLET, BASE, BASEFONT, BDO, BR, FONT, FRAME, FRAMESET, HEAD, HTML, IFRAME, ISINDEX,META, PARAM, SCRIPT, STYLE, TITLE
Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Ein Button wurde geklickt. ondblclick
Anwendung bei allen Tags bis auf: APPLET, BASE, BASEFONT, BDO, BR, FONT, FRAME, FRAMESET, HEAD, HTML, IFRAME, ISINDEX, META, PARAM, SCRIPT, STYLE, TITLE Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Ein Button wurde doppelt geklickt. onfocus
Anwendung bei: A, AREA, BUTTON, INPUT, LABEL, SELECT, TEXTAREA
Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Ein Element bekommt den Fokus. onkeydown
Anwendung bei allen Tags bis auf: APPLET, BASE, BASEFONT, BDO, BR, FONT, FRAME, FRAMESET, HEAD, HTML, IFRAME, ISINDEX, META, PARAM, SCRIPT, STYLE, TITLE
Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Eine Taste wurde gedrückt.
1052
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
onkeypress
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei allen HTML-Elementen bis auf: APPLET, BASE, BASEFONT, BDO, BR, FONT, FRAME, FRAMESET, HEAD, HTML, IFRAME, ISINDEX, META, PARAM, SCRIPT, STYLE, TITLE Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Eine Taste wurde gedrückt und wieder losgelassen. onkeyup
Anwendung bei allen HTML-Elementen bis auf: APPLET, BASE, BASEFONT, BDO, BR, FONT, FRAME, FRAMESET, HEAD, HTML, IFRAME, ISINDEX, META, PARAM, SCRIPT, STYLE, TITLE Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Eine Taste wurde losgelassen. onload
Anwendung bei: FRAMESET Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Alle Frames wurden geladen. onload
Anwendung bei: BODY Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Das Dokument wurde geladen.
Java 2 Kompendium
1053
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
onmousedown
Anwendung bei allen HTML-Elementen bis auf: APPLET, BASE, BASEFONT, BDO, BR, FONT, FRAME, FRAMESET, HEAD, HTML, IFRAME, ISINDEX, META, PARAM, SCRIPT, STYLE, TITLE
Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Ein Button wurde gedrückt. onmousemove
Anwendung bei allen HTML-Elementen bis auf: APPLET, BASE, BASEFONT, BDO, BR, FONT, FRAME, FRAMESET, HEAD, HTML, IFRAME, ISINDEX, META, PARAM, SCRIPT, STYLE, TITLE
Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Ein Mauszeiger wurde über einem Element bewegt. onmouseout
Anwendung bei allen HTML-Elementen bis auf: APPLET, BASE, BASEFONT, BDO, BR, FONT, FRAME, FRAMESET, HEAD, HTML, IFRAME, ISINDEX, META, PARAM, SCRIPT, STYLE, TITLE
Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Ein Mauszeiger hat den Bereich eines Elements verlassen. onmouseover
Anwendung bei allen HTML-Elementen bis auf: APPLET, BASE, BASEFONT, BDO, BR, FONT, FRAME, FRAMESET, HEAD, HTML, IFRAME, ISINDEX, META, PARAM, SCRIPT, STYLE, TITLE
Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Ein Mauszeiger wurde in den Bereich eines Elements hineinbewegt. 1054
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
onmouseup
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei allen HTML-Elementen bis auf: APPLET, BASE, BASEFONT, BDO, BR, FONT, FRAME, FRAMESET, HEAD, HTML, IFRAME, ISINDEX, META, PARAM, SCRIPT, STYLE, TITLE Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Ein Mauszeiger wurde losgelassen. onreset
Anwendung bei: FORM Typ: %Script; Verwendung: #OPTIONAL Event-Handler: Das Formular wurde über den RESETButton gelöscht. onselect
Anwendung bei: INPUT, TEXTAREA Typ: %Script; Verwendung: #OPTIONAL Event-Handler: Text wurde selektiert. onsubmit
Anwendung bei: FORM Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Das Formular wurde gesendet. onunload
Anwendung bei: FRAMESET Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Alle Frames wurden wieder entfernt.
Java 2 Kompendium
1055
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
onunload
Anwendung bei: BODY Typ: %Script; Verwendung: #OPTIONAL Event-Handler. Das Dokument wurde wieder entfernt. profile
Anwendung bei: HEAD Typ: %URL; Verwendung: #OPTIONAL Benennung des Verzeichnisses für eine Meta-Information. prompt
Anwendung bei: ISINDEX Typ: %Text; Verwendung: #OPTIONAL Deprecated Die Prompt-Anzeige. readonly
Anwendung bei: TEXTAREA Typ: (readonly) Verwendung: #OPTIONAL Nur Leseerlaubnis. readonly
Anwendung bei: INPUT Typ: (readonly) Verwendung: #OPTIONAL Nur Leseerlaubnis.
1056
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
rel
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: A, LINK Typ: %LinkTypes; Verwendung: #OPTIONAL Vorwärts-Linkangaben. rev
Anwendung bei: A, LINK Typ: %LinkTypes; Verwendung: #OPTIONAL Rückwärts-Linkangaben. rows
Anwendung bei: FRAMESET Typ: %MultiLengths; Verwendung: #OPTIONAL Liste von Längenangaben (Anzahl Reihen). rows
Anwendung bei: TEXTAREA Typ: NUMBER Verwendung: #GEFORDERT Anzahl der Reihen. rowspan
Anwendung bei: TD, TH Typ: NUMBER Verwendung: Angabe grö ß er 0 Anzahl der Reihen, die von einer Zelle überspannt werden sollen.
Java 2 Kompendium
1057
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
rules
Anwendung bei: TABLE Typ: %TRules; Verwendung: #OPTIONAL Verhaltensregeln zwischen Zeilen und Spalten in einer Tabelle. scheme
Anwendung bei: META Typ: CDATA Verwendung: #OPTIONAL Selektiert einen Inhalt. scope
Anwendung bei: TD, TH Typ: %Scope; Verwendung: #OPTIONAL Wiederholungsbereich der Kopfzellen. scrolling
Anwendung bei: FRAME, IFRAME Typ: (yes | no | auto) Defaulteinstellung: auto Scrollbalken oder nicht. selected
Anwendung bei: OPTION Typ: (selected) Verwendung: #OPTIONAL Selektiert.
1058
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
shape
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: AREA Typ: %Shape; Defaulteinstellung: rect Koordinatenangaben. shape
Anwendung bei: A Typ: %Shape; Defaulteinstellung: rect Koordinatenangaben in einer clientseitigen Imagemap. size
Anwendung bei: HR Typ: %Pixels; Verwendung: #OPTIONAL Deprecated Grö ß enangabe einer Trennlinie. size
Anwendung bei: FONT Typ: CDATA Verwendung: #OPTIONAL Deprecated Grö ß enangabe für eine Schrift. Immer relative Angabe zur Basisschrift. Entweder relativ mit Plus oder Minus von der Basisschriftgrö ß e [+|-]nn, z.B. size="+3", oder genaue Relativangabe zwischen 1 und 7, z.B. size="6"
Java 2 Kompendium
1059
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
size
Anwendung bei: INPUT Typ: CDATA Verwendung: #OPTIONAL Spezifiziert jeden Typ eines Feldes bezüglich seiner Grö ß e. size
Anwendung bei: BASEFONT Typ: CDATA Verwendung: #GEFORDERT Deprecated Basisschriftgrö ß e für jedes Schriftelement. size
Anwendung bei: SELECT Typ: NUMBER Verwendung: #OPTIONAL Sichtbare Einträge in einer Auswahlliste. span
Anwendung bei: COL Typ: NUMBER Verwendung: Angabe grö ß er 0 Die COL-Attribute sollen sich auf die angegebene Anzahl von Spalten auswirken. span
Anwendung bei: COLGROUP Typ: NUMBER Verwendung: Angabe grö ß er 0 Defaultanzahl von Spalten in einer Colgroup.
1060
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
src
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: SCRIPT Typ: %URL; Verwendung: #OPTIONAL URL für ein externes Script. src
Anwendung bei: INPUT Typ: %URL; Verwendung: #OPTIONAL URL für Felder mit Bildern. src
Anwendung bei: FRAME, IFRAME Typ: %URL; Verwendung: #OPTIONAL Source des Inhaltes eines Frames. src
Anwendung bei: IMG Typ: %URL; Verwendung: #GEFORDERT URL des Bildes, das eingebettet werden soll. standby
Anwendung bei: OBJECT Typ: %Text; Verwendung: #OPTIONAL Die Nachricht, die während des Ladevorgangs eines Objekts angezeigt werden soll.
Java 2 Kompendium
1061
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
start
Anwendung bei: OL Typ: NUMBER Verwendung: #OPTIONAL Deprecated Startnummer einer Aufzählung. style
Anwendung bei allen HTML-Elementen bis auf: BASE, BASEFONT, HEAD, HTML, META, PARAM, SCRIPT, STYLE, TITLE
Typ: %StyleSheet; Verwendung: #OPTIONAL Assoziierte Stilinformation. summary
Anwendung bei: TABLE Typ: %Text; Verwendung: #OPTIONAL Strukturangaben für die Sprachausgabe. tabindex
Anwendung bei: A, AREA, BUTTON, INPUT, OBJECT, SELECT, TEXTAREA Typ: NUMBER Verwendung: #OPTIONAL Der Tabulatorindex. target
Anwendung bei: A, AREA, BASE, FORM, LINK Typ: %FrameTarget; Verwendung: #OPTIONAL Die Angabe, wo in dem Frame eine Anzeige erfolgen soll.
1062
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
text
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: BODY Typ: %Color; Verwendung: #OPTIONAL Deprecated Globale Textfarbe im Dokument. title
Anwendung bei: STYLE Typ: %Text; Verwendung: #OPTIONAL Erklärender Titel. Es sollte nicht mit dem TITLE-Element verwechselt werden, das Bestandteil des Header-Teils einer Webseite ist. title
Anwendung bei allen HTML-Elementen bis auf: BASE, BASEFONT, HEAD, HTML, META, PARAM, SCRIPT, STYLE, TITLE Typ: %Text; Verwendung: #OPTIONAL Erklärender Titel für das Element, auf das das Attribut angewandt wird (Quickinfo). Es sollte nicht mit dem TITLE-Element verwechselt werden, das Bestandteil des Header-Teils einer Webseite ist. type
Anwendung bei: A, LINK Typ: %ContentType; Verwendung: #OPTIONAL Erklärender Inhaltstyp.
Java 2 Kompendium
1063
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
type
Anwendung bei: OBJECT Typ: %ContentType; Verwendung: #OPTIONAL Inhaltstyp der Daten. type
Anwendung bei: PARAM Typ: %ContentType; Verwendung: #OPTIONAL Inhaltstyp für Werte, wenn valuetype=ref gilt. type
Anwendung bei: SCRIPT Typ: %ContentType; Verwendung: #GEFORDERT Typ des Inhalts der Script-Sprache. type
Anwendung bei: STYLE Typ: %ContentType; Verwendung: #GEFORDERT Typ des Inhalts der Style-Sprache. type
Anwendung bei: INPUT Typ: %InputType; Default: TEXT Art der Eingabe, die gefordert wird.
1064
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
type
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: LI Typ: %LIStyle; Verwendung: #OPTIONAL Deprecated Stil der Aufzählungsliste. type
Anwendung bei: OL Typ: %OLStyle; Verwendung: #OPTIONAL Deprecated Stil der Nummerierung. type
Anwendung bei: UL Typ: %ULStyle; Verwendung: #OPTIONAL Deprecated Bullet-Stil. type
Anwendung bei: BUTTON Typ: (button | submit | reset) Default: submit Typ der Schaltfläche bei der Verwendung als FormularButton. usemap
Anwendung bei: IMG, INPUT, OBJECT Typ: %URL; Verwendung: #OPTIONAL Verwendung als clientseitige Imagemap. Java 2 Kompendium
1065
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
valign
Anwendung bei: COL, COLGROUP, TBODY, TD, TFOOT, TH, THEAD, TR
Typ: (top | middle | bottom | baseline) Verwendung: #OPTIONAL Vertikale Ausrichtug in Zellen. value
Anwendung bei: OPTION Typ: CDATA Verwendung: #OPTIONAL Defaultwert eines Elementinhalts. value
Anwendung bei: PARAM Typ: CDATA Verwendung: #OPTIONAL Eigenschaftenwert. value
Anwendung bei: INPUT Typ: CDATA Verwendung: #OPTIONAL Wert bei Radioboxen und Checkboxen. value
Anwendung bei: BUTTON Typ: CDATA Verwendung: #OPTIONAL Wird bei der Übermittlung zum Server gesendet.
1066
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
value
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: LI Typ: NUMBER Verwendung: #OPTIONAL Deprecated Setzt eine Aufzählungsnummer. valuetype
Anwendung bei: PARAM Typ: (DATA | REF | OBJECT) Default: DATA Beschreibung, wie ein Wert zu interpretieren ist. version
Anwendung bei: HTML Typ: CDATA Typ: %HTML.Version; Deprecated Verwendung: Konstante. vlink
Anwendung bei: BODY Typ: %Color; Verwendung: #OPTIONAL Deprecated Farbe von bereits besuchten Links. vspace
Anwendung bei: APPLET, IMG, OBJECT Typ: %Pixels; Verwendung: #OPTIONAL Deprecated Vertikale Abstandsangabe. Java 2 Kompendium
1067
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Attribut
Beschreibung
width
Anwendung bei: HR Typ: %Length; Verwendung: #OPTIONAL Deprecated Breite der Trennlinie. width
Anwendung bei: IFRAME Typ: %Length; Verwendung: #OPTIONAL Breite des Frames. width
Anwendung bei: IMG, OBJECT Typ: %Length; Verwendung: #OPTIONAL Breitenangabe. width
Anwendung bei: TABLE Typ: %Length; Verwendung: #OPTIONAL Breite der Tabelle. width
Anwendung bei: APPLET Typ: %Length; Verwendung: #GEFORDERT Deprecated Initialisierungsbreite eines Applets.
1068
Java 2 Kompendium
HTML-Elemente und -Attribute
Attribut
Beschreibung
width
Anhang B Tabelle B.82: HTML-Attribute (Forts.)
Anwendung bei: COL Typ: %MultiLength; Verwendung: #OPTIONAL Spaltenbreite. width
Anwendung bei: COLGROUP Typ: %MultiLength; Verwendung: #OPTIONAL Defaultbreite für eingeschlossene Spalten. width
Anwendung bei: TD, TH Typ: %Pixels; Verwendung: #OPTIONAL Deprecated Breite für Zellen. width
Anwendung bei: PRE Typ: NUMBER Verwendung: #OPTIONAL Deprecated Breitenangaben.
Java 2 Kompendium
1069
Anhang B
B.5
JavaScript- Token
JavaScript ist in der Version 1.3 mit der ECMA-262-Norm kompatibel. Dies umfasst folgende Token: Tabelle B.83: JavaScript-Token
1070
Kategorie
Token
Datenfeldverarbeitung
Array, join, length, reverse, sort
Zuweisungen
Zuweisungsoperatoren und Verbundzuweisungsoperatoren
Boolesche Werte
Boolean
Kommentare
/*...*/ oder //
Konstanten/Literale
NaN, null, true, false, +Infinity, undefined
Ablaufsteuerung
break, continue, for, for...in, if...else, return, while
Java 2 Kompendium
JavaScript- Token
Anhang B
Kategorie
Token
Datum und Zeit
Date, getDate, getDay, getFullYear, getHours, getMilliseconds, getMinutes, getMonth, getSeconds, getTime, getTimezoneOffset, getYear,, getUTCDate, getUTCDay, getUTCFullYear, getUTCHours, getUTCMilliseconds, getUTCMinutes, getUTCMonth, getUTCSeconds,, setDate, setFullYear, setHours, setMilliseconds, setMinutes, setMonth, setSeconds, setTime, setYear,, setUTCDate, setUTCFullYear, setUTCHours, setUTCMilliseconds, setUTCMinutes, setUTCMonth, setUTCSeconds,, toGMTString, toLocaleString, toUTCString, parse, UTC
Deklarationen
function, new, this, var, with
Java 2 Kompendium
Tabelle B.83: JavaScript-Token (Forts.)
1071
Anhang B Tabelle B.83: JavaScript-Token (Forts.)
1072
Kategorie
Token
Funktionserstellung
Function, arguments, length
Globale Funktionen
Global, escape, unescape, eval, isFinite, isNaN, parseInt, parseFloat
Math
Math, abs, acos, asin, atan, atan2, ceil, cos, exp, floor, log, max, min, pow, random, round, sin, sqrt, tan, E, LN2, LN10, LOG2E, LOG10E, PI, SQRT1_2, SQRT2
Zahlen
Number, MAX_VALUE, MIN_VALUE, NaN, NEGATIVE_INFINITY, POSITIVE_INFINITY
Java 2 Kompendium
JavaScript- Token
Anhang B
Kategorie
Token
Objekterstellung
Object, new, constructor, prototype, toString, valueOf
Operatoren
Addition +, Subtraktion –, Modulo %, Multiplikation *, Division /, Unäre Negation –, Gleichheit ==, Ungleichheit !=, Kleiner als <, Kleiner gleich <=, Grö ß er als >, Grö ß er gleich >=, Logisches AND &&, Logisches OR ||, Logisches NOT !, Bitweises AND &, Bitweises OR |, Bitweises NOT ~, Bitweises XOR ^, Bitweises Left Shift <<, Bitweises Right Shift >>, Vorzeichenloses Right Shift >>>, trinäre Bedingung ?:, Komma ,, delete, typeof, void, Dekrement --, Inkrement ++
Java 2 Kompendium
Tabelle B.83: JavaScript-Token (Forts.)
1073
Anhang B Tabelle B.83: JavaScript-Token (Forts.)
1074
Kategorie
Token
Objekte
Array, Boolean, Date, Function, Global, Math, Number, Object, String
Zeichenfolgen
String, charAt, charCodeAt, fromCharCode, indexOf, lastIndexOf, split, toLowerCase, toUpperCase, length
Java 2 Kompendium
C
Anhang C
In diesem Anhang werden wir uns mit ein paar Hintergrundtheorien beschäftigen, die von Java in verschiedenster Weise genutzt werden. Außerdem finden Sie einige Java-Quellen.
C.1
JAR-Archive oder wie arbeiten Komprimierungstechnologien?
Ab der Version 1.1.1 wurde in das JDK und Java die Möglichkeit integriert, so genannte JAR-Files (Java-Archive mit der Erweiterung .jar) zu nutzen. Damit wird die Datenmenge ziemlich komprimiert. In folgendem kleinen Exkurs wollen wir und ein bisschen mit der Theorie der Datenkomprimierung beschäftigen. Vielleicht wollen Sie ja mal selbst ein Komprimierungsprogramm in Java schreiben. Java bietet durch seine an C/C++ angelegte Programmierungsmöglichkeiten auf Bit-Ebene durchaus die Voraussetzung dafür. Programmiersprachen, die diese Programmierungsmöglichkeiten auf Bit-Ebene nicht so direkt zur Verfügung stellen (z.B. PASCAL) erschweren die Erstellung eines Komprimierungsprogramms wiederum.
C.1.1
Komprimierungsverfahren und Programme
Nahezu jedem Computer-Anwender sind schon verschiedene Komprimierungsprogramme über den Weg gelaufen. Insbesondere Java-Anwendern, denn das JDK wird wie nahezu jedes Softwareprodukt komprimiert verteilt. Im Internet ist es sowieso nur noch möglich, die Daten komprimiert zu verschicken, denn die Datentransferraten sind immer niedrig. JAR-Archive sind daher ein weiterer großer Schritt in Richtung Akzeptanz von Java-Technologie im Internet. Wir werden uns bei den zu komprimierenden Daten auf Zeichendarstellungen in einem Byte beschränken, was jedoch keine Beschränkung der Allgemeinheit darstellt. Zuerst stellt sich die Frage, was Komprimierung von Daten heißt? Es bedeutet, Dateien oder Programme zu verkleinern, ohne Informationen zu verlieren. Eine Information, die in der Ursprungsdatei vorhanden ist, muss auch
Java 2 Kompendium
1075
Anhang C in der verkleinerten Datei wiederzufinden sein. Zusätzlich sollte die verkleinerte Datei auch alle Informationen enthalten, um die Ursprungsdatei (bzw. die vollständige Ursprungsinformation) wieder vollständig herzustellen. Die Anzahl der Bytes (Bits), die zur Darstellung von Daten benötigt wird, soll verringert werden. Intuitiv ist klar, dass man eine Datei nur bis zu einer gewissen Grenze verkleinern kann, ohne Informationen zu verlieren (wenn es keine Grenze gäbe, könnte man eine Datei immer wieder komprimieren, hätte zum Schluss noch ein Bit übrig und trotzdem noch alle Informationen). Neben der Verkleinerung der Datenmenge ist die Datensicherheit ein Grund für Komprimierung, denn komprimierte Daten lassen sich nicht so ohne Weiteres entschlüsseln – gerade in der Datenfernübertragung. Noch besser sind natürlich spezielle Kodierungsprogramme, die in ihrer Arbeitsweise Komprimierungsprogrammen aber durchaus ähnlich sein können (zumindest einige der Kodierungstechniken). Einen wesentlichen Nachteil erkauft man sich mit Datenkomprimierung. Entweder man arbeitet mit so genannten Online-Komprimierern, die prinzipiell während des Lesen und Schreibens von Daten komprimieren bzw. entkomprimieren. Dadurch hat man deutlich längere Lese-/Schreibzugriffe auf die Datenträger. Oder man nimmt Packer, die manuell aufgerufen werden und dann die angegebenen Daten komprimieren/dekomprimieren, unabhängig von den normalen Lese-/Schreibzugriffen. Eine dritte Variante sind die so genannten EXE-Komprimierer, die direkt in den Hauptspeicher entpacken, wo das Programm dann sofort gestartet wird. In jedem Fall bedeutet die Verwendung von Packern Zeitaufwand. Das gleichzeitige Verwenden von verschiedenen Komprimierungsvorgängen macht meist keinen Sinn, da bereits einmal komprimierte Daten in der Regel nicht weiter komprimiert werden. Desgleichen macht die nacheinander folgende Anwendung von verschiedenen Komprimierungsverfahren wenig Sinn. Im Gegenteil – es liegt in der Natur der Sache (wir werden es gleich sehen), dass eine mehrfache Verwendung von Komprimierungsprogrammen meist zu einer erheblichen Vergrö ß erung der Datenmenge führt. Welche verschiedenen Methoden zum Komprimieren von Daten gibt es nun? Die Art der Datenkompression ist stark abhängig von der zu verarbeitenden Datei. Nicht jede Komprimierungsart hat bei jedem Typ von Datei den gleichen Erfolg. Bei Dateien, wo sich bestimmte Informationen oft wiederholen, lassen sich recht schnell ordentliche Komprimierungsraten (Komprimierungsrate in % = Grö ß e der Ursprungsdatei / Grö ß e der komprimierten Datei, also je kleiner, desto besser) erzielen, indem man nur angibt, wie oft sich ein Zeichen wiederholt.
1076
Java 2 Kompendium
JAR-Archive oder wie arbeiten Komprimierungstechnologien?
Anhang C
Beispiel: Eine Datei, die wie folgt aussieht XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
benötigt 40 Byte (sie besteht aus 40 mal dem Zeichen X). Die gleiche Information lässt sich auch in X40 verschlüsseln. Diese Datei wäre nur 3 Byte groß. Das Komprimierungsprogramm müsste in der zu verkleinernden Datei nur die Zahl der sich wiederholenden Zeichen ermitteln und in die Zieldatei diese Zahl und einmal das gefundene Zeichen schreiben. Die Entkomprimierung läuft genau umgekehrt. Sobald das Entkomprimierungsmodul eine Zahl findet, schreibt es das vorherige Zeichen in dieser Anzahl in die Zieldatei. Ein entsprechender Algorithmus muss nur in das Komprimierungsprogramm integriert werden. Dieses Verfahren hat zwei ganz offensichtliche Schwachstellen. Die zu komprimierende Datei darf keine Zahlen enthalten – wie sollte sonst das Entkomprimierungsmodul unterscheiden können, ob die gefundene Zahl die Mengenangabe oder das Zeichen ist. Außerdem wird z.B. kaum eine Textdatei so viele Buchstabenwiederholungen haben, dass dieses Verfahren Erfolg hat – erst ab drei gleichen Buchstaben hintereinander würde die Datei kleiner. Daran sieht man bereits, dass dieses Verfahren i.a. nicht geeignet ist, um beliebige Dateien zu verkleinern. Es hat trotzdem eine große Bedeutung. Grafikformate – z.B. PCX vom Windows-Programm Paintbrush – verwenden ein solches Verfahren, sind also bereits gepackt. Es wird eine gute Komprimierungsrate erzielt, wenn große einfarbige Flächen zu speichern sind. Für reine Textdateien lässt sich auch ein funktionierendes Komprimierungsmodell entwerfen. Bekannterweise können auf dem PC alle verfügbaren Zeichen im ASCII-Code gespeichert werden. Der Unicode von Java kann selbstverständlich genauso als Grundlage dienen (alles mal 2). Wir bleiben aber wie schon angedeutet ohne Beschränkung der Allgemeinheit beim ASCII-Code. Ein Zeichen im ASCII-Code hat genau die Länge von 1 Byte, d.h. 8 Bit. Damit lassen sich im Dualsystem 256 (2 hoch 8) Zeichen darstellen, eben den Umfang der ASCII-Tabelle. Wenn nun maximal 128 (2 hoch 7) Zeichen gespeichert werden sollen, kommt man im Prinzip auch mit 7 Bit aus. Das achte Bit ist bei einer geeigneten Kodierung (d.h. Umsortierung oder Verschiebung) immer 0. Damit könnte man alle reinen Textzeichen (mit Umlauten, Punkt, Komma usw.) zuordnen. Eine reine Textdatei lässt sich mit einem solchen Programm schnell und einfach auf 87,5 % der Ursprungsgrö ß e reduzieren. Aber wie gesagt, dieses Verfahren macht nur für Dateien Sinn, die maximal 128 verschiedene Zeichen beinhalten. In der Praxis wird dieses Verfahren so gut wie nicht verwendet. Allerdings steckt in diesem Modell bereits ein wichtiger Hinweis auf einen wesentlichen Bestandteil von allgemeinen Komprimierungsprogrammen. Zeichen müssen
Java 2 Kompendium
1077
Anhang C nicht unbedingt in einem Byte (oder 2 Byte beim Unicode) gespeichert werden, sondern können durchaus in einer geringeren Zahl von Bits verschlüsselt werden – natürlich auch in einer grö ß eren Zahl von Bits. Eine erste Idee könnte sein, bei einem Zeichen, das eine gewisse Zahl von führenden Nullen in seinem Code hat, nur ab dem ersten von Null verschiedenen Bit zu speichern. Wenn z.B. das Zeichen A den ASCII-Code 65 hat, wird das binär als 1000001 dargestellt. Also in 7 Bit. Das Zeichen B mit dem ASCII-Code 66 ist binär 1000010. Wenn man sie nun ohne die führenden Nullen hintereinander wegschreibt, erhält man eine reduzierte Grö ß e der Datei. Das Problem ist aber, dass man nicht weiß, wie viel Bit beim Entkomprimieren einzulesen sind, um den vollständigen Code eines Zeichens zu erhalten. Die kodierte Darstellung ist bei diesem Verfahren nicht eindeutig. Beispiel: 100101 kann ein Zeichen mit dem ASCII-Code 37, aber auch zwei verschiedene Zeichen mit den ASCII-Codes 4 (100) und 5 (101) bedeuten. Eine Darstellung in der Form 100 101 wäre eindeutig, da hier das Leerzeichen als Begrenzer für ein Zeichen dient. Da aber jedes in Frage kommende Zeichen in einer beliebigen Datei auch vorkommen kann und natürlich auch kodiert werden müsste (auch das Leerzeichen, das in einer Binärdatei natürlich nichts zu suchen hat!), hat man wieder ein ähnliches Problem wie bei der Verschlüsselung von Grafikdateien. Es darf mindestens ein Zeichen – eben der Begrenzer einer Zeichendarstellung – nicht in der Quelldatei vorkommen. Begrenzer eignen sich also nicht, um bei verschieden langer Darstellung von Zeichen die einzelnen Zeichen voneinander zu trennen. Welche Lösung gibt es nun für dieses Problem? Es werden genau dann keine Begrenzer benötigt, wenn kein Zeichencode mit dem Anfang eines anderen übereinstimmt, wenn also nach dem Einlesen von jedem Bit klar ist, ob es noch zur Bitfolge des bis dahin gelesenen Zeichens gehört oder bereits der Anfang eines neuen Zeichens ist. Das allgemeine Verfahren, um einen solchen Code zu bestimmen, wurde 1952 von D. Huffman entdeckt und wird nach ihm die Huffman-Kodierung genannt Die meisten gängigen Komprimierungsverfahren arbeiten mit diesem (meist leicht modifizierten) Huffman-Algorithmus.
C.1.2
Der Huffman-Algorithmus
Bei der Erzeugung des Huffman-Codes hat man im Wesentlichen zwei Probleme zu lösen. Zum einen will man einen kürzeren Code erzeugen und zum anderen soll die Darstellung von Zeichen in dem neuen Code eindeutig sein. Klar ist, dass eine Datei, die nur aus tausend mal dem Buchstaben A besteht, auch in der normalen Form 1000 Byte groß ist. Wenn man nun irgendwo definiert, dass der Buchstabe A binär durch 0 dargestellt werden soll, kann diese Datei in 1000 Bit – tausend mal Null – dargestellt werden. Dies ist viel kleiner und eindeutig, solange bekannt ist, dass eine 0 das Zeichen A bedeu1078
Java 2 Kompendium
JAR-Archive oder wie arbeiten Komprimierungstechnologien?
Anhang C
tet. Komme nun noch der Buchstabe B genau einmal in dieser Datei vor. Die Datei wäre 1001 Byte groß in der normalen Darstellung. Das Zeichen B sei binär in der Form 1111111111111111 verschlüsselt, also in einer Zeichenkette von 2 Byte. Die kodierte Darstellung ist damit 1016 Bit groß (1000 x 1 Bit + 1 x 16 Bit), also immer noch bedeutend kleiner und eindeutig. Solange man beim Einlesen der kodierten Datei eine 0 findet, hat man einmal das Zeichen A; sobald eine 1 auftritt, muss man bis zur nächsten 0 lesen. Nachdem 16 mal eine 1 vorkam, ist eindeutig dieser Bitfolge das Zeichen B zugeordnet. Aber wer sagt denn, dass das Zeichen A mit Null und das Zeichen B in der Form 1111111111111111 zu verschlüsseln ist? Man kann ja auch auf die Idee kommen, genau umgekehrt zu verfahren. Oder man hat eine Datei, in der das Zeichen A nur einmal und das Zeichen B 1000-mal vorkommt. Damit wäre die verschlüsselte Datei immer noch eindeutig, aber auf Grund der unglücklichen Wahl des Verschlüsselungsmechanismus ungefähr doppelt so groß wie die Ursprungsdatei (1000 x 16 Bit + 1 x 1 Bit). Es erscheint also äußert sinnvoll, häufig vorkommende Zeichen in einem kürzeren und selten vorkommende Zeichen in einem längeren Code zu verschlüsseln, unter Umständen auch in einem Code, der mehr als ein Byte groß ist, solange das zugehörige Zeichen nur »selten genug« vorkommt. Genau dieses ist der erste Schritt beim Erstellen des Huffman-Codes. Es ist in der zu verschlüsselnden Datei die Häufigkeit jedes Zeichens zu ermitteln. Diese bilden dann eine Häufigkeitsmenge aus lauter so genannten Knoten. Der nächste Schritt besteht darin, dem am häufigsten vorkommenden Zeichen den kürztest möglichen Code zuzuordnen, dem seltensten Zeichen den optimalen längsten Code. Zeichen, die überhaupt nicht in der Ursprungsdatei vorkommen, werden nicht verschlüsselt. Wie wird nun ein optimaler und eindeutiger Code ermittelt? Zunächst werden die zwei Zeichen mit den geringsten (von Null verschiedenen) Häufigkeiten genommen (falls mehr als zwei Zeichen die gleiche Häufigkeit haben, kann man sich beliebig entscheiden). Diese beiden bilden einen neuen Knoten, dessen Wert die Summe der Häufigkeiten der beiden erzeugenden Zeichen ist. Danach werden die beiden erzeugenden Knoten gestrichen und der neu erzeugte Knoten in die verbleibende Häufigkeitsmenge – oder auch Menge von Bäumen – aufgenommen. Die Zahl der Bäume hat sich um 1 reduziert. In der neuen Häufigkeitsmenge sucht man erneut die beiden Knoten mit der geringsten Häufigkeit und fährt wie oben fort. So wird Schritt für Schritt die Anzahl der Bäume reduziert (zwei entfernen und einen hinzufügen). Zum Schluss sind alle Knoten zu einem einzigen Baum verbunden. Nunmehr kann der Huffman-Code abgeleitet werden, indem man die Häufigkeiten an den untersten Knoten des Baumes durch das jeweilige zugehörige Zeichen ersetzt. Wenn man noch vereinbart, dass links in dem binären Baum dem Bit 0 und rechts dem Bit 1 entspricht, kann man jedes vorkommende Zeichen über eine entsprechende
Java 2 Kompendium
1079
Anhang C Folge darstellen. Diese Kodierung ist eindeutig und es lässt sich mit dem mathematischen Mittel der Induktion beweisen, dass keine Kodierungsstrategie zu einem besseren Ergebnis führen kann als die oben beschriebene Methode. Der Huffman-Code ist i.a. der bestmögliche Code. Jetzt muss nur noch jedes in der Ursprungsdatei vorkommende Zeichen durch seinen neuen Code ersetzt werden. Zusätzlich muss noch irgendwo in der komprimierten Datei (im Header) für eine spätere Entkomprimierung hinterlegt werden, welcher Code welchem Zeichen entspricht. Meist werden dort noch diverse weitere statistische und technische Parameter gespeichert. Diese Maßnahme vergrö ß ert die Datei wieder geringfügig und macht auch deutlich, dass eine Komprimierung von Dateien nur dann Sinn macht, wenn der Headerteil der komprimierten Datei nicht grö ß er ist als der Komprimierungsfaktor. Daher würde z.B. ein erneutes Komprimieren eines bereits komprimierten Codes in der Regel keine Vorteile bringen, sondern den Code eher noch vergrö ß ern (der neue Header muss ja noch zusätzlich gespeichert werden und der Code war ja in der Regel schon optimal). Für Java hat dies einige unangenehme Konsequenzen. In diesen JAR-Archiven können neben .class-Dateien auch Image- und Sound-Dateien verpackt werden. Für .class-Dateien lassen sich sehr gute Komprimierungsraten erzielen, aber Image- und Sound-Dateien sind normalerweise schon hochkomprimiert. Ein Verpacken von vielen Image- und Sound-Dateien in JAR-Dateien kann also sogar dazu führen, dass diese sogar grö ß er ist als die Summe der einzelnen Dateien. In diesem spezielen Fall bleibt nur der Vorteil, dass die Applikation in einer einzigen HTTP-Transaktion übertragen wird und nicht in vielen einzelnen Übertragungsschritten. Statt vieler einzelner und unkomprimierter Dateien wird beim Laden aus dem Netz ein gepacktes Archiv übertragen. Wir wollen an einem Beispiel den Huffman-Algorithmus durchsprechen. Unser zu komprimierender Beispieltext lautet wie folgt: EXTREMER ERNST
Es sind 13 Zeichen (lassen wir einmal das Leerzeichen außer Acht) und damit wäre der Text 13 Byte groß. Nach Durchzählen der Zeichen ergibt sich, dass E viermal, R dreimal, T zweimal und alle anderen Zeichen einmal vorkommen. Dies kann beispielsweise eine einfache Java-Routine leicht lösen. Verfolgen wir auf unser Beispiel bezogen unsere einzelnen Schritte zur Erstellung eines Huffman-Codes. Die häufig vorkommenden Zeichen sollen den kürzesten Code, die seltener vorkommenden den längeren Code bekom-
1080
Java 2 Kompendium
JAR-Archive oder wie arbeiten Komprimierungstechnologien?
Anhang C
men. Der Code muss zudem eindeutig sein, denn nur auf Grund einer Eindeutigkeit lässt er sich wieder eindeutig entkomprimieren. Führen wir also diesen Schritt beim Huffman-Verfahren in unserem Beispiel durch. Die Anzahl der jeweiligen Zeichen (die Häufigkeitsmenge) – ist in unserem Beispiel (4, 3, 2, 1, 1, 1, 1). Wir versuchen also, dem am häufigsten vorkommenden Zeichen (E, da es viermal vorkommt) den kürztest möglichen Code zuzuordnen, dem seltensten Zeichen den optimalen längsten Code (wir habe 4 Zeichen zur Auswahl, die jeweils nur einmal vorkommmen – da müssen wir uns gleich entscheiden). Zunächst nehmen wir zwei der Zeichen mit den geringsten (von Null verschiedenen) Häufigkeiten. Da wir 4 Zeichen mit gleicher Häufigkeit haben, kann man sich beliebig entscheiden. Die beiden ausgewählten Zeichen bilden einen neuen Knoten, dessen Wert die Summe der Häufigkeiten der beiden erzeugenden Zeichen ist. Danach werden die beiden erzeugenden Knoten gestrichen und der neu erzeugte Knoten in die verbleibende Häufigkeitsmenge aufgenommen. In unserem Beispiel sind es (eine willkürliche Auswahl unter den 4 Kandidaten mit der Häufigkeit 1) N und M. Die neue Häufigkeitsmenge sieht dann so aus: (4, 3, 2, 2, 1, 1). Die Zahl der Bäume hat sich um eins reduziert, denn es sind offensichtlich nur noch 6 Einträge in der Häufigkeitsmenge (gegenüber 7 am Anfang). In der neuen Häufigkeitsmenge sucht man erneut die beiden Knoten mit der geringsten Häufigkeit (in unserem Fall S und X, denn die haben noch den Häufigkeitswert 1 – das Tupel NM hat nun den Häufigkeitswert 2) und fährt fort, die Anzahl der Bäume zu reduzieren. Zum Schluss sind alle Knoten zu dem Baum verbunden, den wir unten sehen. Leiten wir jetzt den Huffman-Code ab. Wir verfolgen in dem binären Baum von der Wurzel (W) den Weg zu jedem Zeichen. Nach der oben getroffenen Vereinbarung ist eine linke Abzweigung an einem Konten dem Bit 0 zugeordnet und rechts dem Bit 1. Um zu dem Zeichen E zu kommen, müssen wir von der Wurzel aus zweimal links verzweigen. Daraus folgt zweimal das Bit 0. Die Kodierung für das häufigste Zeichen ist nur 2 Bit groß, was einer (eindeutigen) Komprimierung auf 25 % entspricht. Nehmen wir Zeichen S. Dies ist eines der Zeichen, die am seltensten vorkommen. Der Weg von der Wurzel aus ist links, rechts, rechts, links (= 0110), eindeutig und mit 4 Bit immer noch halb so groß wie die orginale ASCII-Codierung in einem Byte. Wenn Sie sich den Baum ansehen, werden Sie anhand der Wege und Verzweigungen von der Wurzel zu einem Zeichen immer die eindeutige Huffman-Verschlüsselung des Zeichens ableiten können. (Alle Striche sollen von einem Knoten (Zahl) zum anderen verbunden sein) W=Wurzel
Java 2 Kompendium
1081
Anhang C Tabelle C.1: Ein Huffman-Baum
E
N
M
S
X
T
R
4
1
1
1
1
2
3
|
\
/
\
/
|
|
|
2
2
|
|
|
\
/
|
|
|
\ \
/
\
4 \
/ 5
/
/
8
/ \
/ \
/ 13
=
W
Daraus ergibt sich folgender Code für die vorkommenden Zeichen – von der Wurzel W aus gesehen: E N M S X T R
= = = = = = =
00 0100 0101 0110 0111 10 11
C.1.3
Selbstextrahierende Programme
Eine Besonderheit in der Welt der Komprimierung sind die so genannten selbstextrahierenden Programme oder auch SFX-Archive. Diese sind im Prinzip lauffähige Programme, deren einziger Zweck es ist, bei Aufruf die in dem Archiv komprimierten Daten zu extrahieren. Im Header dieser Archive ist also in lauffähiger Form die vollständige Information enthalten, wie die komprimierten Daten zu extrahieren sind, der Rest ist die »normale« gepackte Datei. Die meisten gängigen Packer sind in der Lage, selbstextrahierende Archive zu erzeugen. Die Vorteile liegen auf der Hand. Zum einen ist die Anwendung einfach. Keine kryptischen Befehlszeilen, nur das Programm starten. Zum anderen muss man nicht im Besitz des Komprimierungsprogramms sein, um die Daten zu extrahieren. Da aber die meisten 1082
Java 2 Kompendium
Wie funktionieren Verschlüsselungsverfahren?
Anhang C
Packer Sharwareprodukte sind und man sie sich nahezu von überall, ob aus CompuServe, von diversen CDs mit Shareware oder auch von kommerziell vertriebenen Softwarepaketen besorgen kann, ist dieses eigentlich kein gewichtiges Argument. Man findet sogar inzwischen so viele gute Sharewarepacker, dass praktisch kein Markt für kommerzielle Produkte vorhanden ist. Die meisten Vertreiber von Shareware legen den verwendeten Packer mit bei. Selbstextrahierende Archive haben auch einen großen Nachteil. Sie sind einfach grö ß er, weil eben die Extrahierungsfunktion in lauffähiger Form in jeder Datei mit gespeichert werden muss. Bei dem früher in der DOS-Welt weit verbreiteten ARJ ist das SFX-Modul ca. 15 KByte groß.
C.1.4
Wie groß ist die Reduzierung der Datenmenge?
Man kann keine allgemein gültige Aussage zur Reduzierung der Datenmenge machen, denn diese hängt massiv von den zu komprimierenden Dateien ab. So schaffen moderne Packer bei Textdateien (dazu zählen natürlich auch Java-Source-Dateien) sogar Komprimierungsraten bis zu 10 % (also nur noch 10 % der Orignalgrö ß e), während sich DOS-Programme auch in günstigen Fällen kaum unter 50 % verkleinern lassen. Wenn die Programme mit Overlaytechnik arbeiten, wird die Rate schlechter. Auch Windows-Programme sind kritische Kandidaten für Packer. Noch schlechter sieht die Rate bei Grafikdateien aus, die sich manchmal überhaupt nicht oder im besten Fall auf 70 % komprimieren lassen – der Grund dafür ist, dass Grafikformate bereits komprimiert sind (s.o.). Java-Klassen sind als Bytecode ein Mittelding zwischen Textdateien und komprimiertem Maschinencode. Es lassen sich recht gute Komprimierungsraten erzielen. Die genauen Raten sind von der Struktur der Klasse abhängig. Sie bewegen sich zwischen 30 % und 40 %.
C.2
Wie funktionieren Verschlüsselungsverfahren?
Digitaler Datenaustausch hat Hochkonjunktur. Nicht nur HTML-Seiten und Java-Applets, auch immer mehr Daten werden nicht mehr per Papier, Fax oder Telefon verschickt, sondern mittels digitaler Datenträger oder Leitungen auf die Reise geschickt. Auch sensible Angaben, die nicht unbedingt von unbefugten Personen ausgewertet werden sollen. Denken Sie nur an Online-Banking oder Online-Shopping mit Angabe der Kreditkartennummer. Deshalb raten sämtlich Experten, die Daten vor dem Verschicken in eine Form zu bringen, die ein widerrechtliches Auswerten verhindert oder zumindest erschwert. Dies übernehmen so genannte Verschlüsselungs- oder Kryptographieprogramme bzw. speziell in Programme eingebaute Funktionen zum Kodieren. Ursprünglich wurden Verschlüsselungsverfahren bei der Übermittlung von militärischen und diplomatischen Nachrichten eingesetzt. Schon aus der Java 2 Kompendium
1083
Anhang C Römerzeit sind Verschlüsselungsverfahren für Nachrichten bekannt, und eine der gängigsten Verschlüsselungsalgorithmen ist nach Julius Cäsar benannt, die Cäsar-Chiffre. Der Schutz vor unberechtigter Nutzung von Computern hat sich inzwischen auf allen Ebenen – sowohl auf Großrechnern und Client-Server-Systemen, aber auch auf Stand-Alone-PCs – durchgesetzt (wer hat es schon gerne, wenn jedermann private Dateien lesen kann?). Passwörter und Zugangsbeschränkungen zu verschiedenen Bereichen sind überall üblich. Hat ein Datenspion jedoch diese Zugangsbeschränkungen überwunden, kann er oft frei Daten auswerten. Noch schlimmer ist die Situation, wenn Informationen ein geschlossenes Computersystem verlassen und auf die Reise zu einem anderen System gehen. Sowohl der Datenträger, der per Postweg verschickt wird, als auch die E-Mail oder die Datei, die per Internet oder einem kommerziellen Netzwerk zu einem Empfänger unterwegs ist, können relativ leicht abgefangen und ohne Spuren zu hinterlassen kopiert/analysiert werden. Eine Nachricht läuft im Internet normalerweise über mehrere Server, bis sie ihr Ziel erreicht. Verschlüsseln musst für sensible Daten unbedingt sein. Wie aber werden Daten verschlüsselt, wie arbeiten Kodierungsprogramme?
C.2.1
Verschlüsselungs- bzw. Kryptographieprogramme
Um die Arbeitsweise von Verschlüsselungsprogrammen zu verstehen, sollte man sich erst einmal deutlich machen, was das Ziel der Verschlüsselung sein soll. Geheimhaltung! Dazu bedienen sich die Kodierungsprogramme einer recht alten mathematischen Wissenschaft, der Kryptologie. Diese Wissenschaft besteht aus zwei sich ergänzenden Teilgebieten, der Kryptographie und der Kyptoanalyse. Vereinfacht gesagt, versucht die Kryptographie Systeme für eine geheime Kommunikation zu entwickeln, während das Ziel der Kryptoanalyse die Entschlüsselung derselben ist. Man kann sich der Kryptologie nähern, indem man sie sich als eine Art Spiel vorstellt. Allgemeine Spielregeln Wie lauten nun die Spielregeln der Kryptologie, was ist das Ziel? Das Spiel besteht im Allgemeinen darin, dass ein Spieler – der Sender – Daten zu einem zweiten Spieler – dem Empfänger – übermitteln will. Es kann sich durchaus um dieselbe Person handeln, z.B. dann, wenn Daten auf einem Rechner geschützt werden sollen und der Sender/Empfänger zwischenzeitlich die Daten aus dem Augen verliert (Feierabend oder so). Der Sender nimmt die zu übermittelnden Daten, den Quelltext, und wandelt sie in eine geheime Form, den Chiffretext, um. Diese Umwandlung erfolgt mit einer geeigneten Verschlüsselungsmethode und zugehöriger Verschlüsselungsparametern. Nach der Umwandlung wird die Botschaft – der Chiffretext – auf
1084
Java 2 Kompendium
Wie funktionieren Verschlüsselungsverfahren?
Anhang C
den Weg zum Empfänger geschickt (bzw. auf dem Rechner bis zum nächsten Laden abgelegt). Wenn der Empfänger nun die erhaltene Botschaft lesen will, müssen ihm sowohl die zur Verschlüsselungsmethode passende Entschlüsselungsmethode als auch die Verschlüsselungsparameter bekannt sein. Mit diesen Angaben kann er dann den Chiffretext in den Quelltext zurückwandeln. Ein weiterer Mitspieler – der Kryptoanalyst (der verschieden von Sender und Empfänger sein sollte, sonst ist das Spiel langweilig) – versucht nun, die Daten auf dem Weg vom Sender zum Empfänger abzufangen und zu analysieren. Dazu muss er zum einen die Methode, zum Anderen die Parameter herausfinden, um dann den Chiffretext wieder in den Quelltext zu übersetzen. Ein solches Spielsystem wird in der Informatik ein Kryptosystem genannt. Sicherheit contra Ökonomie Einfache und komplexe Verschlüsselungsverfahren unterscheiden sich im Wesentlichen durch die Anzahl und die Komplexität ihrer Verschlüsselungsparameter. In der Regel gilt, ein Kryptosystem ist umso sicherer, je komplexer der Verschlüsselungsparameter ist oder je mehr Verschlüsselungsparameter vorhanden sind. Bei mehreren Parametern wird nach Anwendung des ersten Parameters ein Zwischencode erzeugt, auf den dann der folgende Parameter angewendet wird. Das Verfahren wird so oft wiederholt, bis alle Parameter abgearbeitet sind. Bei der Dekodierung werden die Parameter dann in umgekehrter Reihenfolge angewandt. Allerdings wird bei komplexen Verfahren auch die Bedienung immer komplizierter, zeitaufwändiger und immer unökonomischer. Ähnlich wie bei einem Zahlenschloß mit immer mehr Nummern. Die Frage nach Aufwand und Nutzen ist von zentraler Bedeutung. Im Prinzip ist es möglich, Kryptosysteme zu entwickeln, die nahezu unmöglich zu knacken sind (denken Sie im Extremfall an ein Zahlenschloss mit vielen Millionen Zahlen). Eine Kodierung und die – berechtigte – Dekodierung einer Botschaft wäre allerdings mit einem erheblichen Aufwand verbunden. Auch einfache Verschlüsselung schützt Jedoch auch einfache Methoden schützen bereits Daten. Sogar dann, wenn der Kryptoanalyst Kenntnis von einer der beiden notwendigen Informationen hat und ihm die zweite Information fehlt. Selbst bei nur einem Verschlüsselungsparameter ist es mit einem gewissen Aufwand verbunden, diesen Parameter zu finden, und wenn er nicht über das Entschlüsselungsverfahren informiert ist, d.h. nicht im Besitz eines passenden Dekodierungs-
Java 2 Kompendium
1085
Anhang C programmes ist, bedeutet es eine nicht unerhebliche Erschwerung bei der Auswertung des Chiffretexts. Auch hier ist ein Vergleich mit einem einfachen Fahrradzahlenschloss nicht abwegig. Für Profis zwar kein Hindernis, dagegen so einfach im Vorbeigehen lässt sich das Fahrrad von einem Laien nicht entwenden.
C.2.2
Einige Verschlüsselungsverfahren
Schauen wir uns einmal ein paar Beispiele für bekannte Verschlüsselungsverfahren an. Cäsar-Chiffre Eine relativ einfache und schon recht alte Verschlüsselungsmethode ist die Cäsar-Chiffre. Diese Verschlüsselungsmethode lautet wie folgt: Für jeden im Quelltext vorkommenden Buchstaben des Alphabets setze im Chiffretext einen um einen festen Parameter versetzten Buchstaben. Wenn beispielsweise im Quelltext der erste Buchstabe des Alphabets – ein A – auftaucht und der Verschlüsselungsparameter 3 ist (angeblich der von Cäsar benutzte Parameter), wird im Chiffretext der vierte Buchstabe des Alphabets – ein D – genommen, usw. Hat man einen Buchstaben vom Ende des Alphabets, wird wieder von vorne gezählt, also aus z.B. dem letzten Buchstaben des Alphabets – Z – wird in unserem Fall ein C. Die Dekodierung läuft entsprechend umgekehrt. Dieses Verfahren ist allerdings relativ einfach zu knacken, da der Kryptoanalyst – sofern er die Methode erraten hat – nur die verwendete Konstante ermitteln muss. Da bestimmte Buchstaben in jeder Sprache mit verschiedener Häufigkeit vorkommen, ist die Bestimmung dieser Verschlüsselungskonstanten meist schnell zu bewerkstelligen. In der deutschen Sprache kommt das E am häufigsten vor und dementsprechend wird mit hoher Wahrscheinlichkeit das am häufigsten im Chiffretext vorkommende Zeichen diesem E entsprechen. Die Differenz zwischen dem häufigsten Chiffrezeichen und dem E ist als Verschlüsselungskonstante einen Test wert. Sofern der damit ermittelte Text einen Sinn ergibt, war es sehr wahrscheinlich ein Treffer. Was tun bei binären Zeichen? Neben der relativen Unsicherheit hat unser Cäsar-Chiffre-Verfahren bisher noch einen weiteren Makel. Bis jetzt steht noch nicht fest, wie das Verfahren bei Zeichen reagieren soll, die nicht im Alphabet vorkommen. Bei Texten auf Papier (oder Textdateien) hat dies geringe Auswirkungen, aber was ist mit allgemeinen Dateien? Bei Zahlen ist das Verfahren leicht zu übertragen, jedoch was ist mit Sonderzeichen, Umlauten oder anderen binären Zeichen? Die Lösung dieses 1086
Java 2 Kompendium
Wie funktionieren Verschlüsselungsverfahren?
Anhang C
Problems führt über die Art, wie Zeichen auf Computern dargestellt werden – die ASCII-Tabelle oder eine entsprechende Kodierung. Statt des Alphabets wird diese Tabelle als Basis genommen, also ein Zeichen mit dem ASCIIWert 100 bekommt in unserem Fall im Chiffretext den ASCII-Wert 103. Bei binären Dateien – keinen reinen Textdateien – ist die einfache CäsarChiffre trotzdem wirkungsvoll. Durch die bereits in der Quelldatei vorkommenden Steuerzeichen ist ein Erraten der verwendeten Konstanten extrem erschwert (das E ist wahrscheinlich nicht mehr das häufigste Zeichen des Quelltextes), selbst wenn man mit einem passenden Programm die verschlüsselte Datei analysieren kann. Wenn sich ein Kryptoanalyst ungeschickt anstellt, muss er bis zu 256 Konstanten ausprobieren. Man kann also davon ausgehen, dass bereits das Cäsar-Chiffre-Verfahren für viele Gelegenheiten ausreicht. Damit soll dem Leichtsinn nicht die Tür geredet werden. Ganz im Gegenteil. Sämtliche sensiblen und auswertbaren oder anderweitig manipulierbare Informationen sollten – sofern die Brisanz es notwendig macht – auf jeden Fall besser verschlüsselt werden, wenn Sie über das Internet verschickt werden. Die Frage ist aber, wie kompliziert ein Verfahren überhaupt sein muss. Wenn Sie erwarten, dass Ihre speziellen Daten gezielt abgefangen werden, sollte das Verfahren natürlich so sicher wie möglich sein. Normalerweise wird ein Datenspion jedoch einen zentralen Platz (beispielsweise einen Server) überwachen und dort mit Programmen automatisch nach interessanten Informationen suchen, etwa sämtliche Mails automatisch nach dem Schlüsselbegriff »Kreditkartennummer« durchforsten lassen. Falls die Mail nur nach der Cäsar-Chiffre verschlüsselt ist (also der ASCII-Code nur verschoben), wird der Begriff »Kreditkartennummer« schon so verstellt, dass er den automatischen Routinen nicht mehr auffällt. Die Cäsar-Chiffre oder andere einfache Verfahren können übrigens auch dazu verwendet werden, eine kompliziertere Verschlüsselung zu verschleiern. Dabei wird das Verschlüsselungsverfahren so trickreich aufgebaut, dass es die Entschlüsselung der einfachen Variante bewusst einkalkuliert und damit eine falsche Spur legen will. Wenn der Datenspion das Verfahren geknackt hat und eine lesbare, aber uninteressante Information erhält, wird er wahrscheinlich die weitere Entschlüsselung abbrechen. In diesem Fall muss noch mindestens eine weitere Verschlüsselungsebene mit einem zugehörigen Verfahren existieren, aus der dieser Zwischencode dann weiter bearbeitet wird. Sichere Verfahren Dennoch, seit Cäsar ist viel Zeit vergangen, und die Cäsar-Chiffre ist auch für Binärdateien nicht die sicherste Verschlüsselungsvariante. Leistungsfähi-
Java 2 Kompendium
1087
Anhang C ger ist eine Methode, wo die Kodierung über Tabellen erfolgt. Bei Programmen sind diese Tabellen intern im Code gespeichert – sonst wäre es sinnlos. Es gibt keine feste Konstante, sondern jedes mögliche Zeichen des Quelltextes bekommt eineindeutig (d.h. eindeutig hin- und zurück zuzuordnen) ein anderes Zeichen aus der Tabelle zugeordnet. Auch bei dieser Methode gilt, dass sie für reine Texte über die Häufigkeit bestimmter Buchstaben bzw. Buchstabenpaare zu knacken ist, bei Binärdateien ist sie entsprechend sicherer. Die Vigenere-Chiffre Eine recht sichere Möglichkeit Daten zu verschlüsseln, ist die Vigenere-Chiffre, eine Verallgemeinerung der Cäsar-Chiffre. Eine sich wiederholende Folge von Zeichen bildet einen Schlüssel, aus dem der Verschlüsselungsparameter für jedes Zeichen des Quelltextes einzeln berechnet wird (Addition des Quellzeichens und des Schlüsselzeichens). Beispiel mit ASCII-Werten: Der Schlüssel bestehe aus 4 Zeichen – 100 101 102 103. Tabelle C.2: Verschlüsselung nach dem Vigenere-Chiffre
Quelltext:
50
54
100
50
100
54
100
100
Schlüssel (periodisch):
100
101
102
103
100
101
102
103
Chiffretext:
150
155
202
153
200
155
202
203
Der wesentliche Vorteil dieses Verfahrens liegt darin, dass gleiche Zeichen im Quelltext (in unserem Beispiel Ord(50)) im Chiffretext unter Umständen durch unterschiedliche Zeichen dargestellt werden (Ord(150) oder Ord(153)), je nach Position im Quelltext. Je länger der Schlüssel ist, d.h. je weniger Perioden auftreten, desto sicherer wird das Verfahren, aber auch gleichzeitig unökonomischer. Im Extremfall ist der Schlüssel genauso lang, wie der zu verschlüsselnde Text. Dieses Verfahren wird dann Vernam-Chiffre genannt und ist eines der sichersten – bekannten – Kryptoverfahren, das von vielen wichtigen Institutionen benutzt wird, sofern die Brisanz der Botschaft den Aufwand rechtfertigt. Wie gelangt ein Schlüssel zum Empfänger? Neben der Übermittlung einer Nachricht vom Sender zum Empfänger gibt es das Problem, wie man den Schlüssel sicher übermittelt. Wenn der Schlüssel geknackt oder gestohlen wurde, nützt das beste Verfahren nichts. Daher
1088
Java 2 Kompendium
Wie funktionieren Verschlüsselungsverfahren?
Anhang C
muss der Schlüssel getrennt von der Nachricht über einen möglichst sicheren Weg zum Empfänger gelangen. Oder aber, er wird ganz öffentlich verteilt.
C.2.3
PGP oder wie Java verschlüsselt
Wie ist der eben angeführte Widerspruch zu klären? Eine relativ neue und zuverlässige Verschlüsselungsvariante arbeitet mit zwei Schlüsseln, einem privaten Schlüssel, der nur beim Sender verbleibt, und einem öffentlichen Schlüssel, der verschickt wird. Diesen zweiten Schlüssel darf jeder kennen, denn er wird ausschließlich zum Kodieren einer Nachricht benutzt. Zum Dekodieren kann er nicht verwendet werden. Die Folge ist, dass bis jetzt nur der Empfänger des Schlüssels an den Sender des Schlüssels eine kodierte Botschaft senden kann, die ausschließlich dieser dann mit seinem privaten Schlüssel dekodieren kann. Will der potenzielle Sender eine kodierte Nachricht verschicken, muss er also erst von dem potenziellen Empfänger den öffentlichen Schlüssel erhalten. Dieses Verfahren hat den riesigen Vorteil, dass der Dekodierungsschlüssel niemals verteilt werden muss und es äußerst sicher ist. Es lässt sich auch mit den oben genannten Methoden kombinieren. Man muss aber einen großen Aufwand mit dem Verschicken und Verwalten der verschiedenen Kodierungs-Schlüssel treiben. PGP (Pretty Good Privacy) nutzt beispielsweise das Zweischlüsselsystem (öffentlicher und privater Schlüssel) mit einem 1024 Bit-Schlüssel. In der Vergangenheit gab es bzgl. der Verwendung eines längeren Schlüssels erhebliche Probleme, denn in den USA fallen Verschlüsselungssysteme unter die Exportbestimmungen für militärische Geheimnisse. Aber auch in Deutschland gibt es eine Diskussion, inwieweit überhaupt Verschlüsselungsverfahren benutzt und vertrieben werden dürfen, die interessierte Behörden nicht nach Belieben entschlüsseln können. Offizielles Argument für die Gegner von sicheren Verschlüsselungsverfahren ist, dass auch kriminelle Kreise solche Verfahren nutzen könnten.
C.2.4
Die heutigen Standard-Algorithmen
In der heutigen Verschlüsselungstechnik werden im Wesentlichen drei verschiedene Standards verwendet: DES IDEA RSA
Java 2 Kompendium
1089
Anhang C DES Der Data Encryption Standard (DES) stammt aus den Siebzigerjahren. Zur Verschlüsselung werden 56 Bit verwendet, was zu einer Anzahl von 2 hoch 56 Schlüsseln führt. Es handelt sich um einen symmetrischen Algorithmus mit einem frei wählbaren Schlüssel zum Ver- und Entschlüsseln der Daten. Mithilfe dieses Schlüssels wird der Orginaltext jeweils in 64-Bit-Blöcke umgewandelt. Dies geschieht durch Substitution und Permutation, also das Ersetzen und Vertauschen einzelner Bits nach festen Regeln. In 16 Durchläufen wird die eine Hälfte der Daten mit einem auf 48 Bit reduzierten Schlüssel unter Verwendung einer XOR-Funktion chiffriert, um anschließend mit der anderen Hälfte der Daten verknüpft zu werden. Für jeden Durchlauf wird ein neuer Schlüssel erzeugt. IDEA Der Intenational Data Encryption Algorithm (IDEA) ist ziemlich neu (Beginn der Neunzigerjahre). Er basiert auf einem 128-Bit-Schlüssel, bietet also 2 hoch 128 Möglichkeiten zur Erzeugung des Verschlüsselungscodes. Wie bei DES werden auch hier jeweils 64 Bits der Daten verschlüsselt. IDEA teilt die Originaldaten jedoch nicht in zwei Hälften, sondern benutzt vier Viertel zu je 16 Bit. Diese Blöcke werden mit einem Teil des 128-Bit-Schlüssels in acht Durchläufen verknüpft. Nach den letzten Durchlauf werden die Viertel in einem neunten Durchgang noch einmal mit vier weiteren Teilschlüsseln bearbeitet. Der Schlüssel wird durch Rotation der Bits um 25 Stellen jeweils neu festgelegt. RSA RSA (die jeweils ersten Buchstaben der Nachnamen der Erfinder Rivest, Shamir und Adleman) stammt wie DES aus den Siebzigerjahren. RSA beruht auf der Zerlegung großer Zahlen in ihre Primfaktoren und Moduloverfahren bezüglich dieser Primzerlegung. Die Originaldaten werden nicht in Blöcke unterteilt, sondern wie eine einzige große Zahl behandelt. Es gibt bei dem Verfahren einen zusammengesetzten öffentlichen Schlüssel zum Verschlüsseln und einen privaten Schlüssel zum Entschlüsseln.
C.2.5
Eine kleine Abschlussbemerkung zum Thema
Es ist zwar eine Binsenweisheit, aber jeder sollte sich klar darüber sein, dass es keine absoluten Schutz gegen eine unberechtigte Dekodierung von Informationen gibt. Auch der beste Code ist zu knacken. Durch Zufall. Mit System. Oder mit viel Zeit und genau das ist der springende Punkt: Eine Information hat nur innerhalb einer gewissen Zeitspanne einen Wert. Je länger eine Dekodierung dauert, umso wahrscheinlicher wird die Information keinen Wert mehr für den Kryptoanalyst haben.
1090
Java 2 Kompendium
Von der Dezimalrechnung abweichende Rechenarten
C.3
Anhang C
Von der Dezimalrechnung abweichende Rechenarten
Vielen Lesern dürfte Binär-, Oktal- und Hexadezimalrechnung vertraut sein. Dennoch soll ein kleiner Exkurs noch einmal die Grundlagen diskutieren. Normalerweise nutzt man in der Mathematik die so genannte Dezimalrechnung. Dabei stehen 10 verschiedene Symbole zur Darstellung einer Zahl zur Verfügung: die bekannten Zahlen von 0-9. Dies ist aber relativ willkürlich und auf keinen Fall zwingend. Das in der Computerwelt überall verwendete Binärsystem ist das bekanntete Beispiel.
C.3.1
Binärrechnung
Die Binärrechnung soll nur ganz kurz angedeutet werden. Bei Verwendung der Basis 2 können dementsprechend nur 2 Zustände in einem Zeichen kodiert werden: 0 und 1. Wenn mehr Zeichen dargestellt werden sollen, muss man mehr Stellen nehmen. Das ist im Dezimalsystem (und anderen Systemen wie dem Oktal- oder das Hexadezimalsystem) genauso, was man oft deshalb nicht zur Kenntnis nimmt, da es uns selbstverständlich erscheint (Zahlen, die grö ß er als 9 sind, werden zusammengesetzt: 10, 11, 23456 usw.). Hier folgt eine kleine Tabelle mit den wichtigsten Umrechnungen aus dem Dezimalsystem in das Binärsystem. Dezimaldarstellung Binärdarstellung 0
0
1
1
2
10
3
11
4
100
5
101
6
110
7
111
8
1000
16
10000
32
100000
Java 2 Kompendium
Tabelle C.3: Dezimal versus binär
1091
Anhang C Tabelle C.3: Dezimal versus binär (Forts.)
Dezimaldarstellung Binärdarstellung 64
1000000
128
10000000
255
11111111
C.3.2
Oktalrechnung
Die oktale Darstellung von Zahlen ist nicht so geläufig wie die binäre Darstellung. Allerdings besteht von der Logik her kein wesentlicher Unterschied. Sämtlich Werte werden nur zur Basis 8 dargestellt. Es gibt dementsprechend 8 Zustände, die in einem Zeichen kodiert werden: 0 bis 7. Ab der Zahl 8 erweitert man um eine Stelle, ab der Zahl 64 wieder um eine Stelle usw. Hier folgt eine kleine Tabelle mit den wichtigsten Umrechnungen aus dem Dezimalsystem in das Oktalsystem. Tabelle C.4: Dezimal versus oktal
1092
Dezimaldarstellung Oktaldarstellung 0
0
1
1
2
2
3
3
4
4
5
5
6
6
7
7
8
10
9
11
10
12
11
13
12
14
13
15
14
16
15
17
16
20
Java 2 Kompendium
Von der Dezimalrechnung abweichende Rechenarten
Dezimaldarstellung Oktaldarstellung 17
21
18
22
63
77
64
100
511
777
512
1000
4096
10000
32768
100000
C.3.3
Anhang C Tabelle C.4: Dezimal versus oktal (Forts.)
Hexadezimalrechnung
Die Hexadezimalrechnung ist meist sogar geläufiger als die oktale Darstellung, obwohl hier das Problem besteht, dass mit den herkömmlichen Zahlen 0-9 nicht genug Zeichen zur Darstellung sämtlicher Zeichen der hexadezimalen Darstellung zur Verfügung stehen. Java stellt einer hexadezimalen Darstellung von Zahlen immer ein 0x (oder auch 0X) voran. Sämtlich Werte werden hier nun zur Basis 16 dargestellt. Es gibt dementsprechend 16 Zustände, die in einem Zeichen kodiert werden: 0 bis 9 und noch 6 weiteren Zeichen, die das Dezimalsystem nicht mehr liefert. Man weicht auf das Alphabet aus und verwendet für die fehlenden 6 Zeichen Buchstaben. Meist nimmt man Großbuchstaben, aber Kleinbuchstaben gehen im Prinzip auch. Nach dem Zeichen F, das der Dezimalzahl 15 entspricht, erweitert man um eine Stelle, nach dem Zeichen FF – der Zahl 255 – wieder um eine Stelle usw. Hier folgt eine kleine Tabelle mit den wichtigsten Umrechnungen aus dem Dezimalsystem in das Hexadezimalsystem. Dezimaldarstellung Hexadezimaldarstellung 0
0
1
1
2
2
3
3
4
4
5
5
6
6
7
7
Java 2 Kompendium
Tabelle C.5: Dezimal versus hexadezimal
1093
Anhang C Tabelle C.5: Dezimal versus hexadezimal (Forts.)
1094
Dezimaldarstellung Hexadezimaldarstellung 8
8
9
9
10
A
11
B
12
C
13
D
14
E
15
F
16
10
17
11
18
12
19
13
20
14
21
15
22
16
23
17
24
18
25
19
26
1A
27
1B
28
1C
29
1D
30
1E
31
1F
32
20
254
FE
255
FF
256
100
4095
FFF
Java 2 Kompendium
Die Theorie des Zweierkomplements bei ganzen Zahlen
Dezimaldarstellung Hexadezimaldarstellung 4096
1000
65535
FFFF
65536
10000
C.4
Anhang C Tabelle C.5: Dezimal versus hexadezimal (Forts.)
Die Theorie des Zweierkomplements bei ganzen Zahlen
Ganzzahlige Variablen werden in Java allesamt als ZweierkomplementZahlen mit Vorzeichen verwendet. Zweierkomplement ist eine Methode, um negative ganze Zahlen durch Bits darzustellen. Am einfachsten wird eine Erklärung anhand eines Beispiels. Dabei soll ohne Beschränkung der Allgemeinheit ein Datentyp mit 8 Bit (Datentyp byte) herangezogen werden. Jede ganze Zahl wird auf binärer Ebene wie folgt dargestellt. DezimalZahl =
bit8*(2 bit6*(2 bit3*(2 bit1*(2
hoch hoch hoch hoch
7) + bit7*(2 hoch 6) + 5) + bit5*(2 hoch 4) + bit4*(2 hoch 3) + 2) + bit2*(2 hoch 1) + 0)
Beachten Sie, dass (2 hoch 0) = 1 gilt (und nicht = 0)! Beispiele: 3 + + =
= 00000011 = 0*(2 hoch 7) + 0*(2 hoch 6) + 0*(2 hoch 5) 0*(2 hoch 4) + 0*(2 hoch 3) + 0*(2 hoch 2) 1*(2 hoch 1) + 1*(2 hoch 0) 0 + 0 + 0 + 0 + 0 + 0 + 2 + 1
5 + + =
= 00000101 = 0*(2 hoch 7) + 0*(2 hoch 6) + 0*(2 hoch 5) 0*(2 hoch 4) + 0*(2 hoch 3) + 1*(2 hoch 2) 0*(2 hoch 1) + 1*(2 hoch 0) 0 + 0 + 0 + 0 + 0 + 4 + 0 + 1
7 + + =
= 00000111 = 0*(2 hoch 7) + 0*(2 hoch 6) + 0*(2 hoch 5) 0*(2 hoch 4) + 0*(2 hoch 3) + 1*(2 hoch 2) 1*(2 hoch 1) + 1*(2 hoch 0) 0 + 0 + 0 + 0 + 0 + 4 + 2 + 1
Nehmen wir uns nun das Beispiel 7 vor. Die letzten drei Bit sind auf den Wert 1 gesetzt, alle davor auf den Wert 0. Wie kann man aber nun einen negativen Wert wie -7 darstellen?
Java 2 Kompendium
1095
Anhang C Der wahrscheinlich offensichtlichste Ansatz ist wohl, ein Bit (naheliegenderweise das erste, also das am weitesten links stehende) als Vorzeichen-Bit zu verwenden. Etwa, wenn es den Wert 1 hat, die Zahl als negativ zu interpretieren und wenn es den Wert 0 hat, als positiv. Alles andere (sprich die restliche Darstellung der Zahl) bliebe gleich. Dieser Ansatz hat Einiges für sich, aber den wesentlichen Nachteil, dass in dem Fall sämtliche arithmetischen Operationen für Ganzzahlen kompliziert würden. Die Methode der Zweierkomplement-Arithmetik stellt wesentlich einfachere Algorithmen für binäre Arithmetik und Typkonvertierung zur Verfügung. Man kann wie folgt vorgehen: Stellen Sie eine Zahl positiv da und ziehen Sie davon den Wert 1 ab. Beispielsweise die Zahl 256, und davon 1 abgezogen ergibt 255. Binär ist 256 in 2 Byte darzustellen und sieht so aus: 0000000100000000. Davon 1 (triviale Binärdarstellung 00000001) abgezogen ergibt binär 11111111 = 255. Uns interessiert nur, was im letzten Byte passiert ist. Übertragen wir die Situation auf das letzte Byte: 00000000 – 00000001 = 11111111 Dies heißt jetzt dezimal 0 – 1 = -1. Wir haben die Darstellung von einer negativen Zahl. Wenn wir nun den Vorgang einfach weiter fortsetzen, werden wir die Darstellung aller negativen Zahlen bekommen. Tabelle C.6: Binärrechnungen
Dezimal:
-1
-
1
Binär:
11111111
-
00000001
Dezimal:
-1
-
2
Binär:
11111111
-
11111110
Dezimal:
-1
-
3
Binär:
11111111
-
11111101
Dezimal:
-1
-
4
Binär:
11111111
-
11111100
= = = = = = = =
-2 11111110 -3 11111101 -4 11111100 -5 11111011
Es ist offensichtlich, dass zur Darstellung einer negativen Zahl nur von der analogen positiven Zahl (dem positiven Komplement) die Zahl 1 abgezogen und dann sämtliche Bits umgedreht werden müssen. Diese Aufteilung des Wertebereichs in negativen und positiven Anteil ist auch der Grund, warum beispielsweise in einem Byte Werte von -128 (= – 2 hoch 7) bis 127 (= + (2 hoch 7) – 1) dargestellt werden. Die Null zählt noch zum positiven Anteil. In Java hat dies dann auch die zwangsläufige Folge, 1096
Java 2 Kompendium
Farbangaben in Java und JavaScript
Anhang C
dass das am weitesten links stehende Bit als Vorzeichenbit dient. Wenn das Vorzeichenbit den Wert 1 hat, dann ist der Wert negativ, sonst positiv. So ganz falsch war also unser ursprünglicher Denkansatz nicht, nur musste er ein wenig erweitert werden. Dies führt beispielsweise bei Operationen auf Byte-Variablen zu einigen auf den ersten Blick überraschenden Ergebnissen: 127+1=-128 127+9=-120 127+127=-2
C.5
Farbangaben in Java und JavaScript
Wenn man Farbangaben in Java und JavaScript (oder auch HTML) vornehmen will, hat man normalerweise zwei Varianten zur Definition: Die Angabe der so genannten RGB-Werte (RGB = Rot/Grün/Blau) der Farbe in Hexadezimalform oder in dezimaler Form mit jeweils dem Rot-, Grün- und Blau-Wert als eine Zahl zwischen 0 und 255 Die Angabe eines Farbnamens
C.5.1
Die direkte Angabe eines Farbnamens
Wenn Sie eine Methode, Funktion oder Variable finden, wo Farbangaben eine Rolle spielen und Sie einfach einen standardisierten Namen verwenden können, sind Sie in einer recht glücklichen Situation. Die Eingabe eines Farbnamens ist einfach – es sind die normalen, englischen Farbbezeichnungen. Da es aber noch keine standardisierten Farbnamen gibt, ist die Interpretation der Farbangabe häufig vom Darstellungsmedium – etwa einem Browser bei HTML-Seiten oder JavaScript – abhängig. Wenn das Darstellungsmedium einen Farbnamen nicht kennt, wird es sie nicht darstellen können. Außerdem gibt es nicht für jede denkbare Farbnunance (16,7 Millionen Farben) einen Namen. Flexibler ist da schon die Angabe eines hexadezimalen RGB-Werts.
C.5.2
Die Farbangabe mittels RGB-Werten in Hexadezimalform
Die hexadezimale Variante einer Farbangabe hat den Vorteil, unabhängig vom Darstellungsmedium zu sein, und es stehen im Prinzip 16,7 Millionen Farben zur Verfügung. Die Farben müssen aus hexadezimalen Angaben zu den drei Grundfarben Rot, Grün und Blau zusammengestellt werden. Dabei ist jede hexadezimale Farbdefinition 6-stellig. Die ersten beiden Stellen sind der Rot-Wert, die Stellen drei und vier der Grün-Wert und die letzten beiden Stellen beschreiben den Blau-Wert.
Java 2 Kompendium
1097
Anhang C Eine hexadezimale Ziffer hat bekanntlich 16 Zustände. Für jeden Farbwert (Rot, Grün, Blau) stehen 2 Stellen zur Verfügung. Das macht 16 x 16 (= 256) mögliche Zustände pro Farbwert. Aus diesen 256 Farbnuancen pro Einzelfarbe ergibt sich die Gesamtzahl der Farbzustände (256 * 256 * 256 = 16777216) durch Mischen der einzelnen Farbnuancen entsprechend ihrem Anteil. Wenn wir die RGB-Werte in dezimaler Form (jeweils 2 Byte mit hexadizmalen Werten ergeben den Wertebereich von 0 bis 255) verwenden, erhalten wir die folgenden Beispiele: Tabelle C.7: Farbangaben
Farbe
Standardfarbname
Rot-Wert
Grün-Wert
Blau-Wert
Weiß
Color.white
255
255
255
Hellgrau
Color.lightGray
192
192
192
Grau
Color.gray
128
128
128
Dunkelgrau
Color.darkGray
64
64
64
Schwarz
Color.black
0
0
0
Rot
Color.red
255
0
0
Rosa
Color.pink
255
175
175
Orange
Color.orange
255
200
0
Gelb
Color.yellow
255
255
0
Grün
Color.green
0
255
0
Magenta
Color.magenta
255
0
255
Cyan
Color.cyan
0
255
255
Blau
Color.blue
0
0
255
C.6
Erläuterung der Kurzschreibweisen von Operatoren
Die Kurzschreibweise von Operatoren ist eines der C-Erbstücke, die sicher nicht jeder sofort eingängig findet. Wem die verkürzte Schreibweise von Operatoren nicht ganz so vertraut ist, sollen die Operatoren +=, -=, *=, /= anhand von Beispielen und ihrer ausgeschriebenen Langform erläutert werden.
1098
Java 2 Kompendium
Java-Quellen im Internet
Anhang C
Operator
Beispiel
Langform
+=
n += 42
n = n + 42
-=
n -= 42
n = n – 42
*=
n *= 2
n = n * 2
/=
n /= 4
n = n / 4
%=
n %= 5
n = n % 5
&=
n&=1
n=n&1
|=
n|=1
n=n|1
^=
n^=2
n=n^2
<<=
n<<=1
n=n<<1
>>=
n>>=2
n=n>>2
>>>=
n>>>= 3
n=n>>>3
C.7
Tabelle C.8: Kurzschreibweisen für Operatoren
Java-Quellen im Internet
Selbstverständlich ist die Java-Adresse http://java.sun.com. Aber auch jenseits davon lohnt es sich zu schauen. Hier folgen einige Quellen rund um Java. Die angegebenen Links können sich in der Aktualität durchaus überholen und als tote Links herausstellen. Das Internet lebt halt. Java-Quellen im Internet gibt es wie Sand am Meer. Selbstverständlich ist Sun mit seinen Töchtern die erste Wahl, wenn Sie Orginalinformationen zu Java suchen. Aber auch andere große Firmen stellen Informationen zu Java zur Verfügung, wenn sie diese Technik unterstützen. Diese Informationen sind aber fast immer in Englisch. Es gibt aber auch zahlreiche deutsche JavaQuellen im Internet. Die deutschsprachige Java-Seite – Kaffee & Kuchen – finden Sie unter http://java.seite.net/. Dort gibt es zahlreiche Informationen rund um Java. Vor allem finden Sie dort Querverweise zu den interessantesten Java-Quellen im deutschsprachigen Raum. Das Java House unter http://www.bodo.com/java.htm sollten Sie sich einmal ansehen, wenn Sie kostenlose Applets suchen, die Magdeburger Java-Seite (http://java.cs.unimagdeburg.de/), wenn Sie eine umfangreiche Linkliste mit Dokumentationen, Tools, News, Mailing Lists, FAQs, Java-Verzeichnisse und Downloads benötigen.
Java 2 Kompendium
1099
Anhang C Selbstverständlich bieten sich die großen Suchdienste als Einstieg zu diverse Quellen für Applets und zahlreiche weitere Java-Informationen an. Als Beispiel sei hier nur Yahoo, der Urvater der Suchmaschinen angeführt, der sogar eine spezielle Java-Seite führt (http:// www.yahoo.com/Computers_and_Internet/ Programming_Languages/Java/). Viele Anregungen zur Erstellung von eigenen Applets können Sie sich von fremden Applets holen. Da viele der Seiten mit Applets auch die Orginalquelltexte beinhalten, kann man da viel lernen. Aber natürlich auch viel Spaß haben, denn es finden sich viele schöne Programme dort. Wenn Sie sich für Chaos-Theorie und Fraktale interessieren, ist die Seite http://www.student.nada.kth.se/~d94-rol/fractals/javaapps.html sicher sehr hilfreich. Sie finden dort eine echtzeitberechnete Juliamengenanimation, die wie die bekannteren, verwandten Apfelmännchen zu der Fraktaltheorie gehört. Dass Java nicht unbedingt zu den Performance-Killern (positiv gemeint) gehört, muss leider zugegeben werden. Wenn Sie die Geschwindigkeit Ihres Java-Runtime-Systems, inkl. Garfikspeed, Speicher usw. testen wollen, hilft Ihnen das BenchBeans-Applet (http://www.cs.tu-berlin.de/~mondraig/ english/benchbeans.html). Sowohl optisch als auch als wissenschaftliche Demonstration sehr spannend ist der NPAC Visible Human Viewer ( http://www.npac.syr.edu/projects/ vishuman/VisibleHuman.html). Dort können Sie hier einen Menschen in Form einer Simulation in Scheiben schneiden. Basic und Java miteinander zu verbinden scheint erstmal so, als wolle man eine Ente gegen einen Ferrari antreten lassen. Zwar ist eine Ente bedeutend schöner, orgineller und praktischer, aber hat im Rennen doch keine Chance. So scheint das wohl auch mit einer Basic-Java-Verbindung zu sein. Im Ernst, da gibt es eine BASIC-Implementation in Java für diejenigen, die keine Lust haben, Java zu lernen und dennoch Java-Applets erstellen wollen. Sie nennt sich HotTea (http://www.mbay.net/~cereus7/HotTEA.html). Skurril, aber dennoch faszinierend.
1100
Java 2 Kompendium
D
Anhang D
Anhang D gibt noch einmal einen komprimierten Überblick über die Neuerungen der Java-2-Plattform.
D.1
Neuerungen der Java-2-Plattform
Wie auch die letzten Wechsel des JDK hat der Wechsel vom JDK 1.2 auf das JDK 1.3 zahlreiche Neuerungen mit sich gebracht. Und das, obwohl beide zur Java-2-Plattform gezählt werden. Aber glücklicherweise sind diesmal die massiven Umstrukturierungen ausgeblieben, die den Wechsel vom JDK 1.1 auf das JDK 1.2 so chaotisch gemacht haben. Die meisten Veränderungen im JDK 1.3 sind Erweiterungen. Etwa die in Anhang B beschriebenen neuen Pakete, die hier nochmals kurz angegeben werden sollen. Neu im API des JDK 1.3 sind folgende Pakete: java.awt.im.spi javax.naming javax.naming.directory javax.naming.event javax.naming.ldap javax.naming.spi javax.rmi javax.rmi.CORBA javax.sound.midi javax.sound.midi.spi javax.sound.sampled javax.sound.sampled.spi javax.transaction org.omg.CORBA_2_3 org.omg.CORBA_2_3.portable org.omg.SendingContext org.omg.stub.java.rmi
Aber auch darüber hinaus gibt es ein paar bemerkenswerte Neuerungen, die wir hier nicht alle im Detail ansprechen können und wollen. Dafür ist die Online-Dokumentation viel besser geeignet, zumal sich laufend in Details weitere Veränderungen ergeben. Ein paar wichtige Schlaglichter sollen dennoch aufgeführt werden.
Java 2 Kompendium
1101
Anhang D Das Java Naming and Directory Interface (JNDI), das vorher nur als Erweiterung bereitstand, zählt nun direkt zur Java-2-Plattform. Dies spielt für den erweiterten Einsatz von Dingen wie CORBA Object Services und RMI eine Rolle. Aber auch direkt im Bereicht Remote Method Invocation (RMI) gab es ein paar kleinere, aber durchaus interessante Veränderungen. So hat die Klasse java.rmi.server.RMIClassLoader eine neue Methode (getClassLoader()) und Strings jenseits von 64 K können serialisiert werden (das gilt allgemein für die Serialisation). Auch die Tools für die Arbeit mit RMI wurden geringfügig modifiziert. rmic hat einigen neue Optionen bekommen und rmid fordert eine Security-Policy-Datei. Dazu kommt, dass RMI CORBAs IIOP-Kommunikationsprotokoll verwenden kann. Beim Drag&Drop unter Java gab es nur kleine Erweiterungen, aber beim Java Sound mit seiner Sound-Engine gibt es ein vollständig neues API. Damit können die Audioformate AIFF, AU und WAV und die Musikformate MIDI Type 0, MIDI Type 1 und Rich Music Format (RMF) verwendet werden. Auch in Hinblick auf die Sampling-Rate hat sich viel getan. Für Applets gab es im Wesentlichen Feinschliff für die Performance. Bei Java 2 D gab es dagegen einige Neuerungen, u.a. multiplen Monitor Support über die Klassen Frame(GraphicsConfiguration) und Window(Window, GraphicsConfiguration) und dynamisches Laden von Fonts. Auch Swing und das AWT wurden erheblich erweitert. Der Umfang der Erweiterungen sprengt bei weitem den Rahmen dieses Buchs. Besonders erwähnenswert ist dabei die Einführung einer Klasse zum Generieren von nativen Systemen für Eingabe-Events (Robot) und die Überarbeitung der Druckmöglichkeiten von Java im AWT-API. Mit dem neuen Konzept sind hier zahlreiche neue Klassen eingeführt worden: JobAttributes JobAttributes.DefaultSelectionType JobAttributes.DestinationType JobAttributes.DialogType JobAttributes.MultipleDocumentHandlingType JobAttributes.SidesType PageAttributes PageAttributes.ColorType PageAttributes.MediaType PageAttributes.OrientationRequestedType PageAttributes.OriginType PageAttributes.PrintQualityType
1102
Java 2 Kompendium
Neuerungen der Java-2-Plattform
Anhang D
Auch im Bereich Sicherheit, Serialization und der Arbeit mit Netzwerken hat das JDK 1.3 viele Detailerweiterungen erfahren, deren Details hier nicht besrochen werden können. Zum Thema Accessibility kann man als Schlaglicht anführen, dass diverse neue Klassen zur Java-2-Plattform hinzugefügt wurden (AccessibleIcon, AccessibleRelation, AccessibleRelationSet, AccessibleTable, AccessibleTableModelChange). Ansonsten halten sich die Neuerungen in der API des JDK 1.3 jedoch in Grenzen (mit Ausnahme der zahlreichen neuen Pakete, die in Anhang B angesprochen werden). Allgemein hat Sun mit dem JDK 1.3 wieder versucht, ein großes Problem von Java weiter zu entschärfen – die Performance. Wichtigster Schritt in Hinblick auf eine bessere Performance ist die Hinzufügung der Java HotSpot Client Virtual Machine zu dem SDK. Diese bewirkt eine verbesserte Startup-Zeit, Speicheroperation und Programmausführung. Für den Tool-Support (also die Unterstützung von Programmen, die das JDK nutzen) wurden mit dem Java Platform Debugger Architecture (JPDA) drei Schnittstellen bereitgestellt, die Debugger in fremden Entwicklungsumgebungen verwenden können. Bei den JDK-Tools selbst ist besonders erwähnenswert, dass der Compiler komplett neu reimplementiert wurde und nun bedeutend schneller ist. Die Tools jarsigner und keytool bekamen eine neue Option und das JarTool unterstützt nun die Indizierung von Jar-Dateien. Damit wird der Zugriff auf diese hochkomprimierten Dateien schneller und einfacher.
Java 2 Kompendium
1103
Stichwortverzeichnis
! <APPLET>, Einbindung von Applets 349 <EMBED>, Einbindung von Applets 358 , Einbindung von Applets 360 <SERVLET> 868 2D-API, Grundsätzliches 481 2D-Geometrie 971 2D-Java Operationen auf Bildern 506 Texte 503 2-Tier-System 853 3D-Rechteck zeichnen 425 3-Tier-System 853 A Abfragen von Fonts 448 Abgerundete gefüllte Rechtecke 426 Abgerundete Rechtecke 426 Abrufen von Farbinformationen 444 Abspielen einer Animation 458 abstract, Methoden 307 Abstract Window Toolkit Siehe AWT AbstractMethodError 987 Abstrakte Klassen 160, 282 Abstrakte Methoden 307 accept() 721 Sockets 876 Accessibility 49 AccessibleObject, Reflection 830 action(), Methode 607 actionPerformed(), Methode 615 activation system daemon 410, 937 Activation-Framework 937 Activator 35 activeCount() 400, 407 activeGroupCount() 407 ActiveX-Bridge 837 Java 2 Kompendium
ActiveX-Controls Sicherheitskonzept 766 Vergleich zu Java Beans 837 Adapterklassen 289 add(), Methode 526 addImage(), MediaTracker 455 addItem(), Methode 555 Addition von Zeichenketten 199 Ad-hoc-Typkonvertierungen 250 Advanced Research Projects Agency 58, 69 Ändern von Elementen in Datenfeldern 239 Affine Transformation 487, 501 AffineTransform 501 afterLast(), Datenbanknavigation 891 all, JavaScript-Objekt 807 allowsMultipleSelections(), Methode 564 Alphachannel 478, 494 AlphaComposite 503 anchor, JavaScript-Objekt 807 Andreessen, Marc 61 A-Netz 67 Animation 417, 457 Abspielen 458 Animationsrahmen 458 Clipping 474 Double-Buffering 476 Flimmereffekte 468 Überschreiben der update()-Methode 468 Anonymous Classes 46, 287 Anpassung einer Applikation an verschiedene Länderstandards 40 ANSI SQL92 EntryLevel 903 Antialiasing 500 Anweisungen 258 Anweisungsarten 259f. Anwender-Koordinatenraum 479 Anzeigen von Bildern 452 1105
Stichwortverzeichnis Apache 854 Apfelmännchen 439 appendText(), Methode 571 Applet Bilder 373 dynamisch mit Scripten schreiben 823 Einbindung von 343 Grundmethoden 364 Initialisierung 364 Interaktion 375 interne Arbeitsweise 363 Leben eines 140 Mausereignisse 379 Multithreading-fähig machen 388, 404 Musterapplet als Schablone 366 paint() 368 serverseitige Siehe Servlets Sound 375 Start 365 Stop 365 Tastaturereignisse 384 Unterschrift 364 wichtige Methoden 367 Zerstören 365 applet, JavaScript-Objekt 808 Applet can’t start 115 Applet not initializied 115 AppletContext 376 Applet-Erstellung, Grundlagen 341 Applet-Klasse 367 Applet-Referenzierung, HotJava-Syntax 363 Appletviewer 121 Interne Arbeitsweise 124 Menü 123 appletviewer_g 118 Applikationen und Applets, Unterschiede 137 Architekturneutral 173 Architekturunabhängig Siehe plattformunabhängig Argumentliste, Vergleich Java zu C/C++ 783 ArithmeticException 985 Arithmetische Operatoren 201 Arithmetische Zuweisungsoperatoren 206 ARPA 58 ARPANET 58 ARPNET Siehe ARPANET
1106
Array 235 dynamische 238 JavaScript 802 JavaScript-Objekt 808 mit allgemeinen Objekten 242 Reflection 830 Vergleich Java zu C/C++ 778 ArrayIndexOutOfBoundsException 985 Arrays, verschachtelte 241 ArrayStoreException 680, 985 ASCII-Code 185 Audio-Unterstützung, Neuigkeiten 47 Aufbau eines Animationsrahmens 458 Auflösung von Namensräumen bei Bezeichnern 195 Ausdrucksanweisungen 259, 264 Ausdrücke und Operatoren 245 Ausführungsumgebung 177 Typzustand 761 Ausgabe-Koordinatenraum 479 Ausnahme ArithmeticException 985 ArrayIndexOutOfBoundsException 985 ArrayStoreException 985 ClassCastException 985 ClassNotFoundException 985 CloneNotSupportedException 985 IllegalAccessException 985 IllegalArgumentException 985 IllegalStateException 986 IllegalThreadStateException 986 in Schnittstellen 321 IndexOutOfBoundsException 986 InstantiationException 986 InterruptedException 986 NegativeArraySizeException 986 NoSuchFieldException 986 NoSuchMethodException 986 NullPointerException 986 NumberFormatException 986 RuntimeException 986 SecurityException 986 selbstdefinierte 687 StringIndexOutOfBoundsException 986 UnsupportedOperationException 986 Ausnahmebehandlung 675
Java 2 Kompendium
Stichwortverzeichnis Ausnahmen-Handling, explizites 680 Auswahlanweisung 259, 265 Automatische Speicherbereinigung 177 available(), Daten einlesen 702 AWT 517 Bildlaufleisten 572 Container 523, 525 Eventmodelle 605 Komponenten 522 Kontrollkästchen 545 Label 543 Layoutmanager 524 Listen 554 Menüs 580 Neuigkeiten 42 Optionsfelder 545 Panels 526 Schaltflächen 538 Textbereiche 566 Textfelder 566 Zeichenbereiche 578 Zusammenfassende Beschreibung 521 AWT Drawing Model 480 AWTEvent, Klasse 612 AWTLayoutmanager 590 B Bad command or filename 104 BDK 46, 838 BeanBox 838 Beans 832, 977f. Befehlszeilenargumente, Vergleich Java zu C/C++ 783 beforeFirst(), Datenbanknavigation 891 Benutzer-Threads 409 Berners-Lee 60 Bewertung von Ausdrücken 246 Bewertungsreihenfolge 248 Bezeichner 192 Namenskonventionen 192 Bezeichnete Anweisung 259, 261 Bildlaufleisten 522, 561f., 566, 572f., 575ff., 621 AWT 572 Swing 644 Binärrechnung 1091 Binden 165
Java 2 Kompendium
Bitweise Arithmetik 206 Bitweise arithmetische Operatoren 201, 206 Bitweise Verschiebungsoperatoren 201, 211 Bitweise Zuweisungsoperatoren 201, 213 Bitweiser And-Operator (&) 207 Bitweiser Komplement-Operator (~) 210 Bitweiser OR-Operator (|) 209 Bitweiser Xor -Operator (^) 209 Blockanweisungen 259 Blöcke 261 B-Netz 67 Boolean, JavaScript-Objekt 808 Boolesche Literale 200 Boolesche Werte, Vergleich Java zu C/C++ 780 bootstrap Classpath 108 BorderLayout, Klasse 600 Botschaften 155 break-Anweisung 272 bezeichnete 273 unbezeichnete 272 BufferedInputStream 709 BufferedOutputStream 709 Bug 668 Button Klasse 538 Swing 631 ByteArrayInputStream, Datenfeldströme 707 ByteArrayInputStream(), Daten einlesen 707 ByteArrayOutputStream, Datenfeldströme 707 Bytecode 82 Bytecode-Strom 175 Bytecode-Verifier 761 C C/C++ Ahn von Java 79 Unterschiede zu Java 777 Cäsar-Chiffre 724, 1086 call(), Aufruf von JavaScripten 816 Can't make a static reference to nonstatic..., Fehlermeldung 306 Can’t find class 112 cancel(), Drucken 749 canRead() 719 Canvas, Klasse 578 canWrite() 719
1107
Stichwortverzeichnis CardLayout, Klasse 603 Casting 187, 250 Casting-Operator 250 catch 276, 681 Cedar/Mesa-System 85 CERN 60 CGI 78, 854 Chaostheorie 440 charWidth() 450 C-Header-Dateien 909 checkAll(), MediaTracker 457 Checkbox, Klasse 546 CheckboxGroup, Klasse 548 CheckboxMenuItem, Klasse 581 checkConnect() 874 checkid(), MediaTracker 457 Choice, Klasse 555 Class not found 112 ClassCastException 985 ClassCircularityError 987 classes.zip 93 ClassFormatError 987 CLASSID, Sicherheitskonzept ActiveX-Controls 769 Classloader 756, 762 ClassNotFoundException 680, 985 CLASSPATH 105, 116 CLASSPATH-Bug 773 clear(), Methode 562 clearRect() 423 Client-Server-Architektur mehrschichtige 843 zweischichtig 842 Client-Stubs, CORBA 847 Clipping 474 clipRect() 474 CloneNotSupportedException 985 close() Beispiel 726 Daten einlesen 704 Daten schreiben 707 CMYK, Farbraum 506 C-Netz 67 CODEBASE 115 Codec 514 Collections 244 Color 506
1108
Color-Klasse 435 COM 767 Common Gateway Interface Siehe CGI Common Object Request Broker Architecture Siehe CORBA Component, Drucken 745 Component Object Model Siehe COM componentAdded(), Methode 614 componentRemoved(), Methode 614 connect() 714 Constant Pool 178 Constructor, Reflection 830 Constructors 289 Container AWT 523, 525 HTML 345 ContainerListener, Schnittstelle 614 continue-Anweisung 273 controlDown() 386 Controls 768 copyArea() 424 CORBA 45, 844 Could not read properties file 116 countItems(), Methode 556, 564 createImage() 507 currentThread() 400 D Daemon Siehe Dämonen Dämonen 409 Dämon-Threads 409 Darstellung von Zeichen aus einem Zeichen- oder einem Byte-Datenfeld 445 Data Encryption Standard 1090 Datagram 878 DatagramPacket 880 DatagramSocket 879 Datagram-Sockets 878 DataInput 699, 710 Beispiel 699, 706, 724 DataInputStream 710, 712 Beispiel 699, 706, 724 DataOutput 699, 710, 712 Beispiel 706, 724 DataOutputStream 710, 712 Beispiel 706, 724
Java 2 Kompendium
Stichwortverzeichnis Date, JavaScript-Objekt 808 Dateien löschen 720 Datenbank 880 Löschen 901 Selects 894 Update 897 Datenbankunterstützung 44 Datenbankzugriff, Access 891 Datenfelddeklaration 263 Datenfelder 235 Deklarieren 236 Erstellen 237 Datenkapselung 154, 757, 820 Datentypen 188 JavaScript 801 Vergleich Java zu C/C++ 780 dbx 915 DCE 844 DCOM 767, 844 DDE 767 Deadlock, Multithreading 413 Debuggen, Applets 919 Debugger Arbeit mit einem 673 Direkter Aufruf 917 Folgeprozess des Interpreters 918 Vorbereitung einer Debugger-Sitzung 916 Debugging 668 Deklaration 259, 262 einer Methode 303 einer Schnittstelle 318 primitiver Typen 264 Deklarieren von Datenfeldern 236 Dekrementoperator 201, 204 Delegates 520 delete() 720 delItem(), Methode 562 delItems(), Methode 562 DE-NIC 65 deprecated 39 DES 1090 deselect(), Methode 563 destroy() 140, 398 Destruktor 297 Deutsches Network Information Center 65 Dialog, Klasse 527, 533
Java 2 Kompendium
Dienstprotokolle 60 Digital Signature Algorithm Siehe DSA Digital Signing Tool 929 disable(), Methode 539 Disassemblierung, Bytecode 906 Disk And Execution Monitor 409 dispose(), Methode 529 Distributed Component Object Model Siehe DCOM Distributed Computing Environment Siehe DCE DNS 68 DNS-Bug 773 DNS-Konzept 68 do-Anweisung 270 document, JavaScript-Objekt 808 doDelete(), Servlets 865 doGet(), Servlets 864 Dokumentations-Tool 911 Domain Name System Siehe DNS doPost(), Servlets 864 doPut(), Servlets 864 DOSKEY 119 Double-Buffering 476 do-while-Anweisung 270 Drag & Drop API 50 Drag&Drop 967 draw3DRect() 425 drawArc() 433 drawImage() 373, 452 drawLine() 421 drawOval() 431 drawPolygon() 428 drawRoundRect() 426 drawString() 445 Drucken 746 Drucken 697, 744 Druckvorschau 478 DSA 930 Duke 74 Dynamisches Binden Siehe Spätes Binden E Early Binding 165 echoCharIsSet(), Methode 571 ECMA-262-Norm, JavaScript 1070 Eiffel, Ahn von Java 79 Einbindung von Java-Applets in HTML-Seiten,
1109
Stichwortverzeichnis Grundlagen 343 Einfachvererbung 157, 162 Einstellige arithmetische Operatoren 203 Emulation 174 enable(), Methode 539 enumerate() 407 Enumeration 245 EOFException 710 Beispiel 700 Ergänzungstasten 386 err, System 713 Error 987 Klasse 679 Errors trappable 675 untrappable 672 Errors java.lang 987 Erstellen eines Applets 364 eines Pakets 335 von Datenfeld-Objekten 237 von Fontobjekten 445 von Schnittstellen 318 Erweiterung einer Klasse mit Thread 394 von Schnittstellen 319 Erzeugung eines Datenfeldes mit dem new-Operator 237 European Organisation for Nuclear Research 60 eval(), Aufruf von JavaScripten 816 Event, Klasse 607 event, JavaScript-Objekt 808 Event-Handler 799 Eventhandling 1.0 606 Eventhandling 1.1 Standard-Adapter 632 WindowListener 666 Event-Klasse 385 Event-Listener, Eventmodell 1.1 613 Eventmodell 1.1 611 Eventmodelle, AWT 605 EventObject, Klasse 612 Exception 985 Klasse 679 Exception-Handling, global 678
1110
ExceptionInInitializerError 987 Exceptions 677 selbstdefinierte 687 Vergleich Java zu C/C++ 784 executeQuery() 895 executeUpdate() 897 Execution Environment Siehe Ausführungsumgebung exists() 719 exit(), Methode 529 Explizite Konvertierungen 253 extends 285 eXtensible Markup Language Siehe XML F Farbangaben 434 Farbangaben in Java und JavaScript 1097 Farben setzen 436 Farbraum 506 Fehlerbehandlung, individuell programmierte 676 Festlegungsoperator 253 Field, Reflection 830 FIFO 177 File 718 Beispiel 721 Dateizugriffe 698 file not found 104 FileDescriptor 722 FileDialog, Klasse 537 FileInputStream 721 Beispiel 699, 706 FilenameFilter Beispiel 721 Dateizugriffe – gefiltert 698 FileOutputStream 721 Beispiel 706 fill3dRect() 425 fillArc() 434 fillOval() 432 fillPolygon() 430 fillRect() 423 fillRoundRect() 426 FilterInputStream 709 FilterOutputStream 709 final, Methoden 308 Finale Klassen 281
Java 2 Kompendium
Stichwortverzeichnis Finalisierer 297 Finalisierung 297 finalize() 140, 297 finally 276, 685 First Person 74 first() Datenbanknavigation 891 Methode 604 Flimmereffekte in Animationen 468 FlowLayout, Klasse 593 flush(), Daten schreiben 706 Fonts, 2D Java 503 for-Anweisungen 271 form, JavaScript-Objekt 808 forName() Datenbanktreiber laden 889 Reflection 831 Fraktale 439 Fraktal-Programme 439 Frame, Klasse 527 frame 176 JavaScript-Objekt 808 Freundliche Klassen 280 Freundliche Methoden 303 Frühes Binden 165 Function, JavaScript-Objekt 808 Funktionen 302 G Gamelan 342 Ganzzahlliterale 195 Garbage Collection Heap, theoretischer Aufbau 177 gdb 915 Gefülltes 3D-Rechteck zeichnen 425 Gefülltes Rechteck zeichnen 423 General Inter-ORB Protocol Siehe GIOP GeneralPath 482, 485 Geschützte Methoden 304 getAbsolutePath() 720 getAddress() 875 getAlignment(), Methode 544 getAllByName() 875 getAppletContext() 376 getAppletInfo() 369 getApplets() 377 getAscent() 449 getAudioClip() 375, 510 Java 2 Kompendium
getBoundingBox() 429 getByName() 875 getCheckboxGroup(), Methode 549 getClass(), Reflection 831 getCodeBase() 377, 452 getColumns(), Methode 570 getConfigurations() 508 getConnection(), Datenbankverbindung aufbauen 890 getContentPane(), Swing 626 getCopies(), Drucken 749 getCurrent(), Methode 549 getCursorType(), Methode 530 getDescent() 449 getDeviceConfiguration() 508 getDirectory(), Methode 537 getDocumentBase() 377, 452 getEchoChar(), Methode 571 getFD() 722 getFile(), Methode 537 getFilePointer() 723 getFont() 447, 449 getFontList() 448 getGlyphMetrics() 504 getGlyphOutline() 504 getGraphics(), Drucken 744 getHeight() 450, 454 getHostAddress() 875 getHostName() 874 getImage() 373, 451 Methode 579 getItem(), Methode 556 getLabel(), Methode 539 getLeading() 449 getLineIncrement(), Methode 575 getLineNumber() 716 getLocalHost() 875 getMaximum(), Methode 574 getMaxPriority() 406 getMember(), Zugriff auf JavaScript 817 getMinimum(), Methode 574 getName() 449, 719 Threads 400 getOrientation(), Methode 575 getPageDimension(), Drucken 745 getPageResolution(), Drucken 745 getParameterInfo() 369 1111
Stichwortverzeichnis getParent() 406, 719 getPath() 720 getPrinterJob(), Drucken 749 getPrintJob(), Drucken 744 getPriority() 401 getRows(), Methode 572 getSelectedIndex(), Methode 556, 563 getSelectedIndexes(), Methode 563 getSelectedItem(), Methode 556, 563 getSelectedItems(), Methode 563 getSelectedText(), Methode 569 getSelectionEnd(), Methode 569 getSelectionStart(), Methode 569 getSize() 449 getState(), Methode 547 getStringOutline() 504 getStyle() 449 Getter-Methoden, Java Beans 839 getText(), Methode 543, 569 getTitle(), Methode 530 getToolkit(), Methode 579 getWidth() 454 getWindow(), Zugriff auf JavaScript 817 GIOP, ORB 846 Glasgow 45 Gleichheitsoperatoren 219 Gleitpunktliterale 196 Gleitzahl-Literale 196 Glyph 500, 504 Gosling, James 73 gotFocus(), Methode 610 GradientPaint 502 Graphical User Interface Siehe GUI Graphics2D, Drucken 749 Graphics2D-Objekt, Casten 481 GraphicsConfiguration 508 GraphicsDevice 508 GraphicsEnvironment 508 Graphik 417 Graphische Ausgabemedien 507 Green-Projekt 73 GridBagConstraints, Klasse 598 GridBagLayout, Klasse 598 GridLayout, Klasse 595
1112
Grundgerüst einer HTML-Datei 347 einer HTML-Seite 345 Grundprinzipien der OOP 166 GUI 88 H handleEvent(), Methode 606 Hash-Code 759 Hashtable 244 Heap 177 Hexadezimalrechnung 1093 hide(), Methode 529 Hintergrundfarben 438 history, JavaScript-Objekt 808 Hostidentifikator 66 HotJava 75 HOTJAVA_HOME 116 HOTJAVA_READ_PATH 116 HOTJAVA_WRITE_PATH 116 HotJava-Syntax 363 HotSpot 30 HTML 62 Applet-Referenz mit Pfaden 352 Auflistung aller HTML 4.0 – Attribute 1030 Auflistung aller HTML 4.0 – Befehle 1025 Body & Header 346 Container 345 Graphikbezüge 348 Hyperlink 347 Kommentare 345 Parameter für ein Java-Applet 355 Position des Applets in der Webseite 353 Referenzierung eines Java-Applets 349 wichtige Elemente 347 HTML + Discussion Document 63 HTML 2.0 63 HTML 3.0 63 HTML 3.2 63 HTML 4.0 64 HTML-Grundlagen 343 HTML-Steueranweisungen 344 Huffman-Algorithmus 188, 1078 Hyperlink 347 Hypertext Markup Language Siehe HTML
Java 2 Kompendium
Stichwortverzeichnis I IAB 65 IDE 88 IDEA 1090 Identifier 192 Identities 930 IDL 45, 170, 844 idlj 847 if-Anweisung 265 if-else-Anweisung 266 if-else-if-Anweisungen 266 if-else-Operator 249 IIOP 844 IllegalAccessError 987 IllegalAccessException 680, 985 IllegalArgumentException 985 IllegalMonitorStateException 985 IllegalStateException 986 IllegalThreadStateException 986 image, JavaScript-Objekt 808 ImageIcon 645 Imageobserver 455 immutable, Eventhandling 1.1 612 Implementation von Runnable 403 Implementierung von Schnittstellen in Klassen 322 implements 317 import, Vergleich Java zu C/C++ 781 import-Anweisung 331 in, System 713 IncompatibleClassChangeError 987 IndexOutOfBoundsException 986 InetAddress 874 Information Hiding 154 init() 140 Inkrement-/Dekrement-Operatoren 204 Inkrement-Operator 201, 204 Inner Classes 46, 287 Innere Klassen 287 input method framework 43 InputStream 698 insertText(), Methode 571 inside() 430 Instanceof-Operator 258 InstantiationError 987 InstantiationException 680, 986
Java 2 Kompendium
Instanzen, allgemeine Erklärung 157 Instanzmethoden 305 Instanzvariablen 326 Integer-Literale 195 Integrierte Entwicklungsumgebungen Siehe IDE Intenational Data Encryption Algorithm 1090 Interface Description Language Siehe IDL Interfaces 317 InternalError 987 Internationalisierung 40, 972 Internet 56 Internet Architecture Board Siehe IAB Internet InterORB Protocol Siehe IIOP Internet Protocol Siehe IP Internet Society Siehe ISOC Internet-Topologien 66 InterNIC 65 Interoperabilität 39 Interoperable Object References Siehe IOR interrupt() 397 interrupted() 397 InterruptedException 680, 986 Introspektion, Java Beans 836 Invalid JAVA_HOME 116 InvocationHandler 830 IOR, ORB 845 IP, erstes Auftreten 60 IP-Nummer 66 isAbsolute() 719 isAlive() 398 isBold() 449 isCancelled(), Drucken 749 isDaemon() 411 isDirectory() 718 isEditable(), Methode 570 isEnabled(), Methode 540 isFile() 718 isInterrupted() 397 isItalic() 449 isModal(), Methode 534 ISOC 65 isPlain() 449 isSelected(), Methode 563 isShowing(), Methode 529 Iterationsanweisung 259, 269
1113
Stichwortverzeichnis J JAR, API 1004 jar 924 JAR-Dateien 43 signieren javakey 933 JAR-Files 1075 jarsigner 934 Java architekturneutral 83 Beans 832 Datenbanken 880 Datentypen 188 dezentral 81 Dokumentation 102 dynamisch 86 einfach 80 Herkunft 79 Installation 91 interpretiert 82 klein 81 leistungsfähig 83 Multithreadingfähigkeit 84 objektorientiert 81 offizielle Definition 80 portierbar 83 sicher 83 stabil 82 Verbindung mit JavaScript 811 Zugriff auf JavaScript 816 Java 2D 48 Anzeige von Bildern 493 Drucken 749 Java Archive 43, 924, 1075 JAR 40 Java Archive Tool 924 Java Beans 45 Java Collection Framework 39 Java DataBase Connectivity Siehe JDBC Java Debug Interface 32 Java Debug Wire Protocol 32 Java Development Kit Siehe JDK Java Foundation Classes 478 Java HotSpot Client Virtual Machine 1103 Java Media Framework. 48 Java Media Player 514
1114
Java Naming and Directory Interface Siehe JNDI Java Native Interface 47 Java Native Interface Siehe JNI Java Register 176 Java RMI Activation System Daemon 410 Java Runtime Environment 96 Java Schlüsselworte 190 Java Servlet Development Kit 46, 1017 Java Virtual Machine Profiler Interface Siehe JVMPI Java, 32-Bit-Register 176 Java, architekturneutral 173 Java, Debuggen des Source 915 Java, Debugger 915 Java, Geschichte 73 Java, globale Variablen 172 Java, Heap 177 Java, Konstanten 172 Java, Late Binding 169 Java, manuelle Speicherfreigabe 178 Java, Mehrfachvererbung 169, 317 Java, Polymorphismus 169 Java, primitive Daten 179 Java, Schnittstellenkonzept 170, 317 Java, Verschlüsselung 41 java.applet 960 java.awt 960 java.awt.accessibility 1005 java.awt.color 966 java.awt.datatransfer 967 java.awt.dnd 967 java.awt.event 968 java.awt.font 970 java.awt.geom 971 java.awt.im 972 java.awt.im.spi 973 java.awt.image 973 java.awt.print 976 java.awt.swing 1009 java.awt.swing.event 1014 java.awt.swing.undo 1016 java.bean.beancontext 978 java.beans 838, 977 java.beans.beancontext 838 java.io 979 Ein- und Ausgabe in Java 698 java.lang 983
Java 2 Kompendium
Stichwortverzeichnis java.lang.Class, Reflection 830 java.lang.ref 988 java.lang.reflect 830, 988 java.math 989 java.net 870, 989 java.rmi 991 java.rmi.activation 992 java.rmi.dgc 992 java.rmi.registry 993 java.rmi.server 993 java.security 995 java.security.acl 997 java.security.cert 997 java.security.interfaces 998 java.security.spec 998 java.sql 999 java.text 1000 java.util 1002 java.util.jar 1004 java.util.zip 1004 Java_g 118 Java-Applets, Einbindung von 343 Javac 125 Javac_g 118 Java-Debugger 915 Java-Disassembler 906 javadoc 911 Javadoc_g 118 javadoc-Kommentare 222 javah 909 Javah_g 118 javah-Generator 909 Java-Interpreter 130 Sicherheitsvorkehrungen 131 javakey 929 Java-Klasse, allgemeine Struktur 278 Java-Plug-In 35 HTML-Konverter 37 Java-PlugIn-Tag 358 Java-Programm, Übergabe von Argumenten 144 Java-Quellen im Internet 1099 JavaScript 796 all 807 anchor 807 applet 808 Array 808
Java 2 Kompendium
Boolean 808 Date 808 document 808 ECMA-262-Norm 1070 event 808 form 808 frame 808 Function 808 history 808 image 808 layer 808 link 808 location 808 Math 808 mimeType 808 navigator 808 Number 808 plugin 808 RegExp 808 screen 808 String 809 Style 809 Token 1070 Verbindung mit Java 811 window 809 JavaServer Pages Siehe JSP Java-Stack 176 Java-Token 189 Java-Workshop 75 javax.accessibility 1005 javax.naming 1006 javax.naming.directory 1007 javax.naming.event 1007 javax.naming.ldap 1007 javax.naming.spi 1007 javax.rmi 1008 javax.rmi.CORBA 1008 javax.sound.midi 1008 javax.sound.midi.spi 1009 javax.sound.sampled 1009 javax.sound.sampled.spi 1009 javax.swing 1009 javax.swing.event 1014 javax.swing.undo 1016 Java-Zeichenmodi 450 JButton, Swing 632
1115
Stichwortverzeichnis JCheckBox, Swing 655 JComboBox, Swing 655 jdb 915 jdb_g 118 JDBC 880 JDBC-2.0 44 JDBCODBC.DLL 888 JDBC-ODBC-Bridge 888 JDBC-Typen 887 JDialog, Swing 650 JDK 75, 87 Details 117, 905 Dokumentation 102 JDK 1.1 Lightweight UI Framework 49 JDK, Download 88 JDK, Fehlervorbeugung und Fehlerbehebung 103 JDK, Größ e 90 JDK, Plattformen 87 JFC 478 JFrame, Swing 638 JIT 84 JLabel, Swing 643 JMenu, Swing 644 JMenuBar, Swing 644 JMenuItem, Swing 644 JMF 48, 514 JNDI 1102 JNI 790 Neuigkeiten 47 join() 399 Joy, William 74 JPanel, Swing 643 JPopupMenu, Swing 655 JProgressBar, Swing 644 JRadioButton, Swing 632 JRE 96 JRootPane, Swing 626 JScrollBar, Swing 644 JSDK 30 JSException 816 JSlider, Swing 644 JSObject 816 JSP 859 JTabbedPane, Swing 655 JTextArea, Swing 655
1116
JTextField, Swing 634 JToggleButton, Swing 655 JToolBar, Swing 644 Just-in-Time-Compiler Siehe JIT JVM 174 Aufbau 175 Datentypen 179 Methodenbereich 178 JVMPI 33 K Kestrel 30 keyDown() 384 Methode 611 keytool 934 keyUp(), Methode 611 Klasse 277 Modifier 279 Thread-fähig 394 Klassendeklaration 262 Klassenmethoden 305 Klassenname 285 Klassenvariablen 326 Kodierungsprogramm 723 mit grafischer Oberfläche 729 Körper einer Schnittstelle 319 Kommentare 222 Kommentartypen, Vergleich Java zu C/C++ 783 Kompatibilität & Inkompatibilität 34 Komponenten, AWT 522 Komprimierungsverfahren 1075 Konstanten 329f. Vergleich Java zu C/C++ 783 Konstantenpool 178 Konstruktoren 289 überschreiben 295 Kontrollkästchen, AWT 545 Konvertieren von Objekten 254 von Objekten in Schnittstellen 255 Konvertierung 250 von primitiven Datentypen in Objekte und umgekehrt 255 Koordinatenraum, 2D 479 Koordinatensystem 420
Java 2 Kompendium
Stichwortverzeichnis Kopieren eines Bereichs 424 Kopierprogramm 705 Kurzschreibweisen von Operatoren 1098 L Label AWT 543 Klasse 543 Labels, Swing 643 Laden von Bildern 451 last() Datenbanknavigation 891 Methode 604 lastModified() 719 lastPageFirst(), Drucken 745 Late Binding Siehe Spätes Binden Laufstatus von Threads verändern 397 Laufzeitfehler 672, 675 layer, JavaScript-Objekt 808 Layoutmanager, AWT 524, 590 LDAPv3 1007 Leben eines Applets 140 Leere Anweisung 259, 261 LineNumberInputStream 716 Linien zeichnen 421 link, JavaScript-Objekt 808 LinkageError 987 Linken 165 List, Klasse 559 list() 720 Listen, AWT 554 Listener, Eventmodell 1.1 613 Literale 195 LiveConnect 811 LiveScript 796 Local Level Domain 68 location, JavaScript-Objekt 808 Löschen eines Bereichs 423 Logische Vergleichsoperatoren 201, 219 Lokale Variable 177, 329 loop() 511 Lose Typisierung, JavaScript 800 lostFocus(), Methode 610
Java 2 Kompendium
M makeVisible(), Methode 564 Makros, Vergleich Java zu C/C++ 783 Mandelbrot 439 mark(), Daten einlesen 703 markSupported(), Daten einlesen 703 Marshalling, ORB 846 Math, JavaScript-Objekt 808 Mausereignisse, Applet 379 MAYSCRIPT, Zugriff auf JavaScript 818 McNealy, Scott 73 MediaTracker 455 Mehrfachvererbung 162 Member 830 Menu, Klasse 580 MenuBar, Klasse 580 Menüs AWT 580 Swing 645 MenuItem, Klasse 580 Messages Siehe Botschaften metaDown() 386 Metaklassen 160, 286 Method, Reflection 830 Methoden 302 der Klasse String 200 in Schnittstellen 320 Parameterliste 310 Rückgabewerte 308 synchronisieren 308 Multithreading 413 Zugriffsspezifizierung 303 Methodenaufruf 259 Methodenbereich 178 Methodenmodifier 305 Methodennamen 309 Methodenunterschrift 303 MFC 768 Microsoft Foundation Classes Siehe MFC Migration, JDK-Versionen 118 MILNET 59 mimeType, JavaScript-Objekt 808 mkdir() 720 mkdirs() 720 modal 533
1117
Stichwortverzeichnis Modifier, Reflection 830 Modulo 202 Modulooperator 202 monolithische Systeme 842 Mosaic-Browser 61 mouseClicked(), Methode 614 mouseDown() 379 Methode 611 mouseDrag() 380 Methode 611 mouseEnter() 380 Methode 611 mouseEntered(), Methode 614 mouseExit() 380 Methode 611 mouseExited(), Methode 614 MouseListener, Schnittstelle 614 mouseMove() 380 Methode 611 mousePressed(), Methode 614 mouseReleased(), Methode 614 mouseUp() 379 Methode 611 moveToCurrentRow(), Datenbanknavigation 891 Multicast, Eventhandling 1.1 614 Multidimensionale Datenfelder 241 Multiple Inheritance 162 Multithreading 387 Deadlock 413 Methoden synchronisieren 413 Schutzmaßnahmen 412 Synchronisierte Blöcke 414 Thread-sichere Zugriffe 415 Musterapplet als Schablone 366 N Nachrichten 155 Nachrichtenselektor 161 Namenskonventionen für Klassen 285 für Methoden 310 für Pakete 332 Namensräume 194 Namensregeln für Schnittstellen 318 Namens-Server 71 NaN 179, 225 1118
National Center for Supercomputing Applications Siehe NCSA National Science Foundation 59 native 785 Native Methoden 308 native2ascii 927 Nativer Code, Einbindung 784 Native-To-ASCII Converter 927 Naughton, Patrick 73 navigator, JavaScript-Objekt 808 NCSA 61 NegativeArraySizeException 986 net.socketException: errno = 10047 115 Netscape Server API 854 Network Information Center 65 Netzwerkidentifikator 66 Netzwerkoperationen, API 989 Netzwerkzugriffe 870 new, Vergleich Java zu C/C++ 781 next() Datenbanknavigation 891 Methode 604 nextElement() 377 Nextstep 520 nextToken() 717 Nichtstandardoptionen, JDK 119 NoClassDefFoundError 987 non-modal 533 Non-Standard-Options 52 JDK 119 NoSuchFieldError 987 NoSuchFieldException 986 NoSuchMethodError 987 NoSuchMethodException 680, 986 Not a Number 179 NSAPI 854 NSF 59 NullPointerException 986 Number, JavaScript-Objekt 808 NumberFormatException 986 O Object Application Kernel 74 Object Application Kernel Siehe OAK Object Request Broker Siehe ORB Object Serialization 44, 937
Java 2 Kompendium
Stichwortverzeichnis ObjectInput 699, 715 Objective-C, Ahn von Java 79 Object-Klasse 169, 278 ObjectOutput 699, 715 ObjectOutputStream 715 Objektdeklaration 263 Objekte, allgemeine Erklärung 153 Objektklasse 160 Objektmethoden 153, 305 Objektorientierte Analyse Siehe OOA Objektorientierte Programmierung Siehe OOP Objektorientiertes Design Siehe OOD Objektorientiertheit, Vergleich Java zu C/C++ 784 Objektorientierung 151 von Java 169 Objektströme 715 Objektvariable 326 ODBC 44, 883 Datenbank registrieren 892 Öffentliche (public-) Schnittstellen 318 Öffentliche Klassen 280 Öffentliche Methoden 304 Offscreen-Puffer 507 Oktalrechnung 1092 oldjava 41, 130 OLE 767 OOA 167 OOA-Schichtenmodell 168 OOD 168 OOP 152 opcode 175 Open Database Connectivity Siehe ODBC OpenStep 520 openStream() 873 Operandenanzahl 249 Operanden-Stack 177 Operationen mit Booleschen Variablen 226 mit ganzzahligen Variablen 232 mit Gleitkommazahlen 230 mit Zeichenvariablen 227 Operationscode Siehe opcode Operatorassoziativität 246 Operatoren 201, 248 binäre 249 dyadische 249
Java 2 Kompendium
JavaScript 802 monadische 249 Postfixoperatoren 249 Präfixoperatoren 249 tenärer 249 triadischer 249 Vergleich Java zu C/C++ 782 Operatorvorrang 247 Optionsfelder, AWT 545 optop 176 ORB 845 org.omg.CORBA 845 out, System 713 OutOfMemoryError 987 OutputStream 698 Overflow 232 Overloading 311 Overriding 164 P package, Vergleich Java zu C/C++ 781 Packages 331 Pageable, Drucken 748 pageDialog(), Drucken 749 paint() 419 paint-Modus 450 Paket 331 anonymes 336 Panels AWT 526 Swing 643 Parameterliste von Methoden 310 pc 176 Perl 854 Persistent 831 PGP 41, 1089 Pipe 697 PipedInputStream 699, 714 PipedOutputStream 699, 714 Plattformunabhängigkeit auf Quellebene 180 binär 173 play() 511 Plug-In, Java 35 plugin, JavaScript-Objekt 808 PlugIn-Tag 358
1119
Stichwortverzeichnis Pointer Sicherheit 759 Vergleich Java zu C/C++ 777 Policy-Tool 935 Polygone 427 Polymorphismus 164 Position des Applets in der Webseite 353 Post-Dekrement 259 postEvent(), Methode 606 Post-Inkrement 259 Präprozessor, Vergleich Java zu C/C++ 783 Pre-Dekrement 259 Pre-Inkrement 259 PreparedStatement 901 Pretty Good Privacy Siehe PGP previous() Datenbanknavigation 891 Methode 604 Primitiver Datentyp 223 konsistente Größ e 180 print, Drucken 748 print() Drucken 745, 749 PrintStream 713 Printable, Drucken 748 printAll(), Drucken 745 printDialog(), Drucken 749 PrinterGraphics, Drucken 748 PrinterJob, Drucken 748 PrintJob, Drucken 744 println(), PrintStream 714 PrintStream 713 Priorität von Operatoren 221 Private Methoden 305 Program Counter Siehe PC Programmzähler Siehe PC Progressbar, Swing 645 Proxy, Reflection 830 Prozess vom Java-Sourcecode zum Java-Programm 174 Public-Key-Verschlüsselung 41 Punktnotation 331 PushbackInputStream 717
1120
Q Quickinfos für Komponenten 645 R Radiobutton, Swing 631 RandomAccessFile 710, 722 Beispiel 726 Dateizugriffe 698 read(), Einlesen von Datenströmen 701 readBoolean() 711 readByte( 711 readByte(), Beispiel 699, 724 readChar() 711 Beispiel 706 readDouble() 711 readFloat() 711 readFully() 711 readInt() 711 readLine() 711 readLong() 711 readObject() 715 readShort() 711 readUnsignedByte() 711 readUnsignedShort() 711 readUTF() 711 Rechteck zeichnen 422 Rectangle2D.Double 485 Reference Objects 47 Referenzierung eines Java-Applets 349 Referenztypen 236 Reflection 829, 988 ReflectPermission, Reflection 830 RegExp, JavaScript-Objekt 808 Relationale Operatoren 214 Remote Method Invocation Interfaces Siehe RMI Remote Method Invocation Siehe RMI Remote Object Registry 937 Remote Procedure Call Siehe RPC Remote-Prozedurenaufruf Siehe RPC renameTo() 720 Rendering 499 rendering pipeline 499 repaint() 419 replaceItem(), Methode 562 replaceText(), Methode 571 reset(), Daten einlesen 703
Java 2 Kompendium
Stichwortverzeichnis resize(), Methode 528 ResponseStream, Servlets 863 return-Anweisung 274 RGB, Farbraum 506 RGB-Modell 434 RMI 44, 843, 937 RMI Activation System Daemon 937 RMI Stub Converter 936 rmic 936 rmid 937 RMI-IIOP 1008 rmiregistry 937 Router 66 RPC 844 RSA 1090 rt.jar 51, 93 Rückgabewerte, Methoden 308 run() 394 RuntimeError, Klasse 680 RuntimeException 986 RUNTIME-Modul 93 S Sandbox 761 Sandkasten, Java-Sicherheit 761 Schaltflächen, AWT 538 Schieber, AWT 572 Schleifenlokale Variablen 272 Schlüssel, Datenbanken 882 Schlüsselworte 190 Java 190 Schnittstelle 317 Erstellung 318 Implementierung 322 Vergleich Java zu C/C++ 779 Verwendung 323 Schnittstellendeklaration 263, 318 Schutzanweisung 260, 276 Schwarze Witwen 773 screen, JavaScript-Objekt 808 Scrollbar, Klasse 572 ScrollPane, Klasse 577 SDK 87 Second Level Domain 68 SecurityException 986 SecurityManager 125, 756, 763, 874 seek() 723 Java 2 Kompendium
selbstextrahierenden Programme 1082 select(), Methode 555, 563, 569 selectAll(), Methode 570 Selektor 165 SequenceInputStream 716 Serial Version Command 937 Serialization 829 serialver 937 Server Side Includes Siehe SSI Server-Parsing 867 Server-Skeletons, CORBA 847 ServerSocket 795, 874 service(), Servlets 864 ServletContext 862 ServletException 864 ServletInputStream 866 ServletOutputStream 866 Servlets 46, 853 setAlignment(), Methode 544, 594 setBackground() 438 setCheckboxGroup(), Methode 549 setColor() 436, 502 setColumns(), Methode 595 setComposite() 488 setCopies(), Drucken 749 setCurrent(), Methode 549 setCursor(), Methode 530 setDaemon() 411 setEchoCharacter(), Methode 571 setEditable(), Methode 570 setEnabled(), Methode 539 setFont() 447 setForeground() 438 setIcon() 645 setLabel(), Methode 538 setLayout(), Methode 592 setLineIncrement(), Methode 574 setLineNumber() 716 setLookAndFeel(), Swing 627 setMaxPriority() 406 setMenuBar(), Methode 580 setMultipleSelections(), Methode 564 setName(), Threads 400 setPageable(), Drucken 749 setPaint() 485, 502 setPriority() 401 setRenderingHints() 501 1121
Stichwortverzeichnis setResizable(), Methode 531 setRows(), Methode 595 setSize(), Methode 528 setState(), Methode 548 setStub() 378 Setter-Methoden, Java Beans 839 setText(), Methode 543, 569 setTitle(), Methode 530 setToolTipText() 645 setVisible(), Methode 529 setXORMode() 450 SFX-Archive 1082 Shape 485 Sheridan, Mike 73 shiftDown() 386 Short-Circuit-Evaluation 219 show(), Methode 529, 604 showDocument() 376 showStatus(String msg) 370 Sicherheitskonzept 755 Sicherheitsmanager 763 Sicherheitsschnittstelle, Erweiterung 40 Sicherheitstools 929 Signers 930 Single Inheritance 162 Skeletons, CORBA 847 skip(), Daten einlesen 703 skipBytes() 712 Slider, Swing 644 Small Talk, Ahn von Java 79 Socket 795, 844, 874 Datagram 878 serverseitige 876 Software Development Kit Siehe SDK Sound & Töne 510 Spätes Binden 166 Speicherbereinigung 178 in Java chronologische Ablauf 298 Speichermanagement 296 Vergleich Java zu C/C++ 779 Sprunganweisungen 260, 272 SQL 881 API 999 SQL-3-Datentypen 44 Src.jar 93 src.jar 51, 93 1122
src.zip 93 SRC_OVER 503 sRGB 506 SSI 867 Stack 176 StackOverflowError 987 Standard-Adapter, Eventhandling 1.1 632 Standardoptionen, JDK 119 stark typisiert 82 start() 140 Threads 394 static 305 statusAll(), MediaTracker 456 statusID(), MediaTracker 456 stop() 140, 511 Threads 396 StreamTokenizer 699, 717 String, JavaScript-Objekt 809 StringBufferInputStream 699, 708 StringIndexOutOfBoundsException 986 Strings, Vergleich Java zu C/C++ 778 StringTokenizer 699 stringWidth() 450 Ströme gefilterte 708 gepufferte 709 Stroking 502 Strom 697 Structured Query Language Siehe SQL Stub-Dateien 909 Stubs, CORBA 847 Style, JavaScript-Objekt 809 Subklasse 159, 278 super 172, 314 Superklasse 278, 285 allgemeine Erklärung 159 Swing 49, 1009 absolute Positionierung 634 anonyme Klassen zum Eventhandling 643 Applets 629 Applikation auf dem Bildschirm zentrieren 645 Beispiel eines Taschenrechners 662 Bildlaufleisten 644 Button 631 Checkboxen 655 Dialogfenster 650 Eigenständige Applikation 638 Java 2 Kompendium
Stichwortverzeichnis JButton 632 JCheckBox 655 JComboBox 655 JDialog 650 JFrame 638 JLabel 643 JMenu 644 JMenuBar 644 JMenuItem 644 JPanel 643 JPopupMenu 655 JProgressBar 644 JRadioButton 632 JScrollBar 644 JSlider 644 JTabbedPane 655 JTextArea 655 JTextField 634 JToggleButton 655 JToolBar 644 Label 643 Listenfelder 655 Look and Feel umschalten 627 Menü 645 Panel 643 Popup-Menüs 655 Progressbar 645 Radiobutton 631 Registerblätter 655 Schablone einer eigenständigen Applikation 639 setLookAndFeel() 627 Slider 644 Textfelder 634, 655 Togglebuttons 655 Toolbar 645 Swing-Eventhandling 1014 switch-Anweisung 267 Symboltabellen, Sicherheit 761 Synchronisationsanweisung 260, 276 Synchronisierte Blöcke, Multithreading 414 synchronized 413 System Error during Decompression 115 System.err 713 System.exit() 529 System.in 713 System.loadLibrary() 786, 791
Java 2 Kompendium
System.out 713 Systeme, verteilte 843 T Tags 344 Tastaturereignisse 384 TCP, erstes Auftreten 60 TCP/IP 60 TextArea, Klasse 566 Textausgabe, Zeichen-Modus 445 Textbereiche, AWT 566 TextComponent, Klasse 566 Texte, 2D Java 503 Textfelder, AWT 566 TextField, Klasse 566 TextListener, Schnittstelle 614 TexturePaint 502 textValueChanged(), Methode 614 The Fractal Geometry of Nature 439 this 299 This program cannot be run in DOS mode 115 Thread 393f. Priorität verändern 401 ThreadDeath 987 ThreadGroup 406 Thread-Gruppe 406 Thread-sichere Zugriffe, Multithreading 415 throw 677 Exceptions 687 Throwable, Ausnahmebehandlung 677 throw-Anweisung 275 throws 678 toByteArray() 708 Token 183 Tomcat 859 Toolbar, Swing 645 Toolkit, Drucken 744 Top Level Domain 68 Ländercodes 70 Listen der wichtigsten 69 Traditionelle Kommentare 222 Transmission Control Protocol Siehe TCP Transparenz, Graphikeffekte 503 Trennzeichen 200 try 276, 681 Typen 223
1123
Stichwortverzeichnis Typenzwang, Casting 254 Typkonvertierungen 250 Typzustand 761 U UDP 879 Überladen von Methoden 170, 311 von Operatoren 170, 311 Vergleich Java zu C/C++ 783 Überlauf 232 Überschatten 172 Überschreiben 164, 171, 313 von Schnittstellenmethoden 322 UIMangager, Swing 627 Umgebungsvariablen 105 Unable to initialize threads 112 Unbekannte Fehler 116 Undefined variable ... 315 Unerreichbare Anweisung 260, 277 Ungültiger Befehl oder Dateiname 104 Unicode, Vergleich Java zu C/C++ 781 Unicode-Zeichensatz 185 Programm zur Ausgabe 186 Uniform Resource Locator Siehe URL Unix-Debugger 915 UnknownError 987 Unmarshalling, ORB 846 unread() 717 Unreliable Datagram Protocol Siehe UDP UnsatisfiedLinkError 988 Unsupported version of Windows Socket API 115 UnsupportedClassVersionError 988 UnsupportedOperationException 986 Unterschied zwischen prozeduraler und objektorientierter Denkweise 156 Update 418 update() 419 URL 71 URL-Codierung, CGI 855 URLconnection 874 UTF-8-Codierung 188 V valid() 722 Variablen 325
1124
in Schnittstellen 320 JavaScript 800 schleifenlokal 329 vars 176 VBScript 796 Zugriff auf Java 825 Vector 244, 716 Vektoren 716 Verbindung Java/C, konventionelle Technik 785 Vererbung 161 Vergleich Java zu C/C++ 779 Vergleichsoperatoren 201, 213 Verifier 761 VerifyError 988 Verschachtelte Arrays 241 Verschlüsselung 41 in Java 41 Verteilte Systeme 842 Verwendung von Paketen 331 Verzeichnis erstellen 720 Inhalt auslesen 720 Vigenere-Chiffre 1088 Viren, Java-Sicherheit 760 Virtual Reality Modeling Language Siehe VRML VirtualMachineError 988 Virtuellen Maschine Siehe JVM Virtueller Computer Siehe JVM Virtueller Speicher 177 Vordergrundfarben 438 VRML 826 W W3C 65, 343 waitForAll(), MediaTracker 456 waitForID(int id), MediaTracker 456 WAP 859 while-Anweisung 269 Wiederholungsanweisungen 269 winding rule 485 Window, Klasse 527 window, JavaScript-Objekt 809 WINDOW_DEICONIFY 538 WINDOW_DESTROY 537 WINDOW_EXPOSE 537 WINDOW_ICONIFY 538
Java 2 Kompendium
Stichwortverzeichnis WINDOW_MOVED 538 WindowListener, Eventhandling 1.1 666 Wireless Application Protocol 859 Wireless Markup Language 859 WML 859 World Wide Web-Consortium Siehe W3C Wrapper-Klassen 256 Wrapper-Objekte 169 write() Daten schreiben 705 PrintStream 713 writeBoolean() 712 writeByte() 712 Beispiel 724 writeBytes() 712 writeChar() 712 Beispiel 706 writeChars() 712 writeDouble() 712 writeFloat() 712 writeInt() 712 writeLong() 712 writeObject() 715 writeShort() 712 writeTo(), Daten schreiben 708 writeUTF() 712 WWW, Ursprung 60 WWW_HOME 116 X X.509 v3-Zertifikat 40 X.509-Zertifikate 932
Java 2 Kompendium
X/Open SQL CLI 883 Xerox 85 XHTML 859 XML 859 XOR-Modus 450 Z Zeichenbereiche, AWT 578 Zeichenkettenliterale 198 Zeichenliterale 196 Zeichnen 418 von Bildern 451 von Bögen 433 von gefüllten Bögen 434 von gefüllten Kreisen und Ellipsen 432 von gefüllten Polygonen 430 von Kreisen und Ellipsen 431 von Polygonen 427 Zeichnenoperationen 419 Zertifikate 932 exportieren javakey 933 importieren javakey 933 Zugreifen auf Elemente von Datenfeldern 239 Zuordnung 259 Zuweisungsausdruck 259 Zuweisungsoperatoren 201 Zweierkomplement 1095 Zweierkomplement-Repräsentation 230
1125
Sun Microsystems, Inc. Binary Code License Agreement READ THE TERMS OF THIS AGREEMENT AND ANY PROVIDED SUPPLEMENTAL LICENSE TERMS (COLLECTIVELY "AGREEMENT") CAREFULLY BEFORE OPENING THE SOFTWARE MEDIA PACKAGE. BY OPENING THE SOFTWARE MEDIA PACKAGE, YOU AGREE TO THE TERMS OF THIS AGREEMENT. IF YOU ARE ACCESSING THE SOFTWARE ELECTRONICALLY, INDICATE YOUR ACCEPTANCE OF THESE TERMS BY SELECTING THE "ACCEPT" BUTTON AT THE END OF THIS AGREEMENT. IF YOU DO NOT AGREE TO ALL THESE TERMS, PROMPTLY RETURN THE UNUSED SOFTWARE TO YOUR PLACE OF PURCHASE OR, IF THE SOFTWARE IS ACCESSED ELECTRONICALLY, SELECT THE "DECLINE" BUTTON AT THE END OF THIS AGREEMENT. 1. LICENSE TO USE. Sun grants you a non-exclusive and non-transferable license for the internal use only of the accompanying software and documentation and any error corrections provided by Sun (collectively "Software"), by the number of users and the class of computer hardware for which the corresponding fee has been paid. 2. RESTRICTIONS Software is confidential and copyrighted. Title to Software and all associated intellectual property rights is retained by Sun and/or its licensors. Except as specifically authorized in any Supplemental License Terms, you may not make copies of Software, other than a single copy of Software for archival purposes. Unless enforcement is prohibited by applicable law, you may not modify, decompile, or reverse engineer Software. You acknowledge that Software is not designed, licensed or intended for use in the design, construction, operation or maintenance of any nuclear facility. Sun disclaims any express or implied warranty of fitness for such uses. No right, title or interest in or to any trademark, service mark, logo or trade name of Sun or its licensors is granted under this Agreement. 3. LIMITED WARRANTY. Sun warrants to you that for a period of ninety (90) days from the date of purchase, as evidenced by a copy of the receipt, the media on which Software is furnished (if any) will be free of defects in materials and workmanship under normal use. Except for the foregoing, Software is provided "AS IS". Your exclusive remedy and Sun's entire liability under this limited warranty will be at Sun's option to replace Software media or refund the fee paid for Software.
4. DISCLAIMER OF WARRANTY. UNLESS SPECIFIED IN THIS AGREEMENT, ALL EXPRESS OR IMPLIED CONDITIONS, REPRESENTATIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT ARE DISCLAIMED, EXCEPT TO THE EXTENT THAT THESE DISCLAIMERS ARE HELD TO BE LEGALLY INVALID. 5. LIMITATION OF LIABILITY. TO THE EXTENT NOT PROHIBITED BY LAW, IN NO EVENT WILL SUN OR ITS LICENSORS BE LIABLE FOR ANY LOST REVENUE, PROFIT OR DATA, OR FOR SPECIAL, INDIRECT, CONSEQUENTIAL, INCIDENTAL OR PUNITIVE DAMAGES, HOWEVER CAUSED REGARDLESS OF THE THEORY OF LIABILITY, ARISING OUT OF OR RELATED TO THE USE OF OR INABILITY TO USE SOFTWARE, EVEN IF SUN HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. In no event will Sun's liability to you, whether in contract, tort (including negligence), or otherwise, exceed the amount paid by you for Software under this Agreement. The foregoing limitations will apply even if the above stated warranty fails of its essential purpose. 6. Termination. This Agreement is effective until terminated. You may terminate this Agreement at any time by destroying all copies of Software. This Agreement will terminate immediately without notice from Sun if you fail to comply with any provision of this Agreement. Upon Termination, you must destroy all copies of Software. 7. Export Regulations. All Software and technical data delivered under this Agreement are subject to US export control laws and may be subject to export or import regulations in other countries. You agree to comply strictly with all such laws and regulations and acknowledge that you have the responsibility to obtain such licenses to export, re-export, or import as may be required after delivery to you. 8. U.S. Government Restricted Rights. If Software is being acquired by or on behalf of the U.S. Government or by a U.S. Government prime contractor or subcontractor (at any tier), then the Government's rights in Software and accompanying documentation will be only as set forth in this Agreement; this is in accordance with 48 CFR 227.7201 through 227.7202-4 (for Department of Defense (DOD) acquisitions) and with 48 CFR 2.101 and 12.212 (for non-DOD acquisitions). 9. Governing Law. Any action related to this Agreement will be governed by California law and controlling U.S. federal law. No choice of law rules of any jurisdiction will apply.
10. Severability. If any provision of this Agreement is held to be unenforceable, this Agreement will remain in effect with the provision omitted, unless omission would frustrate the intent of the parties, in which case this Agreement will immediately terminate. 11. Integration. This Agreement is the entire agreement between you and Sun relating to its subject matter. It supersedes all prior or contemporaneous oral or written communications, proposals, representations and warranties and prevails over any conflicting or additional terms of any quote, order, acknowledgment, or other communication between the parties relating to its subject matter during the term of this Agreement. No modification of this Agreement will be binding, unless in writing and signed by an authorized representative of each party. For inquiries please contact: Sun Microsystems, Inc. 901 San Antonio Road Palo Alto, California 94303 U.S.A. JAVA 2 SOFTWARE DEVELOPMENT KIT STANDARD EDITION VERSION 1.3 SUPPLEMENTAL LICENSE TERMS These supplemental license terms ("Supplemental Terms") add to or modify the terms of the Binary Code License Agreement (collectively, the "Agreement"). Capitalized terms not defined in these Supplemental Terms shall have the same meanings ascribed to them in the Agreement. These Supplemental Terms shall supersede any inconsistent or conflicting terms in the Agreement, or in any license contained within the Software. 1. Internal Use and Development License Grant. Subject to the terms and conditions of this Agreement, including, but not limited to, Section 2 (Redistributables) and Section 4 (Java Technology Restrictions) of these Supplemental Terms, Sun grants you a non-exclusive, non-transferable, limited license to reproduce the Software for internal use only for the sole purpose of development of your JavaTM applet and application ("Program"), provided that you do not redistribute the Software in whole or in part, either separately or included with any Program. 2. Redistributables. In addition to the license granted in Paragraph 1 above, Sun grants you a non-exclusive, non-transferable, limited license to reproduce and distribute, only as part of your separate copy of JAVA(TM) 2 RUNTIME ENVIRONMENT STANDARD EDITION VERSION 1.3 software, those files specifically identified as redistributable in the JAVA(TM) 2 RUNTIME ENVIRONMENT STANDARD EDITION VERSION 1.3
"README" file (the "Redistributables") provided that: (a) you distribute the Redistributables complete and unmodified (unless otherwise specified in the applicable README file), and only bundled as part of the JavaTM applets and applications that you develop (the "Programs:); (b) you do not distribute additional software intended to supersede any component(s) of the Redistributables; (c) you do not remove or alter any proprietary legends or notices contained in or on the Redistributables; (d) you only distribute the Redistributables pursuant to a license agreement that protects ! Sun's interests consistent with the terms contained in the Agreement, and (e) you agree to defend and indemnify Sun and its licensors from and against any damages, costs, liabilities, settlement amounts and/or expenses (including attorneys' fees) incurred in connection with any claim, lawsuit or action by any third party that arises or results from the use or distribution of any and all Programs and/or Software. 3. Separate Distribution License Required. You understand and agree that you must first obtain a separate license from Sun prior to reproducing or modifying any portion of the Software other than as provided with respect to Redistributables in Paragraph 2 above. 4. Java Technology Restrictions. You may not modify the Java Platform Interface ("JPI", identified as classes contained within the "java" package or any subpackages of the "java" package), by creating additional classes within the JPI or otherwise causing the addition to or modification of the classes in the JPI. In the event that you create an additional class and associated API(s) which (i) extends the functionality of a Java environment, and (ii) is exposed to third party software developers for the purpose of developing additional software which invokes such additional API, you must promptly publish broadly an accurate specification for such API for free use by all developers. You may not create, or authorize your licensees to create additional classes, interfaces, or subpackages that are in any way identified as "java", "javax", "sun" or similar convention as specified by Sun in any class file naming convention. Refer to the appropriate version of the Java Runtime Environment binary code license (currently located at http:// www.java.sun.com/jdk/index.html) for the availability of runtime code which may be distributed with Java applets and applications. 5. Trademarks and Logos. You acknowledge and agree as between you and Sun that Sun owns the Java trademark and all Java-related trademarks, service marks, logos and other brand designations including the Coffee Cup logo and Duke logo ("Java Marks"), and you agree to comply with the Sun Trademark and Logo Usage Requirements currently located at http:// www.sun.com/policies/trademarks. Any use you make of the Java Marks inures to Sun's benefit.
6. Source Code. Software may contain source code that is provided solely for reference purposes pursuant to the terms of this Agreement. 7. Termination. Sun may terminate this Agreement immediately should any Software become, or in Sun's opinion be likely to become, the subject of a claim of infringement of a patent, trade secret, copyright or other intellectual property right.